Perplexity内部Agent设计手册拆解:3个思路让你少走6个月弯路

你有没有遇到过这种情况:

精心搭了一个Agent,给它配好工具、写好Prompt,跑起来的前两步还算顺畅,但到第三步它突然开始「自由发挥」——要么调用了一个根本不该调用的工具,要么在工具返回空结果时陷入无限重试,要么干脆编造了一个看起来合理但完全错误的输出。

你开始怀疑是模型不够强,换了更贵的版本,问题依然在。

其实问题不在模型,在你的工具设计哲学。

最近流传出一份Perplexity内部的Agent Skills设计文档,不是官方PR稿,是工程团队内部使用的方法论手册。这份文档之所以值得认真对待,是因为Perplexity的Assistant在多步任务执行上的稳定性,在同类产品里确实属于第一梯队——而这背后有一套系统性的设计思路,而不是靠堆算力和试错。

我把这份手册里最核心的3个设计思路拆出来,逐一解释,并给出可以直接套用的模板。如果你在搭Agent,这篇文章能帮你绕过大多数人要踩半年才能踩完的坑。

---

第一章:技能原子化——大而全的工具是Agent失控的根源

手册里有一句话,我觉得是整份文档的纲领:

"A skill should do exactly one thing, and do it completely. If you find yourself writing 'and' in a skill description, you have two skills."

>

「一个技能只做一件事,并把它做完整。如果你在技能描述里写了'以及',那你其实有两个技能。」

这句话听起来简单,但大多数开发者在实践中做的恰恰相反。

常见的错误模式是这样的:搭一个「搜索并总结」的工具,或者「获取数据并格式化输出」的工具。逻辑上没问题,但对LLM来说,这种「复合技能」会带来一个隐患——模型在决定是否调用这个工具时,它的判断边界是模糊的

当任务只需要搜索、不需要总结时,模型可能还是调用了这个工具,然后把总结的部分「凑」出来。当搜索失败时,模型不知道该在哪个环节fallback。整个调用链的可观测性也极差,你不知道是搜索出了问题还是总结出了问题。

原子化的核心逻辑是:让每个技能成为最小可独立执行单元。

对比一下同一个功能的两种定义方式:

# ❌ 复合技能:职责不清,边界模糊

def search_and_summarize(query: str) -> str:

"""

搜索相关信息并返回摘要。

支持网页搜索、文档检索,自动判断来源并总结。

"""

results = web_search(query)

summary = llm_summarize(results)

return summary

✅ 原子化技能:职责单一,边界清晰

def web_search(query: str, max_results: int = 5) -> list[SearchResult]:

"""

执行网页搜索,返回原始结果列表。

只负责检索,不做任何内容处理或总结。

"""

return search_engine.query(query, limit=max_results)

def summarize_results(results: list[SearchResult], focus: str) -> str:

"""

对给定的搜索结果列表生成摘要。

输入必须是SearchResult列表,不执行搜索操作。

"""

return llm.summarize(results, focus_point=focus)

两段代码的功能是等价的,但原子化之后,模型在调用时的决策路径清晰了一个量级——它知道什么时候该搜索,什么时候该总结,两件事可以独立失败、独立重试。

给开发者的拆分Checklist:
  • 技能描述里是否出现了「并且」「同时」「以及」?如果有,拆开。
  • 这个技能的输入和输出能否用一句话说清楚?说不清楚,继续拆。
  • 如果这个技能执行失败,你能精确定位到是哪一步出问题了吗?不能,继续拆。
  • 这个技能能否在不依赖其他技能的情况下独立测试?不能,重新设计。

---

第二章:上下文边界声明——告诉模型「它不做什么」同样重要

如果说第一个思路是搭骨架,这个思路就是在骨架上装保险丝。

手册里这条规则是反直觉的:每个Skill必须显式声明它不做什么(Negative Scope Declaration)。

