ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PostHog 沙箱化 Eval 编写指南:SandboxedEvalCase 字段契约、Seeder 与评分器全解析

PostHog 沙箱化 Eval 编写指南:SandboxedEvalCase 字段契约、Seeder 与评分器全解析 PostHog 沙箱化 Eval 编写指南SandboxedEvalCase 字段契约、Seeder 与评分器全解析【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本文面向 PostHog 仓库中从事 AI Agent 评测eval编写的工程师系统讲解沙箱化评测用例SandboxedEvalCase的字段级契约、Seeder 数据播种契约、output字典与LogParser日志访问层以及 Judge 评分器的编写规范。读完本文你将能够基于 authoring-reference.md 的规范结合 config.py、base.py、log_parser.py 等源码独立编写可复现、可判分、可调试的沙箱化评测用例与配套评分器。一、理解沙箱化 Eval 的整体结构在深入字段细节之前先建立整体认知。PostHog 的评测体系位于 products/posthog_ai/eval_harness/其核心思路是每个 eval case 在一个隔离的沙箱中运行一个真实的编码 Agent拥有独立的 org/team/userAgent 被给予一个自然语言任务prompt执行工具调用完成工作运行结束后评测框架收集 Agent 的产物AgentArtifacts与完整会话日志raw JSONL交给一组评分器scorers打分结果经 Braintrust 汇总并写入 PostHog 可观测性体系。关键文件一览文件职责config.py定义SandboxedEvalCase、AgentArtifacts等数据模型字段契约的源头base.py评测运行编排_SandboxedEvalRun、SandboxedEval()入口、timeout/error 语义log_parser.py统一的日志访问层LogParser、ToolCall、SkillCall、名称归一化scorers/评分器JudgedScorerLLM 裁判、确定性评分器、评分契约seeders/数据播种insight.py、survey.py、common.py公共工具data_setup.py固定EVAL_SEED保证数据集逐字节可复现框架的权威事实以源码为准文档契约若与代码冲突代码优先——config.py、base.py、log_parser.py是三个事实来源sources of truth。二、SandboxedEvalCase字段级契约SandboxedEvalCase定义在 config.py继承自BaseEvalCase后者提供name、prompt、followups、expected、metadata。字段契约如下字段类型含义namestr用例名称。同时作为--eval substr过滤目标与 per-case 日志文件名在套件内必须唯一promptstr交给 Agent 的自然语言任务描述repo_fixturestr 仅作信息记录tracking用途expecteddict {}每个评分器的期望值以各评分器的_name()为键。评分器只读取自己的子字典缺键意味着走默认行为或自我跳过metadatadict {}任意跟踪/过滤元数据disable_bundled_skillsbool False在 Agent 启动前清空原生 skill 目录。用于评估独立 skill 分发路径的场景setupCallable[[CustomPromptSandboxContext], dict] \| None播种钩子。从序列化中排除callable 无法通过 Braintrust 的 JSON 往返runner 会按名称从原始 case 重新绑定2.1 关于 skills 与超时的设计约束没有 per-case 的skills附件字段。Skills 每次运行构建一次默认使用原生捆绑交付native bundled delivery。--skill-delivery exec开启 MCP 分发会为每个 case 清空原生 skillsdisable_bundled_skills是额外的 per-case 覆盖项。没有 per-case 超时--case-timeout是运行级别参数从沙箱获取sandbox acquisition开始计时。这一点在 base.py 中有对应实现Agent 的预算计时在团队设置完成之后才开始因此信号量等待与 ClickHouse 复制都不会消耗超时预算超时后_timeout_output()返回exit_code1、stderr 注明超时的产物字典被记为得分 0 的 Agent 失败而非基础设施错误。2.2followups多轮对话用例BaseEvalCase.followupsconfig.py允许在prompt之后逐轮追加用户消息保持同一 Agent 会话存活1 len(followups)轮用于评测路由或行为在多轮对话中的变化。注意空列表默认保持单轮行为不变仅沙箱化用例支持one-shot runner 每个 case 只执行一次模型调用从不读取该字段。三、Seeder 契约播种评测数据Seeder 是评测的前置数据准备钩子契约形式如下def seed_my_domain(context: CustomPromptSandboxContext) - dict[str, Any]: ...3.1 Seeder 执行语义同步执行使用普通 Django ORM /sync_executeharness 通过asyncio.to_thread运行它见 base.py因为 Django 的 async 安全守卫会拒绝在 async 上下文中进行同步 ORM 调用。每个 case 运行一次在隔离的 team/user 铸造之后、prompt 分发之前。context是冻结 dataclass实际使用中 seeder 读取context.team_id和context.user_id。返回值成为该 case 每个评分器的output[seed]。抛异常会将该 case 标记为基础设施错误infra error排除在平均值之外而不是计 0 分。3.2 现有 Seeder 一览Seeder返回值seeders/insight.py::seed_insight_noise{noise_count, lookup_insights: [{id, short_id, name}]}seeders/survey.py::seed_survey_feature_flags{feature_flags_by_key: {...}}data_warehouse/seeder.py::seed_warehouse_schemaper-needle 元数据含queryable对象存储凭据缺失时为 False——依赖它的评分器自我跳过error_tracking/seeders.py::seed_error_tracking_issues{lookup_issues: [{id, name}]}experiments/seeders.py::seed_*播种的实验 id、flag key、split 签名3.3 公共工具seeders/common.pycommon.py 提供所有 seeder 都需要的共享构件LOOKUP_PREFIX [lookup]保证在生成的噪声中绝对不出现的子串因此 prompt 可以无歧义地指名查找实体DEFAULT_NAME_SEED 42固定的 mimesis 种子保证噪声可复现make_name_providers(seed)返回(rnd, text, person)提供者三元组NameProvidersfrozen dataclass同一种子生成一致的噪声名称。以 insight.py 为例它批量创建NOISE_INSIGHT_COUNT 1000条看似合理的洞察insight外加一组确定性的查找洞察名称形如[lookup] Northern Lights FunnelLOOKUP_NAMES与模糊查找名Monthly Active Users (Hedgebox)FUZZY_LOOKUP_NAMES其括号品牌格式是噪声生成器无法产生的碰撞仍不可能。[lookup]前缀保证与噪声生成器零碰撞prompt 因而可以逐字硬编码查找名称。四、评分器收到的output字典评分器接收的output由AgentArtifacts.model_dump()config.py与 runner 附加字段合并而成base.py来自AgentArtifactsexit_codeAgent 是否干净退出、stdout、stderrgit_diff从工具调用中提取的 git difffiles_changed文件路径列表test_exit_code/test_output测试工具调用的推断退出码与输出未运行则为 Nonelint_exit_code/lint_outputtool_call_count0 且exit_code非零 运行在开始工作前就失败被当作基础设施错误而非得分duration_seconds、pr_urlRunner 附加last_message最终 assistant 文本messages解析后的消息列表raw_log原始 JSONL——LogParser的输入seedsetup 钩子的返回字典prompt4.1 错误语义per-case 超时→ 得分 0 的 Agent 失败exit_code1stderr 注明 timeout。其他任何异常demo 复制、seeder、provisioning→ 重新抛出为 errored case从得分平均值中排除见 base.py基础设施故障不应该把 harness 的过错记成 Agent 的 0 分。五、LogParser评分器的日志访问层log_parser.py 包装acp_log.parse_log提供类型化访问器让评分器可以直接提出是否调用了 skill X工具 X 每次调用的输入结果是什么这类问题而不必重新遍历扁平消息列表。5.1 核心 API方法说明LogParser.cached(raw_log, initial_prompt)记忆化优先使用它而非构造函数保证同一 case 的日志在所有评分器间只解析一次_cached_parser使用functools.lru_cache(maxsize32)get_tool_calls(nameNone) - list[ToolCall]按时间顺序的非 Skill工具调用可按归一化名称过滤get_skill_calls(nameNone)/was_skill_called(name)Skill工具调用SkillCall(name, args, call_id, output, is_error, position)get_user_prompt()首个用户文本回退到initial_promptget_final_agent_message()最后一个 assistant 文本块或Nonenormalize_tool_name(name)把mcp__server__tool剥成toolINFO_SYNTHETIC_PREFIX __info__:合成调用名把exec {command: info tool}与 Claude Code 的ToolSearch(select:...)统一为工具 schema 已加载信号5.2ToolCall字段name归一化exec会被解包为内部工具、input: dict、output: str、is_error配对结果报错或无配对结果时均为 True、call_id、position按时间顺序的索引、raw_name、is_exec_unwrapped、requested_output_formatjson/optimized/None。5.3 exec 解包与 schema 发现判定_parse_exec_command识别两种 CLI 形状info tool→(__info__:tool, {})call [--json] tool json→(tool, 解析后的 JSON)。其余形状search、tools、schema、畸形命令返回None回退到原始exec表示。is_schema_discovery_call(call)用于识别execute-sql的目录查找调用查询information_schemaMCP 指令强制要求先做目录查找这类调用与回答用户问题的查询落在同一个execute-sql列表中因此评估 SQL 用法的评分器必须先丢弃它们否则评的是 Agent 被告知要遵循的指令而不是它选择的路径。六、Judge 评分器LLM 裁判的编写形状Judge 评分器基于JudgedScorerscorers/judged.py标准形状如下from typing import Any from braintrust import Score from products.posthog_ai.eval_harness.scorers import BINARY_CHOICE_SCORES, JUDGE_MODEL, JudgedScorer class SaysHello(JudgedScorer): def __init__(self, **kwargs): super().__init__( namesays_hello, prompt_template...did the agent greet the user?...\nmsg{{output.last_message}}/msg\nAnswer yes or no., choice_scoresBINARY_CHOICE_SCORES, modelJUDGE_MODEL, max_completion_tokens128, **kwargs, ) def _prepare(self, output, expected) - dict[str, Any] | Score: if not output or not output.get(last_message): return Score(nameself._name(), score0.0, metadata{reason: No final message}) return {output: {last_message: output[last_message]}}6.1 关键语义_prepare返回模板变量prompt_template中的{{output.*}}/{{expected.*}}针对返回字典中的output/expected值解析而非原始 case 输出。共享常量BINARY_CHOICE_SCORES{yes: 1.0, no: 0.0}GRADED_ALIGNMENT_CHOICE_SCORES六档量表perfect1.0 / near_perfect0.9 / slightly_off0.75 / somewhat_misaligned0.5 / strongly_misaligned0.25 / useless0.0JUDGE_MODEL gpt-5.4。短路与异常都映射到score0.0而非NoneBraintrust 把None当作跳过并从聚合中剔除会静默隐藏坏掉的裁判与缺失的查询输入。需要真正自我跳过的裁判如分级工具未运行且另一评分器已覆盖应显式返回Score(scoreNone)见 judged.py。评分器通过 Braintrust 的eval_async分发因此JudgedScorer继承AsyncOnlyScorerMixin把永不使用的同步分支变成显式报错scorers/contract.py。6.2 确定性评分器deterministic.py 提供无需 LLM 的二进制评分器常见于 Agent 卫生检查ExitCodeZero进程是否干净退出沙箱 harness 自动注入禁止手动添加NoToolCall(forbidden)Agent 是否成功调用任何禁用工具失败调用允许——模型可以尝试并失败RequiredToolCall(required)是否成功调用至少一个必需工具如强制read-data-schema让 Agent 先验证事件/属性存在再跑查询AnswerToolCallNot(forbidden, preferred)答案是否来自非禁用工具——只读取答案产出型调用is_answer_query_toolquery-*前缀的输入化 runner 或execute-sql且排除 schema 发现并让preferred中的类型化工具优先于尾随的execute-sql验证调用。七、确定性纪律Determinism Discipline评测数据集在固定EVAL_SEEDdata_setup.pyb1ef3c66-5f43-488a-98be-6b46d92fbcef下逐字节可复现噪声生成器使用固定种子。编写 eval 时须遵守精确引用Hedgebox 数据集参考products/posthog_ai/evals/AGENTS.md中列出的事件、属性、flag 与 insight 名称优先相对日期范围-30d、-8w、-6m与基于形状的断言绝不硬编码绝对计数——模拟变化时它们会漂移镜像播种的 insight 时使用filterTestAccountsTrue组数学使用account组类型索引 0共享常量在 synthesizer/seeder、prompt 与评分器之间通过 import 共享常量如 warehouse needle 模式而不是重复书写字符串。八、运行与调试配套实操评测在仓库根目录的 flox shell 中通过hogli evals运行详见 running-evals.mdflox activate -- bash -c hogli evals --list # 列出已发现的套件 ID hogli evals product/eval_example::eval_my_thing --eval case-name # 单个套件单个 case必填环境变量所有套件需要BRAINTRUST_API_KEY、LLM_GATEWAY_ANTHROPIC_API_KEY任何沙箱化套件另需SANDBOX_JWT_PRIVATE_KEYCodex runtime 另需LLM_GATEWAY_OPENAI_API_KEY。本地 Docker 模式下默认上限 4 个并发沙箱、每沙箱最高 16GB 内存远程 Modal 模式推荐并发无界务必显式指定--max-sandboxes。调试提示失败 case 可用--keep-sandbox-containers保留容器检查每次真实调用都会把 stdout/stderr 镜像到products/posthog_ai/eval_harness/logs/harness/timestamp_id.loglogs/harness/latest.log指向最新运行case 启动后其 Agent 日志目录包含case.jsonl、case.artifacts.json、case.summary.txt常见失败flox 外运行导致 personhog Rust 构建失败、Docker daemon 停止、Modal 凭据缺失、Funnel 端口被占用、给 one-shot-only 套件传了 provider 标志。九、测试验证与进一步阅读评测框架自带大量单元测试是验证你理解的最佳证据日志解析见 test_log_parser.py评分器契约见 test_scorer_contract.pySeeder 见 test_eval_seeders.py沙箱 harness 见 test_sandboxed_harness.py。完整运行与远程配置请阅读 running-evals.md更广泛的评测场景说明见 eval_harness/README.md。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表