Perplexity内部Agent设计手册拆解:3个思路让你少走6个月弯路
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