大多数开发者写工具描述时,只写「这个工具能做什么」。但LLM在决定调用哪个工具时,它的判断是基于语义相似度的——如果一个工具的描述和当前任务「看起来相关」,模型就可能调用它,即使这个工具并不适合当前场景。

这就是手册里所说的「幻觉式工具滥用」(Hallucinatory Tool Overuse)——模型不是在精确匹配工具能力,而是在做语义联想。

解决方案是在Prompt里显式声明边界。 看两个版本的对比:
# ❌ 没有边界声明的搜索技能Prompt

你是一个网页搜索工具。

当用户需要查找信息时,使用此工具搜索互联网。

返回相关的搜索结果。

✅ 带边界声明的搜索技能Prompt(可直接复制使用)

工具:网页实时搜索(web_search)

适用场景

  • 需要获取实时信息(新闻、价格、最新数据)
  • 需要验证不确定的事实
  • 需要查找特定URL或网站内容

明确不适用场景(禁止调用)

  • 用户询问的是你已知的通用知识(历史事件、基础概念等)
  • 任务是对已有文本进行处理、总结或改写
  • 用户明确说"不要搜索"或"根据你的知识回答"
  • 查询内容涉及用户的私人信息或本地文件

输出说明

  • 返回原始搜索结果列表,不做内容判断
  • 不总结、不推断、不补充搜索结果以外的信息
  • 如果搜索结果为空,返回空列表,不伪造内容
关键不在于写得多详细,而在于「负向边界」的存在本身——它给模型提供了一个明确的「不该做」的参照系,把工具滥用的概率大幅压低。

这个技巧在工具数量超过5个时效果尤其显著。工具越多,模型的选择困难越严重,边界声明相当于给每个工具贴了一张「禁止入内」的标签,让模型的决策树更清晰。

---

第三章:失败路径优先设计——先想清楚「坏了怎么办」

这是手册里我认为最被低估的一条原则。

手册原文:

"Design the failure path before the success path. A skill that handles failure gracefully is more valuable than a skill that succeeds perfectly but crashes on edge cases."

>

「先设计失败路径,再设计成功路径。一个能优雅处理失败的技能,比一个在边缘情况下崩溃的完美技能更有价值。」

大多数开发者的Agent只有一条路:成功路径。当工具调用失败时,模型要么无限重试,要么直接放弃并给用户一个莫名其妙的错误。这是Agent在生产环境里最常见的崩溃模式。

Perplexity的手册要求每个Skill在设计时必须预定义至少3种失败模式及其fallback行为

以「自动整理会议纪要」这个场景为例:

| 失败类型 | 触发条件 | Fallback行为 | | 输入为空 | 音频文件损坏、转录结果为空字符串 | 返回结构化错误信息,提示用户重新上传;不继续执行后续步骤 | | 内容识别失败 | 转录内容无法识别出会议结构(无发言人、无议题) | 降级处理:跳过结构化提取,直接返回原始转录文本,并标注「低置信度」 | | 下游服务不可用 | 邮件发送API超时或返回错误 | 将摘要内容缓存到本地,记录失败日志,等待重试;向用户返回「摘要已生成,发送待重试」而非报错 |

这张表格不只是文档,它直接对应到系统Prompt里的指令:

SKILL_PROMPT = """

失败处理规则(优先级高于成功路径)

1. 如果输入内容为空或无法解析:

- 立即停止执行

- 返回:{"status": "error", "code": "EMPTY_INPUT", "message": "输入内容无效"}

- 不要尝试补全或猜测内容

2. 如果内容识别置信度低于阈值:

- 继续执行,但在输出中标注 "confidence": "low"

- 附加原始内容供用户核验

- 不要隐藏不确定性

3. 如果下游服务调用失败:

- 最多重试2次,间隔5秒

- 超过重试次数后,保存结果到临时存储

- 向用户报告部分成功状态,而非完全失败

"""

失败路径设计的本质是:把边缘情况从「运行时惊喜」变成「设计时决策」。 你在设计阶段想清楚了,模型在执行时就不需要自己发挥——而LLM的「自由发挥」,往往是Agent不稳定的最大来源。

