PostHog 沙箱化 Agent 评测框架(Sandboxed Eval Harness)完整指南

发布时间:2026/9/16 18:31:36
PostHog 沙箱化 Agent 评测框架(Sandboxed Eval Harness)完整指南 PostHog 沙箱化 Agent 评测框架Sandboxed Eval Harness完整指南【免费下载链接】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本篇指南以 products/posthog_ai/eval_harness/README.md 为核心骨架结合 harness 内部实现、requirements.py、CLI 解析 与真实套件源码如 eval_sql.py展开帮助你理解 PostHog 如何在真实沙箱中运行编码型 Agent 的离线评测、如何选择沙箱 Provider、如何新增自己的评测套件。一、背景与定位为什么需要一套独立的沙箱化评测框架PostHog 的 AI 能力Analytics、Session Replay、Feature Flags 等本质上是让 Agent 在真实产品环境里干活。要验证这些 Agent 干得好不好PostHog 构建了这套Sandboxed agent evals沙箱化 Agent 评测评测运行的是真实的编码 Agent放在真实的沙箱里沙箱面对的是一个预先播种好的 Hedgebox 演示项目一个虚构的云存储 SaaS类似 Dropbox每个评测 case 拥有独立的 org/team/usercase 之间互不可见、互不污染状态。它与 PostHog 原有的 CI 评测ee/hogai/eval/ci/有本质区别维度ee/hogai/eval/ci/products/posthog_ai/eval_harness/运行方式跑在 pytest 下session fixture独立的评测 harness不走 pytest基础设施pytest fixture 生命周期内构建一次启动共享基础设施测试数据库、Django live server、LLM gateway、MCP server、Temporal再并发运行所有选中的套件并行度套件之间严格串行单事件循环上多套件并发全局信号量限制沙箱数量旧的 pytest 方案里套件们排队等待而沙箱容量却闲置新 harness 把同一套基础设施只启动一次然后用共享信号量同时约束沙箱负载和团队数据准备负载从而把墙钟时间压下来。此外harness 还支持非沙箱化的套件类型one-shot见下文Suite kinds。一次运行只会启动所选套件需要的那部分基础设施——如果只跑 one-shot 套件则完全不会启动沙箱 Provider、Temporal、live server、LLM gateway 或 MCP server。二、架构总览一次运行会拉起什么harness 的核心目录结构如下products/posthog_ai/eval_harness/ ├── README.md # 使用指南本文骨架 ├── config.py # BaseEvalCase / SandboxedEvalCase / AgentArtifacts ├── base.py # _BaseEvalRun / _SandboxedEvalRunExitCodeZero 自动注入 ├── one_shot.py # OneShotEval 一次性进程内模型调用运行器 ├── runner.py # 沙箱化 case 的 Agent 运行封装 ├── acp_log.py / log_parser.py # ACP 会话日志解析 ├── log_sink.py # 本地 case 日志落盘 ├── trace_events.py # PostHog 追踪事件上报 ├── engines/ # EvalEngine 抽象 Braintrust 引擎 │ ├── base.py / types.py / registry.py / braintrust.py ├── harness/ # 编排核心 │ ├── README.md # 内部工作原理 │ ├── AGENTS.md # 修改 harness 时要保持的不变量 │ ├── __main__.py / cli.py # 入口与参数解析 │ ├── env_preflight.py # .env 加载与按 kind 校验 │ ├── requirements.py # SuiteKind / Infra 与映射 │ ├── providers.py # docker / modal 沙箱策略 │ ├── tunnels.py # Modal 专用 Tailscale Funnel │ ├── django_env.py / live_server.py / services.py / temporal_env.py │ ├── demo_data.py # 主 Hedgebox team 播种 按 case 克隆 │ ├── discovery.py # 按约定发现 eval_*.py │ ├── lifecycle.py # 引导、运行、拆除编排 │ └── ... ├── scorers/ # 评分器deterministic / judged / tracing / contract ├── seeders/ # 数据播种工具 └── test/ # 对 harness 自身的 pytest 测试2.1 Suite kind → 基础设施的映射每个评测模块通过模块级SUITE_KIND定义在 harness/requirements.py声明其套件如何执行未声明即为 sandboxedKind标记每个 case 执行什么启动的基础设施sandboxed默认无或SuiteKind.SANDBOXED真实沙箱里的真实编码 Agent全部数据库、personhog、live server、LLM gateway、MCP、demo data、沙箱Temporalone-shotSUITE_KIND SuiteKind.ONE_SHOT一次进程内模型调用测试数据库、personhog、demo data源码中Infra是一个枚举_IMPLIES字典维护了基础设施间的隐含依赖requirements.py_IMPLIES { Infra.LIVE_SERVER: frozenset({Infra.DATABASE}), Infra.LLM_GATEWAY: frozenset({Infra.LIVE_SERVER}), Infra.MCP_SERVER: frozenset({Infra.LIVE_SERVER}), Infra.DEMO_DATA: frozenset({Infra.DATABASE, Infra.PERSONHOG}), Infra.SANDBOX: frozenset({Infra.LLM_GATEWAY, Infra.MCP_SERVER, Infra.DEMO_DATA}), } INFRA_BY_KIND { SuiteKind.SANDBOXED: frozenset(Infra), # 全部 SuiteKind.ONE_SHOT: frozenset({Infra.DATABASE, Infra.PERSONHOG, Infra.DEMO_DATA}), }expand()会对需求集合做闭包展开因此一个 kind 永远不可能请求某个服务却漏掉它依赖的服务harness 只启动所选套件需求的并集若某个套件少声明了 kind、其 runner 又用到了没启动的基础设施会直接大声失败而不是静默出错。2.2 启动序列Boot sequence引导阶段刻意是同步的在任何事件循环创建之前因为 Django ORM 的异步安全保护会拒绝在 async 上下文中做同步 ORM 调用harness/README.md__main__创建运行转录transcript加载仓库根目录.env不覆盖 shell 已导出的值setup_django()设置DEBUG/TEST/IN_EVAL_TESTING、强制SELF_CAPTURE0执行django.setup()与setup_test_environment()EvalDatabase.setup()创建测试数据库驱动 PostHog 自身的 eval 数据库设置persons 数据库、ClickHouse启动personhog-replica:15051与personhog-router:15052指向测试 persons 数据库——任何查询之前必须先起来死掉的 router 会污染 30s 的负向 group-types 缓存EvalLiveServer在0.0.0.0:18000提供 PostHog 完整 ASGI 应用含沙箱事件接入路由LLM gateway:13308启动并构建本地 skillsbundled 模式下 MCP server:18787以关闭 exec skill 分发启动exec 模式下把 skills 打包、启动本地归档 server:18788MCP 以内容寻址 URL 并强制开启分发Dockerposthog-sandbox-base镜像新鲜度检查posthog/agent版本更新或 Dockerfile 变更时重建ModalTailscale Funnel 起来把回调服务暴露到公网bundled 模式把构建好的 skills 以 bind-mount 方式挂进 Docker 沙箱、烤进 Modal 镜像exec 模式抑制这些 skills 并在每个 case 前清空原生 skill 目录播种主 Hedgebox team。随后进入异步阶段在主循环启动 Temporal dev server、应用 Provider 的设置覆盖、终止陈旧 workflow、在独立线程跑 Temporal worker最后用asyncio.gather扇出所有套件。拆除则按ExitStack/AsyncExitStack逆序进行atexit钩子与子进程管理器的信号处理覆盖 Ctrl-C 场景。三、快速开始运行评测3.1 环境准备从flox shell运行——personhog 构建需要 flox 的 Rust 工具链cargo、pkg-config、OpenSSL在 flox 之外 preflight 构建会失败。原因在于person 与 group 读取走 personhog无 ORM 回退所以 harness 会从rust/构建并运行personhog-replicapersonhog-router指向测试 persons 数据库。首次构建要编译这些 crate可能耗时数分钟之后的构建是增量空操作。两条等价启动路径# 路径一通过 hogli推荐会额外加载 .env.local / .env.development / .env.services hogli evals [SELECTOR ...] [flags] # 路径二直接调用 harness 模块 python -m products.posthog_ai.eval_harness.harness [SELECTOR ...] [flags]hogli evals:sandboxed是hogli evals的向后兼容别名两者跑的是同一个 harness。两条路径都不需要手动 source 环境变量harness 自己加载仓库根目录.envshell 值优先hogli evals还会通过 hogli 的标准 env 加载把.env.local/.env.development/.env.services叠加进来——包括当.env.local里是op://引用时的 1Password 解析。3.2 Preflight必填环境变量在任何基础设施启动前preflight 会校验所需变量是否就位每个缺失变量给出一行修复建议。哪些变量必填取决于 eval 引擎与所选套件的 kind参见 harness/env_preflight.py变量何时需要用途BRAINTRUST_API_KEY每次运行都必填它是 Braintrust 引擎自身的required_env()engines/braintrust.py不是 core harness 变量SANDBOX_JWT_PRIVATE_KEYsandboxed 套件给沙箱 Agent 的 API token 签名开发密钥在.env.example中harness 从.env自动加载LLM_GATEWAY_ANTHROPIC_API_KEYsandboxed 与 one-shot 套件sandboxed 由 LLM gateway 代理 Agent 的模型调用one-shot 直接使用不经 gatewayLLM_GATEWAY_OPENAI_API_KEY--agent-runtime codexgateway 代理 Codex Agent 的 OpenAI 调用preflight 校验3.3 选择器Selector选择器是子串匹配形如domain/module::fn的套件 id例如experiments、sql、eval_lifecycle_skills。省略选择器则运行全部套件。不匹配的选择器会在任何资源被创建之前立即失败发现阶段先行拼错的 selector 只浪费一次模块导入而不是一次数据库构建。从源码看套件 id 由 harness/discovery.py 生成f{self.domain}/{self.module_name}::{self.fn_name}。domain 就是文件所在目录名内置树products/posthog_ai/evals/domain/顶层文件为root产品自有树products/product/evals/的 domain 是产品名。3.4 常用运行示例# 所有套件Docker 沙箱同时最多 4 个 python -m products.posthog_ai.eval_harness.harness # 只跑两个 domain python -m products.posthog_ai.eval_harness.harness experiments sql # 一个套件中的一个 case python -m products.posthog_ai.eval_harness.harness eval_sql --eval churn # 远程沙箱所有 case 同时跑 python -m products.posthog_ai.eval_harness.harness --provider modal # 只打印套件 id 并退出不创建转录 python -m products.posthog_ai.eval_harness.harness --list3.5 完整 CLI 标志以下表格完整继承自 eval_harness/README.md并补充了 cli.py 中的默认值Flag含义--eval substr只运行名称包含该子串的 case--provider {docker,modal}沙箱运行在哪里。默认docker--max-sandboxes N所有套件同时存活的沙箱数量上限默认docker 为 4modal 无上限--agent-model model沙箱 Agent 使用的模型固定以便跨运行稳定对比默认claude 运行时claude-opus-5codex 运行时gpt-5.5见 cli.py--agent-runtime {claude,codex}服务该模型的 Agent 运行时。默认claude--skill-delivery {bundled,exec}技能投递路径。默认bundledexec会从每个沙箱移除原生 skills--reasoning-effort effortAgent 推理力度合法值取决于 runtimemodel--keep-sandbox-containers跳过运行结束时的 Docker 清理以便检查残留容器仅 Docker--rebuild-sandbox-image运行前强制重建posthog-sandbox-base镜像仅 Docker--create-db重建评测测试数据库而非复用--case-timeout secondsAgent 运行预算最小 1 秒从该 case 的团队数据准备完成后开始计时默认普通模式 900sEVAL_MODEoffline时 3600s--trials N每个 case 跑 N 次Braintrust trials用于观察随机 Agent 的方差默认 1--fail-under fraction所有 experiment 的均分低于该比例0-1时以非零退出--list打印发现的套件 id含 kind并退出几个从 cli.py 可以看到的参数校验逻辑--max-sandboxes至少为 1--keep-sandbox-containers/--rebuild-sandbox-image只能配合--provider docker--trials至少为 1--case-timeout至少为 1--fail-under必须大于 0 且不超过 1模型与运行时不能混搭codex 运行时配claude-*模型或 claude 运行时配gpt-*模型会在解析期直接报错而不是跑到运行中途以 gateway 403 收场。沙箱专用标志--provider、--max-sandboxes、--agent-runtime、--skill-delivery、--reasoning-effort、--keep-sandbox-containers、--rebuild-sandbox-image在没有选中任何 sandboxed 套件时会在 preflight 被拒绝而不是被静默忽略。另外设置EXPORT_EVAL_RESULTS1会把每个 experiment 的结构化 JSON 摘要追加写入eval_results.jsonl完整纯文本运行转录则始终写入见下文输出。四、评测引擎与实验元数据Braintrust 是当前默认也是唯一的 eval 引擎。关键是整个编排通过一个EvalEngine接缝engines/与 Braintrust 解耦_BaseEvalRun.run()把中立的ExperimentSpec交给engines/registry.resolve_engine()解析出的引擎拿回中立的ExperimentResultengines/types.py。未来接入 PostHog 原生引擎只需实现EvalEngine协议并通过一致性测试套件无需改动 run 基类或任何套件。Braintrust 引擎有几个承重不变量engines/braintrust.py破坏任何一个都会产生挂起或静默错误的结果绝不传timeoutEvalAsync的 timeout 会包住整个 task 调用含在 harness 自己的并发信号量上排队的时间会杀掉从未开始跑的 case。真正的每-case 预算在_execute_case内部的asyncio.wait_for且从拿到槽位之后才开始计时绝不让max_concurrency绑死设为总 case 数cases × trials让 harness 的信号量成为唯一限流器使用QUIET_REPORTER所有套件共享一个 stdoutquiet reporter 阻止每个 experiment 把各自的分数表倾倒进交错流其回调被EvalAsync同步调用不能是协程updateTrueexperiment 名称保持运行时/模型无关以便历史跨运行对齐update 保留这段历史而不是分叉它。每个 Braintrust experiment 会在 metadata 里记录agent_runtime、agent_model、skill_delivery见 base.py因此跨运行比较得分时应保持在同一种 runtime 和投递模式内。五、技能投递对比bundled 与 execharness 会从当前 checkout 构建产品 skills默认使用原生 bundled skills。--skill-delivery exec则把这些 skills 打包用于 MCP 分发、启用 exec skill prompt并从每个沙箱移除原生 skills从而保证两条路径无法满足同一个 case。MCP 指令是对整个 harness 进程一次性配置的所以bundled 与 exec 必须分开运行。对比时应使用相同的套件、模型、运行时、provider、case 过滤与 trial 数hogli evals eval_skill_distribution --skill-delivery bundled --agent-runtime claude --agent-model claude-opus-4-8 --trials 3 hogli evals eval_skill_distribution --skill-delivery exec --agent-runtime claude --agent-model claude-opus-4-8 --trials 3两次运行使用相同的六个 prompt 与 Braintrust history keyexperiment metadata 记录了skill_delivery。对比时应看共享的expected_skill_loaded与skill_loaded_before_tool得分而不是聚合均值——因为 exec 分支还会上报搜索与发现诊断指标。另外注意模型 id 应使用裸 id不带anthropic/或openai/前缀因为 LLM gateway 用startswith对裸 id 白名单做校验带前缀的形式会被 403 拒绝Agent 会什么都没干就结束。六、Codex 运行时--agent-runtime codex用 OpenAI 的 Codex harness 运行同一个 agent-server而不是 Claude默认模型为gpt-5.5。它要求环境中存在LLM_GATEWAY_OPENAI_API_KEYpreflight 校验harness 的 LLM gateway 用该 key 代理 Agent 的 OpenAI 调用。experiment 名称不随运行时或技能投递方式变化——每个 Braintrust experiment 在 metadata 中记录agent_runtime、agent_model、skill_delivery所以请在同一个运行时与投递模式内比较跨运行得分。七、沙箱 ProviderDocker 与 Modal7.1 docker默认沙箱以本地容器运行因此必须能访问 Docker daemon。每个容器默认16 GB所以宿主机内存决定并发上限默认上限是 4调高--max-sandboxes需要大内存主机。注意跑 notebook python 或 duckdb cell 的 case 会在其上再占一个容器约 2 GB 的 notebook kernel该容器不计入沙箱上限见下文。每次 docker 运行在任何 case 开始前都会校验posthog-sandbox-base镜像的新鲜度当posthog/agent发布了比镜像里烘焙的版本更新的版本、或 Dockerfile 自镜像构建后有改动时会重建镜像未变化的镜像不到一秒即可通过检查即使重建也大多命中 layer 缓存——只有 npm install 之后的层在 agent 版本变化时需要重跑npm 不可达时检查会告警并复用现有镜像而不是让运行失败--rebuild-sandbox-image无论何种情况都强制重建。Modal 的 DEBUG 镜像由 Modal 在 Dockerfile 或构建上下文变化时重建但仅posthog/agent发布新版本不会使其失效——这是一个已知限制。7.2 modal远程Modal 网络无法访问localhost所以 harness 自己通过Tailscale Funnel暴露宿主服务并把沙箱指向公网 URL。评测沙箱运行在专门的posthog-sandbox-evalsModal app 中与生产环境及本地开发沙箱隔离——因此 docs/internal/sandboxes-setup-guide.md 里的手动隧道设置对评测并不需要。首次 modal 运行会支付一次性的远程镜像构建后续运行复用缓存镜像直到 skills 或构建上下文变化。Modal 前提条件全部在 preflight 中检查早于任何启动tailscale在PATH上tailscaled 已运行且本节点已登录tailscale uptailnet 与节点需在 admin console 的 ACL 中启用 Funnel 并开启 HTTPS 证书。harness 会把 Django、LLM gateway、MCP 服务在 Funnel 的三个公网端口443、8443、10000上Modal 凭据MODAL_TOKEN_ID与MODAL_TOKEN_SECRET或modal token new生成的~/.modal.toml环境中存在SANDBOX_JWT_PRIVATE_KEY开发密钥在.env.exampleharness 从.env自动加载。modal 上沙箱默认无上限每个 case 同时各占一个--max-sandboxes N是成本旋钮。每个 case 等待其 workflow 终止沙箱后还会执行一次按 tag 作用域的 Modal 清扫作为安全网整次运行结束时 harness 会再次清扫自己的残留沙箱因此崩溃或中断的运行不会让沙箱一直计费到 TTL。清扫按本次运行的task_id沙箱 tag 匹配所以共享同一 Modal app 的第二次评测运行不会误删前一次的沙箱。7.3 两种 Provider 的差异对照dockermodalSANDBOX_PROVIDERdockerMODAL_EVALS服务 URLhost.docker.internal:portTailscale Funnel URLstart()base 镜像新鲜度检查Tailscale Funnel本地 skillsbind-mount 挂载烤进镜像默认沙箱上限4无上限沙箱 TTL默认case timeout 加余量每-case 安全网容器清扫task 标记的沙箱清扫运行结束清理容器清扫task 标记的沙箱清扫harness/README.md。MODAL_EVALS是posthog-sandbox-evalsapp 下的同一个ModalSandbox类所以评测镜像构建与生产/本地开发沙箱不共享镜像缓存。modal 的 TTL 覆盖是因为TEST1下沙箱 TTL 等于默认每-case timeout会让 Modal 恰好在慢 case 收尾时回收其沙箱。选型建议多 case 的沙箱化评测在前提条件齐备时优先 Modal——远程沙箱可并行且不消耗本地 Docker 内存通常更快完成Docker 适合小规模冒烟测试或无法访问远程环境时。另外注意 providers.py 中的关键细节SANDBOX_PROVIDER必须在django.setup()之前通过环境变量设置正确因为products.tasks只解析一次 Sandbox 类并缓存在模块全局.env里默认SANDBOX_PROVIDERdocker如果不提前设置一次 modal 运行会静默缓存DockerSandbox而实际在本地执行。八、Notebook Kernel 沙箱notebook 的 SQL cell 若 refs 全为 HogQL则走直接通道异步查询管理器无沙箱只需要 live server而 python 或 duckdb cell 会分发 notebook Temporal workflow为 case 额外开一个第二个沙箱notebook kernel约 2 GB与 Agent 的 16 GB 并存。关键限制该通道只支持 docker。notebook kernel 直接从SANDBOX_PROVIDER解析其后端而 modal provider 把它设为MODAL_EVALS不属于KernelRuntime.Backend的取值——端口映射与 liveness 检查都会落空kernel 永远起不来。因此含 python/duckdb cell 的套件请用--provider docker纯 SQL 套件不受影响可运行于任何 provider。让该通道工作的三个自动化机制eval Temporal worker 把 notebook workflow 与 tasks workflow 一起注册且GENERAL_PURPOSE_TASK_QUEUEnotebook 运行的派发队列指向同一个每进程队列一个 worker 同时服务两者SANDBOX_API_URL已经把沙箱指向 Django live server——kernel 的 result 回调与数据面读取都在这里docker provider 在启动期间构建 notebook 镜像harness/kernel_sandboxes.py。若留到首次使用时构建会在限时五分钟的 Temporal activity 里发生冷构建必然超时。kernel 沙箱由 harness 回收而非 TTL——docker 后端忽略SandboxConfig.ttl_seconds。每个 case 在释放自己的沙箱槽位前销毁自己的 kernel运行在评测数据库两端都会清扫残留。--keep-sandbox-containers对 kernel 容器与 Agent 容器一视同仁地保留。九、并发模型所有选中的套件在单个事件循环上并发运行一个全局信号量限制所有套件的存活沙箱数——因此选择更多套件会提高吞吐而不提高峰值负载harness/README.md。第二个信号量覆盖完整的每-case 团队数据准备含 demo-data 克隆与可选 setup hook普通本地机器同时只允许 1 个 setupCODER或CI环境变量存在时放宽到 4cli.py。因为这些阶段会产生大型 ClickHouse 拷贝或直接插入独立限制可以在 Modal 沙箱容量无上限时仍保护 ClickHouse 免于内存耗尽同时让托管环境更快准备 case。对象存储启用时setup 还会校验主 warehouse 并为每个 case 克隆 team 级 warehouse 元数据同时复用 master 的不可变 CSV 文件。一个 setup 完成后其槽位立即让给下一个 case而准备好的 case 继续跑自己的 Agent。几个要点case只在确实需要时才持有沙箱槽位团队数据准备与 Agent 运行。日志解析、Braintrust span 构建、trace 上报与评分都发生在槽位释放之后见 base.py 的_run_sandbox_window每-case timeout 是槽位内部的asyncio.wait_for从团队数据准备完成后开始——等待信号量或队列都不会消耗 Agent 预算one-shot 套件不碰沙箱信号量由独立的全局one_shot_slots信号量约束并发 case 数默认 8见 cli.py采用同样的acquire-once、预算在内规则存在两个事件循环主循环拥有 Temporal dev server、套件与 reporterTemporal worker 在守护线程上拥有自己的循环两者只通过loop.call_soon_threadsafe通信。十、新增一个评测套件完整的创作流程cases、seeders、synthesizers、scorer 模式与验证见/writing-evals技能文档.agents/skills/writing-evals/SKILL.md。下面是简版。10.1 发现机制约定优于注册没有注册表。套件按约定从两个根集发现harness/discovery.pyproducts/posthog_ai/evals/domain/—— 内置树保留给 Max 与其它 Agent 套件products/product/evals/—— 产品自有评测套件注意是复数evals/单数products/signals/eval/是一个无关的 pytest 树harness 会忽略它。套件 id 为product/module::fn导入路径products.product.evals.module。新产品自有评测放在products/product/evals/下即可该目录无需任何 pytest-collection 排除——pytest 默认的python_files只匹配test_*.py永远不会收集那里的eval_*.py。新增步骤创建products/posthog_ai/evals/domain/eval_name.py或products/product/evals/eval_name.py目录即套件的 domain写一个或多个名为eval_*的协程接收单个ctx: EvalContext构建SandboxedEvalCase列表连同 scorers 与ctxctx交给SandboxedPrivateEval或SandboxedPublicEval。10.2 沙箱化套件模板from products.posthog_ai.eval_harness.base import SandboxedPrivateEval from products.posthog_ai.eval_harness.config import SandboxedEvalCase from products.posthog_ai.eval_harness.harness.context import EvalContext async def eval_my_thing(ctx: EvalContext) - None: await SandboxedPrivateEval( experiment_namesandboxed-my-thing-cli, cases[SandboxedEvalCase(namemy_case, prompt...)], scorers[], ctxctx, )harness 会自动给每个 sandboxed experiment 添加ExitCodeZeroscorerbase.py不要把它加进套件的scorers列表harness 会拒绝重复。SandboxedEvalCase的字段config.py还包括expected: dict—— 供评分器读取的期望值按 scorer 的_name()键控如{tests_pass: {should_pass: True}}缺失键时 scorer 回退到默认行为followups: list[str]—— 首轮prompt之后的后续用户消息逐轮发送保持同一 Agent 会话多轮 case 用于评估路由或行为在对话中的变化仅 sandboxed case 支持one-shot runner 每 case 只执行一次模型调用、从不读该字段disable_bundled_skills: bool—— 对该 case 移除沙箱镜像中烘焙的 skills当被测行为是另一条技能投递路径、需防止原生技能发现意外满足任务时使用exec 模式对所有 case 施加同样行为interaction_origin: str | None—— 以某个产品表面如slack运行该 caseAgent server 会据此分支系统 prompt让 case 真正练习真实 promptsetup: Callable[[CustomPromptSandboxContext], dict] | None—— 团队数据准备完成、Agent prompt 派发前的可选钩子返回值并入 task output 的seed键供 scorer 读取实体 id。10.3 one-shot 套件模板from products.posthog_ai.eval_harness.config import BaseEvalCase from products.posthog_ai.eval_harness.harness.context import EvalContext from products.posthog_ai.eval_harness.harness.requirements import SuiteKind from products.posthog_ai.eval_harness.one_shot import OneShotPrivateEval SUITE_KIND SuiteKind.ONE_SHOT async def eval_my_generation(ctx: EvalContext) - None: async def task(case: BaseEvalCase, task_ctx: EvalContext) - dict: return {answer: ...} # 一次模型调用必须可 JSON 序列化 await OneShotPrivateEval( experiment_namemy-generation, cases[BaseEvalCase(namemy_case, prompt...)], scorers[...], tasktask, ctxctx, )one-shot 的 task 在全局 one-shot limiter 下每 case 运行一次直接返回 scoreroutput字典one_shot.py。task 返回的 dict 会经过 Braintrust 的 JSON 往返必须可序列化。10.4 设计约定把相关的 case 捆绑进一个套件函数而不是拆到很多个一个套件就是一个 Braintrust experiment这正是跨 case 比较与--eval过滤有意义的前提experiment_name是 Braintrust experiment 的 key修改它会开启一段全新历史。现有套件以-cli结尾因为 MCP server 提供的是cli面保留该后缀是为了让历史与早期运行对齐确认发现机制识别了你的套件python -m products.posthog_ai.eval_harness.harness --list | grep my_thing10.5 真实示例sandboxed-sql-cliproducts/posthog_ai/evals/sql/eval_sql.py 是一个很好的参照它的意图与 CI 版eval_sql一致但让同一个问题端到端穿过沙箱 Agent PostHog MCP 工具并用execute-sqlMCP 工具判定 Agent 运行的 HogQL。由于沙箱 Agent 没有AgentMode.SQL这样的强制模式、可以自由选择 typed query 工具query-trends/query-funnel/query-retention套件用Write a HogQL query that… 的 prompt 措辞作为强制函数。case 的expected里是完整期望 SQLscorers 组合了NoToolCall禁止保存 insight、AnswerQueryRan、AnswerToolCallNot禁止 typed query 工具、偏好execute-sql、SkillLoaded、SQLSchemaAlignment、SQLResultMessageAlignment。十一、输出与日志11.1 终端输出模型进度行使用稳定标签标记 case 与套件的起止SUITE START、EXPERIMENT START、CASE DONE、EXPERIMENT DONE、SUITE DONE。只有整次运行才使用PASS/FAIL——因此行为分低的套件仍显示为完成而非通过。最终摘要给出带标签的套件与 case 总数、分数门限、总时长以及每个 experiment 一个块scorer 均值、PostHog 与 Braintrust URL、Agent 日志目录。崩溃的套件标记为CRASH、在摘要中包含 traceback并让运行非零退出——但不会拖垮其它套件。11.2 运行转录与本地日志每次真实评测调用都会把完整 stdout/stderr 镜像到products/posthog_ai/eval_harness/logs/harness/timestamp_id.log路径前一行标注这是完整 stdout/stderr 运行转录终端最后一行是该路径的无标签绝对路径其后无任何输出便于人或 Agent 可靠打开转录本身以同一路径结尾logs/harness/latest.log指向最新转录。套件列出--list与参数错误不会创建转录。每个 case 的原始 Agent 日志落在本地磁盘case.jsonl、case.artifacts.json、case.summary.txt这通常是查看 Agent 实际做了什么的最快方式。设置EXPORT_EVAL_RESULTS1时还会在每个 experiment 的日志目录里写case_results.jsonl每 case × trial 一行含run_id、experiment、trial_index、scores。11.3 结果上报范围SandboxedPublicEval设置no_send_logsFalse同时向Braintrust 与 PostHog上报SandboxedPrivateEval设置no_send_logsTrue两个服务都不上传但本地日志照常写入PostHog 结果上传遵循no_send_logs与OPT_OUT_CAPTURE相互独立——后者仍适用于普通 SDK 与 trace 客户端。上报设置与范围详见 docs/internal/ai-offline-evaluation-reporting.md。沙箱化的 trace 以sandboxed-agent命名空间上报 PostHogone-shot 为one-shot见 base.py每个 case 在评分完成后还会补发$ai_traceroot 事件把分数挂到 trace 上。十二、配套文档与进一步阅读products/posthog_ai/evals/AGENTS.md ——Hedgebox 数据集参考。写 eval case 之前必读你的expected值与 scorers 必须与该 taxonomy 完全一致例如事件是signed_up不是sign_up。数据由固定 seed 播种EVAL_SEED逐字节可复现且事件横跨过去 120 天与未来 30 天paid_bill提前排期所以期望查询里优先使用相对时间范围products/posthog_ai/eval_harness/harness/AGENTS.md —— 修改 harness 本身时要保持的不变量清单products/posthog_ai/eval_harness/harness/README.md —— harness 内部工作原理模块职责、启动序列、并发模型、Provider 差异products/posthog_ai/eval_harness/scorers/contract.py —— PostHog 自有 scorer 合约当前是 Braintrust 的 shim后续会翻转为纯 PostHog 类.agents/skills/writing-evals/SKILL.md —— 完整套件创作流程docs/internal/ai-offline-evaluation-reporting.md —— 评测结果上报Braintrust / PostHog的设置与范围。最后提醒一点运行时前提评测依赖 personhogRust首次cargo build需要数分钟务必在 flox shell 内运行否则 preflight 构建会失败。选择器建议先用--list确认套件 id 再跑未匹配的选择器会在任何资源创建前立即报错退出。【免费下载链接】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),仅供参考