文章

把搜索当代码来写:AI Agent 的搜索范式重构

Perplexity 提出 Search as Code 范式,让 AI 模型用 Python 代码编排搜索工作流原语,而不是直接消费黑盒搜索结果,在复杂检索任务中实现候选更多、上下文更少。

把搜索当代码来写:AI Agent 的搜索范式重构
  1. 固化管道的三个裂缝
  2. Search as Code:倒转主客关系
  3. 三层架构
    1. 为什么选 Python
    2. Agent Skills:不只是 API 文档
    3. 状态管理:文件系统还是 REPL
  4. CVE 漏洞库构建:一个贯穿三轮的例子
  5. 五项基准的对比结果
  6. 混合计算的更大图景
  7. 核心
  8. 评价
  9. FAQ:SaC 到底新在哪里
    1. SaC 和“用 Google API 多轮搜索”有什么区别?
    2. 如果过滤和抽取仍然需要 LLM,SaC 凭什么省上下文?
    3. “候选更多,但上下文更少”是什么意思?
    4. SaC 是 Search Pipeline as Code 吗?
    5. 那更理想的接口形态是什么?
    6. SaC 最大的风险是什么?
    7. 最终怎么理解 Perplexity 的 SaC?

原文:Rethinking Search as Code Generation

先把结论放前面:SaC 更准确地说不是 Search Pipeline as Code,而是 Search Workflow as Code。它不是真的让模型自由改造搜索引擎内部的召回、粗排、精排,而是用 skill 教模型写搜索工作流程序。

它最核心的收益可以压缩成一句话:

候选更多,但上下文更少。

也就是:系统可以大胆扩大检索面,搜更多 query、更多来源、更多候选页面;但不会把这些原始结果全部塞进主模型上下文,而是先用程序化过滤、结构化抽取和局部模型调用压缩成更干净的中间结果。

AI agent 越来越多地承担需要大量检索的任务——调研竞品、分析漏洞库、追踪技术文档。但这些 agent 所使用的搜索接口,本质上还是为人类设计的:输入一条查询,返回一页结果,由人类判断下一步。

当模型取代人类坐在搜索框前,这套流程开始暴露出深层矛盾。

固化管道的三个裂缝

传统搜索系统把检索逻辑封装成黑盒——模型只能看到最终输出,无法触及排序信号、候选集过滤逻辑或子文档粒度的内容。这种架构给 AI agent 带来了三个具体问题。

信息粒度太粗。同一个单体管道(monolithic pipeline)对所有任务用同一套召回与精排配置。但不同任务对信息的需求差异极大:有时需要广泛扫描官方来源,有时需要针对特定时间段精确匹配,系统无法按需调整。

领域知识被封死。模型本身可能知道“Mozilla 安全公告在 mozilla.org/en-US/security/advisories 下”“Chrome 版本信息在 release notes 里”。但面对固定的搜索接口,这些知识只能以查询词的形式隐式表达,无法直接影响检索策略。

控制流效率低下。多轮检索的中间结果全部进入模型上下文,造成上下文污染(context pollution)。过滤、聚合、去重等操作本可以用确定性代码完成,却不得不消耗 token 在模型的推理空间里完成。

原文的核心判断是:最强大的 AI 系统需要能够“决定如何检索上下文、处理上下文、聚合上下文、呈现上下文”,而不是只能消费固定管道的产出。

Search as Code:倒转主客关系

Perplexity 的方案叫做 Search as Code(SaC)。思路是把搜索系统从一个黑盒服务,拆解成一套可编程的原语(primitive)库,让模型通过生成 Python 代码来主动编排检索流程,而不是被动消费搜索结果。

原文的表述是:“让模型伸入搜索栈内部,而不仅仅是消费它的最终输出。”这句话容易让人误以为模型可以直接重排底层搜索 pipeline。更谨慎的理解是:模型主要伸入的是搜索任务工作流,而不是搜索引擎内部的 ranking pipeline

这是一次主客关系的倒转。原来是“模型调用搜索 API,等结果”;现在是“模型写代码,告诉搜索栈怎么运行”。模型从搜索的消费者变成搜索流程的编排者。

三层架构

SaC 由三层组成,分工清晰:

