逆向 Codex 的网页搜索:原来搜索和推理早就分家了

想象一个挺常见的场景:你在用 Gemini、Claude,或者本地跑的一个小模型写代码,想让 agent 顺手搜一下「Rust 最新版本改了什么」。结果发现,要么这模型根本没有联网搜索能力,要么搜出来的结果又慢又贵,还得搭进去一堆推理 token。这时候有人干了一件很「硬核」的事:他没有自己造一个搜索引擎,而是把 OpenAI Codex 自带的搜索接口逆向了出来,做成一个插件,让任何模型都能直接调它,而且不花一分推理 token。
这个项目叫 pi-gpt-search,作者是 mateusdcc。它的目标很直接,给 Pi 这个编码 agent 用的各种模型(Gemini、Claude、本地模型)都接上实时网页搜索。但真正有意思的,不是这个插件本身,而是它逆向过程中挖出来的那个事实:Codex 的网页搜索,根本不是一个「模型调用搜索工具」那么简单的设计,后端其实藏着一个完全独立的搜索服务。
意外的发现:搜索是「带外」返回的#
作者的第一步,是跑了一条命令 codex app-server generate-ts。这条命令会把 Codex 本地的 app-server 协议,直接生成成 TypeScript 类型定义。然后他在这些类型里搜 “WebSearch”,找到了两个结构:WebSearchItem 和 WebSearchAction。
其中 WebSearchItem 的文档注释写得很直白,大意是「由独立网页搜索带外返回的结构化搜索结果」。关键词是「带外」(out-of-band):搜索结果不是从模型的推理流里长出来的,而是从旁边一条独立的通道送进来的。
这句话信息量很大。它说明 Codex 的搜索是一个独立组件,有自己的协议、自己的端点、自己的返回格式,跟模型推理是两套系统。模型在推理过程中,只是「订阅」了这个独立服务的返回结果。
逆向四步:从类型定义到能用的接口#
完整的手法分四步,每一步其实都很值得学。
第一步,生成类型。 codex app-server generate-ts 把内部协议导成 TypeScript。这招的巧妙在于,很多编码 agent 为了方便二次开发,本身就暴露了协议 schema,等于主动递了一份地图给你。
第二步,挖字符串。 用 strings 扫一遍 Codex 的 Rust 编译产物,grep 关键词 “search”、“alpha/search”、“backend-api”。编译后的二进制不会说谎,常量字符串都嵌在里面。于是他捞到了几个关键标识:standalone_web_search、codex.web_search.results、alpha/search,还有基础域名 chatgpt.com/backend-api/。把这些拼起来,就得到了端点的候选地址:https://chatgpt.com/backend-api/codex/alpha/search。
第三步,用错误信息「问路」。 这是最精彩的一步。他没有瞎猜参数,而是不断发「错误」的请求,让后端自己的校验报错来告诉他正确答案。先发 {q: "Rust"},后端回 400,提示「缺参数 id」。加上 id,又回 400,提示「缺参数 model」。补上 model,这次回 200,但提示「不能给 web.run 发空的 calls」。再往下试,把 command 改成 commands,后端甚至贴心地提示「你是不是想说 commands」。最后把 search_query 从字符串改成数组,请求就通了。后端的报错,成了一份活的参数文档。
第四步,验证零推理。 最终合法的请求长这样:
{
"id": "1",
"model": "gpt-4o",
"commands": { "search_query": [{ "q": "OpenAI Codex GitHub repository" }] }
}
返回 200,带一个结构化的 results 数组,里面是标题、URL 和摘要。作者还专门写了一个拦截测试来证明:这次调用里,模型推理的次数是 0。
零 token 意味着什么:搜索和推理分家了#
这是整件事里我最想强调的一点。用这个接口搜一次网页,消耗的是 0 输入 token、0 输出 token、0 推理额度。为什么能做到零 token?因为搜索压根不走 LLM 的推理管线,它直接命中一个独立的检索服务,返回结构化结果。
这个设计透露出 Codex 一个很重要的架构观:它没有把「搜索」做成模型的工具调用,而是把搜索做成了基础设施,跟推理彻底解耦。模型负责思考,搜索服务负责检索,两者通过协议对接。这跟云计算里「计算和存储分离」是同一个道理。解耦的收益很直接:搜索不再占用宝贵的上下文窗口,不消耗推理额度,延迟也更可控。代价则是协议更复杂,这就是为什么会有「带外返回」这种设计。
模型主权:搜索成了可以「借用」的公共设施#
正因为搜索被拆成了独立服务,它天然就是「模型无关」的。作者做的插件,本质上就是在 Pi 的 Gemini、Claude、本地模型,和 OpenAI 的搜索端点之间架了一座桥:模型发一条 codex-search 查询,插件把它包装成协议请求,用 ~/.codex/auth.json 里的登录态打到那个 alpha/search 端点,拿到结构化结果再交回给模型继续推理。
于是出现了一个有点讽刺的局面:你在用 Gemini 或者 Claude 写代码,用的却是 OpenAI 的搜索能力,还不用为它付推理费。模型生态之间的「围墙」,被一个薄薄的协议层给拆穿了。这其实说明,搜索这类能力正在变成一种可以跨模型复用的「公共设施」,谁手里有现成的登录态,谁就能搭个桥把它接过来。
我的解读:亮点和隐忧并存#
抛开技术细节,这件事有三层值得看。
第一,它印证了「harness 的协议就是最好的文档」。Codex、Claude Code 这些工具为了可扩展性,主动暴露了 app-server 协议和类型生成命令。这对生态是好事,但也意味着协议一旦暴露,就有人能绕过官方 UI 直接调底层服务。逆向本身不难,难的是愿意一层层去挖。
第二,脆弱性也很明显。这个 alpha/search 端点并没有公开文档,属于内部接口。作者自己也承认,它要求一个有效的 codex login 登录态,一旦过期就得重新登录。更现实的风险是,OpenAI 哪天改一下参数校验,或者干脆封掉未授权的访问,这套插件就会失效。用「逆向出来的内部接口」做生产依赖,等于把地基建在别人的施工图上。
第三,它揭示了 agent 工具的真实成本结构。很多人以为让 agent 搜个网页很贵,其实贵的是推理,不是检索。检索被拆成独立服务之后,成本几乎为零。这给了我们一个启发:判断一个 agent 工具的「贵」到底贵在哪,先看清楚它是消耗推理,还是消耗独立服务。两者的成本差了数量级。
结语#
pi-gpt-search 这个项目本身不大,但它像一把手术刀,剖开了一个大家平时看不到的切面:OpenAI 的 Codex 不是一块铁板,而是由 app-server、搜索服务、推理管线等一组解耦的组件拼起来的。搜索早就从推理里分家出去,成了可以独立调用的基础设施。
对普通用户来说,这提醒我们:你在 agent 里点的每一个「搜索」按钮,背后可能都不是模型在搜,而是一个独立的服务在干活。对做 agent 基础设施的人来说,这个案例给了一条很实在的路径:想理解一个 agent 的内部构造,先跑 generate-ts,再用 strings,最后让错误信息带你走。
参考来源:
- pi-gpt-search(GitHub):Native, Model-Independent Web Search for Pi using OpenAI Codex Standalone Search Engine
- HOW-IT-WAS-EXTRACT.md:How the Standalone Search Engine Was Extracted
- Hacker News:I reverse-engineered Codex’s web search for use with Claude, and any local model