---

第四章:把3个思路落地——从零搭一个会议纪要Agent

理论讲完,来一个完整的迷你案例。

目标:自动整理会议录音,生成结构化摘要,发送给参会者。 第一步:原子化拆分技能(对应思路一)

不要搭一个「处理会议」的大工具,拆成4个原子技能:

1. transcribe_audio(file_path) → 音频转文字

2. extract_meeting_structure(transcript) → 提取议题/决议/待办

3. format_summary(structure, template) → 格式化摘要

4. send_email(recipients, subject, body) → 发送邮件

每个技能独立可测,独立可替换。

第二步:给每个技能加边界声明(对应思路二)
TRANSCRIBE_SKILL = """

工具:音频转录(transcribe_audio)

适用:将音频文件转换为文字记录

不适用:

  • 处理视频文件(只接受音频格式)
  • 对转录内容做任何理解或总结
  • 识别说话人情绪或语气

输出:纯文字转录,保留原始语序,不做任何编辑

"""

第三步:定义失败路径(对应思路三)
# 完整的会议纪要Agent主循环,含失败处理

def meeting_agent(audio_file: str, recipients: list[str]) -> dict:

# Step 1: 转录

transcript = transcribe_audio(audio_file)

if not transcript or len(transcript) < 50:

return {"status": "failed", "step": "transcription",

"message": "转录内容过短,请检查音频文件"}

# Step 2: 结构提取,失败时降级

structure = extract_meeting_structure(transcript)

if structure.confidence < 0.6:

structure = {"raw_transcript": transcript,

"confidence": "low", "note": "自动提取失败,附原文"}

# Step 3: 格式化

summary = format_summary(structure)

# Step 4: 发送,失败时缓存

result = send_email(recipients, "会议纪要", summary)

if not result.success:

cache_to_local(summary) # 缓存到本地

return {"status": "partial", "message": "摘要已生成,邮件发送待重试"}

return {"status": "success", "summary": summary}

整个Agent不到30行核心逻辑,但覆盖了主要的失败场景,不会在边缘情况下让用户看到莫名其妙的报错。

---

如果你想直接跑起来验证这套思路,可以用 [api.884819.xyz](https://api.884819.xyz) 接入主流模型API——兼容OpenAI格式,按量计费,新用户注册即送体验token,国产模型(Deepseek/千问等)完全免费,不用担心额度问题影响你的测试节奏。调通了再决定要不要用在生产环境。

---

结语:工具工程学,99%的开发者还没意识到这件事

Perplexity这份手册真正在说的,不只是「如何搭一个好用的Agent」。

它背后有一个更大的命题:AI产品团队需要建立一套「工具工程学」(Tool Engineering)——这和软件工程里的接口设计哲学一脉相承,只是大多数开发者还没意识到这件事。

我们花了几十年建立起一套关于「如何设计好的API」的工程文化:单一职责、明确契约、优雅降级。这些原则在LLM时代不是过时了,而是变得更重要——因为调用你工具的不再是确定性的代码,而是一个会「自由发挥」的语言模型。

你给它设计的每一个工具,都是在给它划定一块认知领地。划得清楚,它就能精准执行;划得模糊,它就会用幻觉来填补空白。

这三个思路——原子化、边界声明、失败优先——本质上是在说同一件事:把你作为设计者的决策,显式地编码进工具定义里,而不是期待模型自己想明白。

---

这份手册里其实还有一个部分我没展开讲:Perplexity如何设计Skills之间的编排层(Orchestration Layer)——也就是当多个原子技能需要协作时,谁来决策调用顺序、如何处理技能间的状态传递。这个问题在单Agent场景下不明显,但一旦你开始搭Multi-Agent系统,它会成为最先卡住你的那道墙。下一篇我们专门拆这个。

---

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。

#AI教程 #Agent开发 #Perplexity #工具调用 #LLM #8848AI #AI工程 #MultiAgent