graph TB
    M["模型<br/>(控制平面)"] -->|"生成 Python 代码"| S["计算沙箱<br/>(执行环境)"]
    S -->|"调用原语"| SDK["Agentic Search SDK"]
    SDK --> P1["sdk.search.web_many()<br/>批量并行搜索"]
    SDK --> P2["sdk.llm.extract_many()<br/>结构化提取"]
    SDK --> P3["dedupe / filter<br/>去重过滤"]
    SDK --> P4["summarize()<br/>聚合统计"]
    S <-->|"序列化 / 反序列化"| FS["文件系统<br/>(跨轮状态)"]

模型(控制平面):负责理解任务、拆解检索子目标、生成 Python 代码。模型不直接调用搜索 API,而是通过代码声明整个检索策略。

计算沙箱(compute sandbox,执行环境):安全执行模型生成的代码,支持条件分支、异步、并行、批处理和自动重试。单次推理轮次(inference turn)内可以驱动数千个原子操作。

Agentic Search SDK(原子接口层):把 Perplexity 的搜索基础设施拆成可组合的模块——批量搜索、结构化提取、去重、过滤、聚合、子文档检索等——每个都是独立可调用的函数,而不是预打包的单体端点。这里的“原子化”主要发生在 agent 工作流层,不等于把底层 BM25 / dense retrieval / 粗排 / 精排参数交给模型自由组合

为什么选 Python

团队评估了 Python、Rust、TypeScript 和 Bash 四种运行时。Python 胜出的理由是“在数据处理生态和模型代码生成能力上最自然”,并通过实验验证了这一结论。这个选择不是一劳永逸的,会随着前沿模型能力和使用场景的变化持续重新评估。

Agent Skills:不只是 API 文档

SDK 的使用文档本身也是精心设计的产物,Perplexity 把它叫做智能体技能文档(Agent Skills)。与普通 API 文档不同,Skills 的定位不是“列出有哪些函数”,而是“教模型怎么把这些函数组合成有效的流程”。

每个根文件的 token 预算控制在 2000 以内,防止上下文膨胀。文件里除了函数签名,还有少样本示例(few-shot examples)和编排模式指导,帮助模型在面对复杂任务时直接套用可验证的代码框架。

Skills 本身也在持续优化——通过专门的自动研究循环(autoresearch loop),以延迟、代码生成质量和任务准确率为指标,连续数周迭代,驱动了 SDK 结构和风格上的大量改动。

状态管理:文件系统还是 REPL

多轮任务需要跨推理轮次保存中间状态。团队考虑过两种方案:

  • REPL 持久化:变量在会话中自动保留,代码更简洁
  • 文件系统 + 显式序列化反序列化(serde):每轮代码明确写入和读取状态

实验结果是文件系统方案在长轨迹任务上更可靠。背后的推测是:要求模型显式声明“我保存了什么、下一轮读什么”,能帮助模型更清晰地管理信息,而 REPL 的隐式状态积累容易引发命名空间混乱,在几十轮操作之后尤为明显。团队也坦诚这个结论仍是实验性的,还在持续迭代。

CVE 漏洞库构建:一个贯穿三轮的例子

用一个具体任务来看 SaC 的工作机制:从 2023—2025 年各厂商官方渠道收集 200+ 条高危 CVE,每条记录需包含受影响产品、修复版本,以及 CVE 与修复版本的明确对应关系,来源必须是厂商官方 advisory

这个任务的难点在于三个约束叠加:数量门槛、来源限制、字段绑定——正好把固化管道的所有短板都暴露出来。非 Perplexity 系统在这个任务上准确率全部低于 25%。

sequenceDiagram
    participant M as 模型
    participant S as 沙箱
    participant SDK as SDK
    participant FS as 文件系统

    M->>S: Turn 1:生成编排代码
    S->>SDK: web_many(厂商×年份查询模板, concurrency=12)
    SDK-->>S: 官方 advisory 结果
    S->>FS: 序列化中间结果

    M->>S: Turn 2:生成精化代码
    S->>FS: 反序列化上轮结果
    S->>SDK: summarize(by=["vendor","year","url_kind"])
    S->>SDK: 内嵌 LLM 生成补充查询 + 过滤聚合源
    S->>SDK: web_many(补充查询)
    SDK-->>S: 扩充结果
    S->>FS: 序列化

    M->>S: Turn 3:生成验证代码
    S->>FS: 反序列化
    S->>SDK: llm.extract_many(schema={CVE,vendor,fix_version...})
    S->>SDK: filter(confidence>0.75, severity=high/critical)
    SDK-->>M: 230+ 条记录,准确率 100%

