「让 Codex 集成 Langfuse 做 Inspect AI 评测」——这句话我第一次也是照着写的,写完才发现它预设了一个错误的坐标系:把 Langfuse 和 Inspect AI 当成一对需要搭桥的对等系统。翻开本站评测路由系列的施工图就会发现,这两个组件在同一条闭环里差着三个节点。先摆正坐标,Codex 的接入方式才立得住。
名词速查
| 术语 | 一句话解释 |
|---|---|
| 评测链路(闭环) | 本站施工图定义的数据流:请求 → 路由 → Trace → 评测 → 策略更新 → 回到路由 |
| M5 Trace/观测 | 闭环里记录「每次请求发生了什么」的节点,选型是 Langfuse |
| M8 评测执行引擎 | 闭环里「给定模型/题目跑出分数」的节点,选型是 Inspect AI |
| M6 评测数据集 | M5 和 M8 之间的中转站:把线上 trace 挖成带答案的评测样本 |
| Langfuse | 开源 LLM 可观测平台,收 trace、按 session 分组、存抽样打分 |
| Inspect AI | 英国 AISI 的评测框架,Task/Solver/Scorer 三段式,带 Docker 沙箱 |
| rollout | Codex 每次会话写下的完整事件流文件(JSONL),含每轮模型输出和工具调用 |
| LiteLLM callback | 网关侧挂一个回调,把流经网关的每次调用异步上报给 Langfuse |
先摆正坐标系:这条链路长什么样
本站评测路由系列反复强调的一条主线是:这套系统的本体不是路由器,而是一条闭环——请求 → 路由 → Trace → 评测 → 策略更新 → 回到路由。施工图把它拆成十个模块,和本文相关的是这几个(模块职责引自施工图,核实过的来源):
- M1 统一网关(选型 LiteLLM):所有模型调用的唯一入口,也是唯一埋点。
- M5 Trace 与观测(选型 Langfuse):闭环的「原材料仓库」,记录每次请求的完整链路。网关侧配一个 callback,全公司流量自动落进 Langfuse。
- M6 评测数据集:从 M5 的 trace 里挖样本(fallback 请求、点踩请求、高成本请求优先入池),配上 ground truth 或 rubric,成为评测集。
- M8 评测执行引擎(选型 Inspect AI):给定「模型版本 × prompt 版本 × 数据集版本 × 评分器版本」四元组,可重复地跑出分数。
关键在数据流向:Langfuse(M5)→ 挖掘 → 数据集(M6)→ Inspect AI(M8)。Langfuse 在前,喂给 Inspect AI;不是反过来。
错位在哪:Langfuse 不是 Inspect AI 的对等物
我上一版把它们写成「Inspect 产生分数、Langfuse 存证据」,暗示两者是并列的、缺一座桥。这个定位是错的,错在三处:
- 节点不同。Langfuse 是 Trace 节点(M5,在线、异步落库),Inspect AI 是评测执行引擎(M8,离线、可重复跑)。它们在闭环里隔着 M6 数据集,不是并排站的两个盒子。
- 方向单一。真实数据流是 Langfuse → 数据集 → Inspect AI,单向前进。所谓「Inspect→Langfuse 搭一座桥把分数送回去」是个伪命题——Inspect 的离线分数进的是实验分析(M9)和能力标签(M2),不是回灌 Langfuse。
- Langfuse 的打分是另一回事。Langfuse 自己确实有 scores 功能,但它承接的是线上 LLM-as-judge 抽样打分(在线、采样、快),和 Inspect AI 的离线全量可重复评测(钉死四个版本号、跑沙箱)根本不是同一档评测。把这两种分数混为一谈,是错位的根源。
一句话纠正:Langfuse 管「线上发生了什么 + 线上抽检」,Inspect AI 管「离线严格评测」,两者靠数据集(M6)串联,不靠直接搭桥。
Codex 从哪个节点进场
摆正坐标后,Codex 的位置就清楚了:它是施工图里「业务方 / Agent 应用」那个框,从 M1 统一网关进场。它和评测链路的关系有两条线——一条是「trace 生产者」,一条是「被评对象」。
作为 trace 生产者:两条互补的路
路一:走网关,全流量自动落库。 Codex 支持自定义 model provider,在 ~/.codex/config.toml 里配一个 [model_providers.xxx] 块,把 base_url 指向公司网关即可:
# 伪配置,对应 Codex config.toml 的 model_providers 段
model = "gateway-default"
model_provider = "company_gw"
[model_providers.company_gw]
name = "Company Gateway"
base_url = "https://llm-gw.internal/v1" # 指向 M1 统一网关(LiteLLM)
env_key = "GW_API_KEY"
wire_api = "responses" # 见下方诚实的提醒
requires_openai_auth = false # 网关用非 OpenAI key 前缀时需要
一旦 Codex 的调用流经网关,M1 侧的 LiteLLM→Langfuse callback 就把每次调用自动写进 M5,Codex 一行埋点代码都不用写。代价是:网关只看得见扁平的单次模型调用(哪个模型、多少 token、多少钱),看不见 Codex 的 agent 结构——一轮对话里它怎么想、调了 apply_patch 还是 shell、有没有 spawn 子 agent。
路二:走插件,补上 agent 结构。 langfuse/codex-observability-plugin 挂 Codex 的 Stop hook:每轮对话结束后,插件读这次会话的 rollout JSONL,把模型步骤、工具调用、token 用量、子 agent 重组成一轮轮完整对话,用 Langfuse TypeScript SDK 转成 trace 落进同一个 Langfuse——一轮是一个 trace,工具调用是 span,子 agent 挂在父 trace 下。要求 Node ≥ 22、Codex ≥ 0.128,默认关闭,靠 TRACE_TO_LANGFUSE=true 开启,出错 fail-open 不卡会话。
两条路互补,不互斥:网关 callback 回答「花了多少钱、调了哪个模型」,插件回答「agent 这一回合的推理和工具轨迹」。 都落进 M5 这一个 Langfuse,只是粒度不同。
作为被评对象:Codex 是 M8 的一道题
Codex 本身就是编码 agent,而 M6 装载的公开基准里,SWE-bench(真实 GitHub issue)和 Terminal-Bench(docker 化终端任务)正是评编码/终端 agent 的——施工图指出 Inspect Evals 已经把这些打包成可直接跑的实现,而 Inspect AI(M8)自带 Docker 沙箱正是为跑这类要执行代码的评测准备的。
于是闭环闭合:Codex 的线上 trace(M5)被挖成评测样本(M6)→ Inspect AI(M8)用沙箱评测把 Codex 当作被测 agent 跑出分数 → 实验分析(M9)和异常归因(M10)消化 → 更新能力标签(M2)和路由策略(M4)→ 路由层据此决定「这类请求要不要交给 Codex」。Codex 同时是喂数据的嘴和被打分的对象。
说明:把 Codex 作为完整 agent 塞进 Inspect AI 沙箱评测(而不只是评它背后的单个模型),需要用 Inspect 的 agent bridge 之类机制包一层——这条我没有亲手跑通,属于基于架构的建议,不是已验证方案。
一张图把 Codex 放回闭环
flowchart LR
CODEX["Codex CLI(Agent 应用)"]
subgraph ONLINE["在线"]
M1["M1 统一网关 LiteLLM"]
M5["M5 Trace / 观测 Langfuse"]
end
subgraph OFFLINE["离线评测"]
M6["M6 评测数据集"]
M8["M8 执行引擎 Inspect AI"]
end
FB["M2 能力标签 / M4 路由策略"]
CODEX -->|base_url 指向网关| M1
CODEX -.->|Stop hook 读 rollout| M5
M1 -->|callback 全流量落库| M5
M5 -->|挖掘样本| M6
M6 --> M8
M8 -->|离线分数| FB
FB -.->|路由决定要不要用 Codex| CODEX
实线是主数据流,虚线是 Codex 插件旁路上报和最终的路由反馈。看这张图能一眼确认:Langfuse(M5)和 Inspect AI(M8)之间没有直接连线,它们靠 M6 数据集串联。
什么时候用哪条路(灰度)
trace 那两条路不是二选一,是按你要回答的问题分层上:
- 只关心成本与模型选择 → 网关 callback 就够(路一)。你要的是「Codex 这类请求平均花多少、命中哪个模型、fallback 多不多」,扁平调用日志足以支撑 M9 的成本口径。
- 要做 agent 级归因 → 加插件(路二)。当异常归因(M10)需要区分「是模型能力问题还是 agent 编排问题」时,你必须看到 turn/tool/subagent 层级——这是网关视角天然缺失的,只能靠读 rollout 补。
- 要把 Codex 纳入回归门禁 → 走 M6→M8。让 Inspect AI 定期用固定金集评 Codex,分数进能力标签,路由才敢/才不敢把活派给它。
诚实的提醒
本文的架构定位(M1/M5/M6/M8 的职责与选型、数据流向)引自本站评测路由系列施工图,是核实过的来源;Codex→Langfuse 插件机制核对过其官方仓库 README。但整条链路我没有端到端亲手搭建复现——尤其:(1) Codex wire_api 该用 responses 还是 chat,各来源说法冲突(Codex 迭代快,第三方网关多只讲 Chat Completions),照配前务必按你的版本和网关实测,网关只讲 Chat 时用 LiteLLM 做协议翻译;(2) 把 Codex 作为完整 agent 送进 Inspect 沙箱评测是我的建议,未验证。
最低成本的验证实验,分两步各自独立可跑:
- 验 trace 落库:本机配好 Codex 的
model_providers指向一个开了 Langfuse callback 的 LiteLLM 实例,跑一次codex exec简单任务,去 Langfuse 确认看到调用记录——这一步验证「路一」通不通。 - 验评测引擎:照本站 Inspect AI 实操陪读篇用免 key 的 mockllm 跑通一次最小评测,确认能拿到分数和 transcript——这一步验证 M8 通不通。
两步都通了,再谈把它们用 M6 数据集接起来。不要一上来就想搭全链路。
参考来源
本站评测路由系列(架构与选型的一手依据):
工程实践 / 官方文档: