<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>网页搜索 on Codexer</title><link>https://codexer.com/tags/%E7%BD%91%E9%A1%B5%E6%90%9C%E7%B4%A2/</link><description>Recent content in 网页搜索 on Codexer</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 18 Aug 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://codexer.com/tags/%E7%BD%91%E9%A1%B5%E6%90%9C%E7%B4%A2/index.xml" rel="self" type="application/rss+xml"/><item><title>逆向 Codex 的网页搜索：原来搜索和推理早就分家了</title><link>https://codexer.com/posts/2026-08-18-codex-standalone-search-reverse-engineering/</link><pubDate>Tue, 18 Aug 2026 09:00:00 +0800</pubDate><guid>https://codexer.com/posts/2026-08-18-codex-standalone-search-reverse-engineering/</guid><description>&lt;p&gt;想象一个挺常见的场景：你在用 Gemini、Claude，或者本地跑的一个小模型写代码，想让 agent 顺手搜一下「Rust 最新版本改了什么」。结果发现，要么这模型根本没有联网搜索能力，要么搜出来的结果又慢又贵，还得搭进去一堆推理 token。这时候有人干了一件很「硬核」的事：他没有自己造一个搜索引擎，而是把 OpenAI Codex 自带的搜索接口逆向了出来，做成一个插件，让任何模型都能直接调它，而且不花一分推理 token。&lt;/p&gt;
&lt;p&gt;这个项目叫 pi-gpt-search，作者是 mateusdcc。它的目标很直接，给 Pi 这个编码 agent 用的各种模型（Gemini、Claude、本地模型）都接上实时网页搜索。但真正有意思的，不是这个插件本身，而是它逆向过程中挖出来的那个事实：Codex 的网页搜索，根本不是一个「模型调用搜索工具」那么简单的设计，后端其实藏着一个完全独立的搜索服务。&lt;/p&gt;
&lt;h2 id="意外的发现搜索是带外返回的"&gt;意外的发现：搜索是「带外」返回的&lt;/h2&gt;
&lt;p&gt;作者的第一步，是跑了一条命令 &lt;code&gt;codex app-server generate-ts&lt;/code&gt;。这条命令会把 Codex 本地的 app-server 协议，直接生成成 TypeScript 类型定义。然后他在这些类型里搜 &amp;ldquo;WebSearch&amp;rdquo;，找到了两个结构：WebSearchItem 和 WebSearchAction。&lt;/p&gt;
&lt;p&gt;其中 WebSearchItem 的文档注释写得很直白，大意是「由独立网页搜索带外返回的结构化搜索结果」。关键词是「带外」（out-of-band）：搜索结果不是从模型的推理流里长出来的，而是从旁边一条独立的通道送进来的。&lt;/p&gt;
&lt;p&gt;这句话信息量很大。它说明 Codex 的搜索是一个独立组件，有自己的协议、自己的端点、自己的返回格式，跟模型推理是两套系统。模型在推理过程中，只是「订阅」了这个独立服务的返回结果。&lt;/p&gt;
&lt;h2 id="逆向四步从类型定义到能用的接口"&gt;逆向四步：从类型定义到能用的接口&lt;/h2&gt;
&lt;p&gt;完整的手法分四步，每一步其实都很值得学。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步，生成类型。&lt;/strong&gt; &lt;code&gt;codex app-server generate-ts&lt;/code&gt; 把内部协议导成 TypeScript。这招的巧妙在于，很多编码 agent 为了方便二次开发，本身就暴露了协议 schema，等于主动递了一份地图给你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步，挖字符串。&lt;/strong&gt; 用 &lt;code&gt;strings&lt;/code&gt; 扫一遍 Codex 的 Rust 编译产物，grep 关键词 &amp;ldquo;search&amp;rdquo;、&amp;ldquo;alpha/search&amp;rdquo;、&amp;ldquo;backend-api&amp;rdquo;。编译后的二进制不会说谎，常量字符串都嵌在里面。于是他捞到了几个关键标识：standalone_web_search、codex.web_search.results、alpha/search，还有基础域名 chatgpt.com/backend-api/。把这些拼起来，就得到了端点的候选地址：&lt;code&gt;https://chatgpt.com/backend-api/codex/alpha/search&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步，用错误信息「问路」。&lt;/strong&gt; 这是最精彩的一步。他没有瞎猜参数，而是不断发「错误」的请求，让后端自己的校验报错来告诉他正确答案。先发 &lt;code&gt;{q: &amp;quot;Rust&amp;quot;}&lt;/code&gt;，后端回 400，提示「缺参数 id」。加上 id，又回 400，提示「缺参数 model」。补上 model，这次回 200，但提示「不能给 web.run 发空的 calls」。再往下试，把 command 改成 commands，后端甚至贴心地提示「你是不是想说 commands」。最后把 search_query 从字符串改成数组，请求就通了。后端的报错，成了一份活的参数文档。&lt;/p&gt;</description></item></channel></rss>