Turn 1(扇出编排):模型生成代码,把领域知识直接编码为查询模板——('Mozilla', 'site:mozilla.org/en-US/security/advisories ...')('Jenkins', 'site:jenkins.io/security/advisories ...')('Chrome', 'site:chromereleases.googleblog.com ...')。然后调用 sdk.search.web_many(queries, limit_per_query=8, concurrency=12) 并行扫描所有厂商和年份组合,同时过滤只保留官方 advisory URL。

Turn 2(自适应精化):代码调用 summarize(pages, by=["vendor", "year", "url_kind"]) 统计覆盖情况,识别稀疏的厂商-年份组合,再调用内嵌 LLM 生成针对性补充查询,并验证这些查询不包含 NVD、MITRE、CERT 等聚合源——因为任务要求来源必须是厂商官方。

Turn 3(结构化验证):去重后,调用 sdk.llm.extract_many() 批量提取,schema 强制要求:CVE 编号、厂商、产品、修复版本、严重性、来源 URL、证据文本、version_bound_to_cve 标志位、置信度。代码过滤 confidence > 0.75version_bound_to_cve=True、严重性为 high 或 critical 的记录,持续补充直到满足数量要求。

最终结果:准确率 100%,token 使用量从基线的 288,700 降至 42,900(减少 85.1%)。

五项基准的对比结果

基准Perplexity (SaC)OpenAIAnthropicExaParallel
DSQA0.8710.7330.8150.5300.810
BrowseComp0.8050.7200.5980.3800.560
HLE0.6120.6140.5660.3870.515
WideSearch0.6510.5220.5900.4710.584
WANDR0.3860.1300.1520.0570.126

相比非 SaC 基线,DSQA 提升 19.77 个百分点(相对提升 29%),WANDR 提升 12.00 个百分点(相对提升 45%)。WANDR 上的领先尤为突出——最接近的竞争系统得分 0.152,Perplexity 以 2.5 倍优势领先。

在成本-性能边界(cost-performance frontier)上,SaC 在低推理配置下比所有其他系统都便宜,同时性能优于其中两个;中等推理配置下每任务成本低于 1 美元,但性能超过所有非 SaC 系统。

混合计算的更大图景

论文最后提出了一个更宏观的框架:最强大的计算系统,将把语言模型推理与确定性运行时(deterministic runtime)结合起来,而不是二选一

Token 空间的推理(reasoning in token space)擅长判断“什么证据重要”;确定性运行时擅长批量处理、过滤、排序和聚合。搜索基础设施是连接两者的 I/O 桥梁。

SaC 的可能后续方向包括:把 SDK 优化和 Skills 优化合并到同一个自动研究循环、在模型训练阶段引入低级原语的直接利用,以及让 SDK 设计与模型能力协同演化。

Perplexity 认为这个模式不局限于搜索,凡是涉及“语言理解 + 大量确定性操作”的领域,都可以用类似方式重构。


核心

SaC 的本质洞察是:搜索不只是模型的输入源,搜索流程本身应该是模型可以编程的对象。当模型能写代码来控制“怎么搜”,它就能把领域知识直接编码进检索策略,把并行、过滤、验证等逻辑从 token 空间移到确定性执行层,同时大幅减少上下文污染。

CVE 案例最能说明这一点:传统系统把收集 200+ 条高危漏洞当成一个检索-理解任务,而 SaC 把它分解成“编码领域知识 → 并行扫描 → 自适应补充 → 结构化验证”四个确定性步骤,每步都有代码保障,而不是依赖模型在上下文里推理。这就是为什么准确率能达到 100% 而 token 消耗反而降了 85%。

另一个值得记住的细节是文件系统 serde 的选择。显式声明“我保存了什么”看起来是冗余操作,但在长轨迹任务里,它实际上帮助模型保持对状态的清晰认知——和软件工程里“显式优于隐式”的原则一脉相承。

评价

说得好的地方:架构分层清晰,三层职责不重叠。CVE 案例不是为了炫技,而是一个真实揭示传统系统失败原因的任务——数量门槛、来源限制、字段绑定三个约束叠加,正好把固化管道的所有短板都暴露出来。文章也诚实地承认“文件系统 vs REPL 的结论是实验性的,仍在迭代”,没有过度美化。

有问题的地方:基准测试的公正性存疑。Perplexity 同时设计基准、运行自己的系统、汇报结果,对手方是否在相同条件下运行、用的是哪个版本、是否做过针对性优化,文章没有说明。WANDR 2.5× 的领先幅度让人印象深刻,但也让人想问:这个基准是不是天然更适合 SaC 的架构?

没说到的地方:SaC 对延迟的影响几乎没有讨论。代码生成、沙箱启动、跨轮序列化都有额外开销,文章只提到“autoresearch loop 以延迟为优化指标之一”,但没有给出具体数字。对于实时或交互式搜索场景,这个代价是否可接受,读者无从判断。

隐含假设:Python 代码生成的可靠性被默认为已解决的问题。现实中,模型生成的代码可能有运行错误、逻辑错误或安全风险,这些在文章里几乎没有涉及。沙箱的隔离机制和错误恢复策略也语焉不详。此外,整个方案高度依赖 Perplexity 自己的搜索基础设施,可移植性有限——这是一篇技术营销文章,而不是通用架构设计指南。

FAQ:SaC 到底新在哪里

SaC 和“用 Google API 多轮搜索”有什么区别?

如果只看单次搜索能力,区别没有想象中那么大。Google API 本来就支持 site:、时间范围、语言、地区等参数,agent 也可以通过多轮调用不断调整 query。

真正的差别在于抽象层级:传统方案是模型反复调用搜索接口;SaC 是模型先写一个搜索工作流程序,再由程序批量调用搜索、过滤、抽取和聚合。

flowchart TB
    Q["同一个复杂研究任务<br/>例如:跨厂商收集 200+ 条高危 CVE"] --> L["主模型理解任务"]

    L --> GQ["传统多轮搜索:<br/>主模型逐轮生成 query / 参数"]
    L --> SC["SaC:<br/>主模型先生成搜索工作流程序"]

    GQ --> GA["调用 Google / 搜索 API"]
    SC --> SQ["程序批量展开 query<br/>厂商 × 年份 × 来源策略"]
    SQ --> SA["并发调用搜索 API"]

    GA --> GR["返回 doc list / snippet"]
    SA --> SR["返回更多候选 doc / 页面"]

    GR --> GC["差异点 1:<br/>大量原始结果进入主模型上下文"]
    GC --> GD["差异点 2:<br/>主模型承担阅读、去重、记忆、判断、规划"]
    GD --> GQ

    SR --> PF["差异点 1:<br/>程序化中间处理<br/>域名过滤、去重、分组、coverage 统计"]
    PF --> EX["差异点 2:<br/>局部 LLM / extractor<br/>只对候选片段做 schema 抽取"]
    EX --> SS["差异点 3:<br/>结构化记录 + 少量证据 + 缺口统计"]
    SS --> SD["主模型只看压缩后的状态<br/>决定是否补搜或修正程序"]
    SD --> SC

    GC -.-> C1["传统路径瓶颈:<br/>候选一多,主模型上下文就膨胀"]
    SS -.-> C2["SaC 路径收益:<br/>候选更多,但上下文更少"]

    classDef traditional fill:#3a1f1f,stroke:#e46a6a,color:#fff,stroke-width:2px
    classDef sac fill:#173429,stroke:#54d18a,color:#fff,stroke-width:2px
    classDef insight fill:#2d2a12,stroke:#f0c84b,color:#fff,stroke-width:2px

    class GC,GD,C1 traditional
    class PF,EX,SS,SD,C2 sac
    class Q,L insight

一句话:Google API 让模型更会用搜索;SaC 让模型临时写出一个搜索任务处理器。

如果过滤和抽取仍然需要 LLM,SaC 凭什么省上下文?

SaC 不是把 LLM 从流程里拿掉,而是把 LLM 的使用拆开:主模型负责编排,局部模型负责抽取,确定性代码负责机械处理。

很多过滤根本不需要 LLM:

  • URL 是否属于官方域名
  • 年份是否在目标区间
  • 是否匹配 CVE 正则
  • 是否来自 NVD、MITRE、CERT 等聚合源
  • 是否重复
  • 字段是否为空
  • 置信度是否超过阈值

真正需要语言理解的部分,例如“这个版本号是否确实修复了这个 CVE”“严重程度是 High 还是 Critical”,仍然可以调用 LLM。但它被限制为局部字段抽取任务,而不是让主模型在一个巨大的上下文里同时读网页、维护状态、规划下一步。

flowchart TB
    A["大量候选页面"] --> B["代码:域名过滤、去重、年份过滤"]
    B --> C["局部 LLM:按 schema 抽取字段"]
    C --> D["代码:confidence / evidence / severity 校验"]
    D --> E["结构化记录 + 少量证据片段"]
    E --> F["主模型:看覆盖率、缺口和异常,决定下一轮策略"]

所以 SaC 的收益不是“不用模型判断”,而是:不要让同一个主模型上下文承担所有判断。

“候选更多,但上下文更少”是什么意思?

这是 SaC 最值得记住的一句话:候选更多,但上下文更少。

传统 multi-turn 搜索为了不把上下文撑爆,往往只能让模型读有限数量的结果;SaC 反过来,先大胆扩大候选集,再用程序和局部抽取把原始网页压缩成结构化中间结果。

flowchart LR
    subgraph T["传统多轮搜索"]
        T1["搜索 20 个结果"] --> T2["结果摘要进入主上下文"]
        T2 --> T3["主模型读、记、判断、规划"]
    end

    subgraph C["SaC"]
        C1["搜索 300 个候选"] --> C2["程序过滤成 80 个官方页"]
        C2 --> C3["局部抽取 45 条有效记录"]
        C3 --> C4["主模型只看统计、缺口、证据样本"]
    end

可以把它概括成两个动作:

  • 扩大检索面:搜索更多 query、更多来源、更多候选页面。
  • 压缩推理面:只把高质量结构化结果和必要证据交给主模型。

这也是为什么 SaC 适合复杂研究任务,而不是简单问答。简单问题搜三五个页面就够了,SaC 反而可能显得笨重;但当任务需要跨几十个来源收集几百条记录时,这种架构就能发挥优势。

SaC 是 Search Pipeline as Code 吗?

严格说不是。更准确的名字应该是:Search Workflow as Code。

搜索系统内部的 pipeline 包括 query rewrite、召回、相关性特征、粗排、精排、多样性控制、freshness boost、authority boost 等阶段。Perplexity 文章展示的 SaC 并没有把这些底层模块完全开放给模型自由组合。

flowchart TB
    subgraph P["底层搜索 pipeline(未开放)"]
        direction TB
        P1["query rewrite<br/>查询改写"]
        P2["recall<br/>召回"]
        P3["ranking features<br/>相关性特征"]
        P4["first-stage ranking<br/>粗排"]
        P5["reranking<br/>精排"]
        P6["dedupe / diversity / snippet<br/>去重、多样性、摘要"]

        P1 --> P2 --> P3 --> P4 --> P5 --> P6
    end

    P6 --> API["高层搜索能力<br/>web_many / fetch / extract_many"]

    subgraph W["SaC 编排的 workflow 层"]
        direction TB
        W1["批量生成 query<br/>厂商 × 年份 × 来源策略"]
        W2["调用 web search / fetch<br/>拿到候选页面"]
        W3["代码过滤、去重、分组<br/>官方域名、年份、覆盖率"]
        W4["局部抽取<br/>schema + evidence + confidence"]
        W5["聚合缺口<br/>决定是否补搜"]

        W1 --> W2 --> W3 --> W4 --> W5 --> W1
    end

    API --> W1

    P4 -.-> B1["不适合模型临场决定:<br/>BM25/dense 权重、粗排阈值、精排模型"]
    W3 -.-> B2["适合模型编排:<br/>搜哪些来源、如何补洞、如何过滤和验证"]

    classDef closed fill:#3a1f1f,stroke:#e46a6a,color:#fff,stroke-width:2px
    classDef open fill:#173429,stroke:#54d18a,color:#fff,stroke-width:2px
    classDef boundary fill:#2d2a12,stroke:#f0c84b,color:#fff,stroke-width:2px

    class P1,P2,P3,P4,P5,P6,B1 closed
    class W1,W2,W3,W4,W5,B2 open
    class API boundary

Perplexity 开放的是上层任务编排能力,例如 web_many()extract_many()filter()summarize(),而不是让模型直接决定 BM25 和 dense retrieval 的权重、粗排阈值或精排模型。

这个边界是合理的。底层 ranking pipeline 不适合让大模型临场猜参数,因为相关性质量依赖离线评测、在线实验和长期反馈闭环。模型可以理解任务意图,但很难凭空决定“BM25 权重应该是 0.37 还是 0.52”。

那更理想的接口形态是什么?

更现实的方向不是让模型自由组合所有底层搜索模块,而是提供一组经过搜索工程师验证过的策略档位,再让模型在任务层编排它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sdk.search(
    query,
    profile="official_sources_high_precision",
    time_range="2023-2025"
)

sdk.search(
    query,
    profile="broad_recall_research",
    diversity="high"
)

sdk.search(
    query,
    profile="technical_docs",
    source_types=["docs", "github", "spec"]
)

这些 profile 背后仍然可以包含复杂的 query rewrite、hybrid retrieval、authority boost、freshness adjustment 和 cross-encoder rerank。但这些底层策略应该由搜索团队调优,而不是由模型临场拍脑袋。

flowchart TB
    E["算法工程师 / 评测系统"] --> P["验证过的搜索 profile"]
    P --> A["agent 选择和组合 profile"]
    A --> R["批量搜索候选"]
    R --> C["代码过滤、抽取、聚合"]
    C --> M["主模型根据缺口修正策略"]

更准确的分工是:

  • 搜索团队:保证底层召回、排序、精排和 profile 质量。
  • 大模型:根据任务选择搜索策略,组织多轮探索和补洞。
  • 程序运行时:承担去重、过滤、统计、阈值判断等机械工作。
  • 局部抽取模型:在必要时从页面里抽取结构化字段和证据。

SaC 最大的风险是什么?

最大风险是:第一版程序如果写错,错误会被批量放大。

例如模型漏掉某些官方域名、错误排除了有用页面、把过滤条件写反,后面的并发搜索和批量抽取都会沿着错误方向高速执行。SaC 的优势会变成劣势:错得更系统、更快、更隐蔽。

因此 SaC 不能只依赖“模型一次写对程序”,还需要运行反馈和中间指标:

stateDiagram-v2
    [*] --> WriteCode: 生成搜索程序
    WriteCode --> Run: 执行
    Run --> Inspect: 查看覆盖率、错误率、抽取成功率
    Inspect --> Revise: 发现缺口或异常
    Revise --> Run: 修改程序继续跑
    Inspect --> Done: 覆盖率和证据质量达标
    Done --> [*]

一个健康的 SaC 流程应该暴露这些指标:

  • 每个来源、年份、主题的覆盖率
  • 官方来源占比
  • 抽取成功率
  • unknown 字段比例
  • 带证据记录的比例
  • 低置信度记录数量
  • 去重前后的候选数量

这也是 SaC 相比纯自然语言多轮检索更工程化的地方:错误更容易定位到 query 生成、过滤条件、schema 抽取或覆盖率统计这些具体环节。

最终怎么理解 Perplexity 的 SaC?

可以把它压缩成这句话:

SaC 是用 skill 教模型写搜索工作流程序,通过更大的检索扇出、更机械化的中间处理、更局部化的模型抽取,减少主模型上下文负担,从而在复杂检索任务中保留更多有效 doc、丢掉更多噪音 doc。

它的关键不是减少搜索 query,而是:

扩大检索面,压缩推理面。

也就是前面那句:候选更多,但上下文更少。

本文由作者按照 CC BY 4.0 进行授权