Skip to content

Latest commit

 

History

History
537 lines (409 loc) · 38.6 KB

File metadata and controls

537 lines (409 loc) · 38.6 KB

11 构建智能体系统的技巧(Tips for building agentic systems)

本章内容

  • 按五个智能体层组织的经过实践验证的技巧;
  • 面向客户支持智能体的角色特定指导;
  • RAG 智能体系统的设计模式;
  • 深度研究智能体的蓝图。

随着我们即将结束这段智能体开发之旅,我们可以从理论和教程阶段进一步迈向更具实践性的内容,介绍一些实用技巧。这些技巧和经验都来自于在实际环境中构建生产级智能体和智能体系统的真实经历。它们可以帮助你提升智能体开发能力,并了解真实世界中的智能体是如何运行的。在本章结束时,你应该能够掌握一套实用模式和检查清单,并将其应用于设计真实世界中的智能体和智能体系统。

11.1 按五个智能体层组织的经过实践验证的技巧

在本书的整个过程中,我们一直通过智能体的五个功能层来探索智能体:角色设定(persona)、工具与行动(tools and actions)、推理与规划(reasoning and planning)、知识与记忆(knowledge and memory),以及评估与反馈(evaluation and feedback)。图 11.1 回顾了每一层所负责的内容。

图 11.1 智能体的五个层次。对于每一层,内部的方框表示可以帮助改进该层开发的模式。

现在,让我们重新回顾这些层次,并逐层探讨一些实践技巧。

11.1.1 核心层:角色设定(Persona)

智能体的个性会影响所有后续选择(工具、记忆、规划)。应将角色设定视为一种 API 契约,而不是一段描述性文字。精心设计的角色设定(配合清晰的提示词指令)能够在各个层面显著影响智能体的行为。以下是在构建智能体核心部分——角色设定时,需要牢记的四条指导原则:

  • 定义清晰的角色和边界。明确说明智能体_做什么_以及_不做什么_;在指令的开头和结尾重复关键规则,以抵抗近期信息偏差和行为漂移(。提供一个明确的“我不知道”退出机制。
  • 保持范围狭窄。专注于单一职责的专业化智能体,其表现优于“什么都能做”的机器人,同时也更容易进行测试和成本优化。
  • **优先使用结构化输出_。当智能体需要与其他代码或智能体交互时,返回符合某个模式(schema)的 JSON 响应(例如 Pydantic/dataclass)。这样可以减少解析错误和 token 膨胀问题。
  • 使用动态指令。通过函数注入运行时信息(例如日期、组织、用户信息),而不是将这些内容硬编码。注意不要过度使用动态注入;保持这些信息与智能体的使用场景和目标相关。过度注入可能导致 token 膨胀、智能体混乱以及响应质量下降。

下面的代码清单融合了这四项原则,并提供了如何持续应用这些原则来构建智能体的示例。

清单 11.1 01_core_persona_tips.py
from pydantic import BaseModel, Field
from agents import Agent, Runner
from datetime import date

class Answer(BaseModel):    #1
   """Machine-readable answer format."""
   answer: str = Field(..., description="Concise, user-facing answer")
   citations: list[str] = Field(default_factory=list)
   confidence: float = Field(..., ge=0, le=1)

def core_instructions(ctx, agent) -> str:    #2
   today = date.today().isoformat()    #3
   return (
       f"You are SupportMentor, a helpful domain assistant. Today is {today}.\n"    #4
       "Always:\n"
       "1) Answer concisely; 2) Prefer retrieved context; 3) If context is missing, say you don't know.\n"
       "Output must be valid JSON matching the Answer schema.\n"
       "Never: make policy exceptions or invent facts."
   )

core_agent = Agent(
   name="SupportMentor",
   instructions=core_instructions,   
   output_type=Answer                
)

input = "What's our refund window for accessories?"
result = Runner.run_sync(core_agent, input)
print(result.final_output)

注释

  • #1 使用类型化数据类(typed data class)定义智能体的输出
  • #2 保持智能体范围狭窄且专注
  • #3 在提示词指令中使用动态输入
  • #4 保持角色设定专注且定义清晰

这个简单的代码示例展示了这四项原则在实际中的应用。在创建智能体时,请使用这四条指导原则。

11.1.2 工具与智能体行动

工具是智能体的双手和感官。优秀的工具可以赋能智能体,而糟糕的工具同样会让智能体变得低效。在构建工具、使用来自其他 MCP 服务器的工具,或将智能体作为工具使用时,需要遵循以下五条核心指导原则:

  • 使用单一职责的工具,并提供清晰的文档字符串和类型化参数。模型通过工具描述和模式(schema)来学习如何使用工具。工具的描述、名称以及输入参数必须能够清晰指导模型如何使用它。
  • 优先为代码和 API 使用函数工具。装饰器可以根据类型提示和文档字符串自动生成模式,使工具更容易测试和复用。
  • 控制工具何时运行。使用模型/工具设置,在特定轮次中强制或禁止调用工具;当单个 API 结果已经足够时,应在第一次工具调用后停止。需要在智能体的提示词中明确说明何时以及如何使用工具,以支持这种模式。
  • 在合适的情况下使用预构建工具(代码、Web、文件搜索)。使用托管工具和经过强化的 MCP 服务器来快速扩展能力。对于使用的任何 MCP 服务器,都应进行充分的审查,以确保其安全、可靠,并且职责明确。
  • 为失败情况做好规划。添加超时、重试机制以及友好的降级方案。工具可能会失败,因此需要确保智能体理解工具失败的原因,以及可能的修正方式。

下一个代码清单展示了一个简单的工具调用型智能体,该智能体融合了全部五条指导原则,并展示了最佳实践。

清单 11.2 02_tool_action_tips.py
from agents import (
   Agent, 
   Runner, 
   function_tool, 
   ModelSettings
)
import json

def _failure_error_function(context, e) -> str:        #1
   return json.dumps({"status": "error", "message": str(e)})

@function_tool(
   failure_error_function=_failure_error_function)    #1
def lookup_order(order_id: str) -> dict:   #2
   """Check order status by ID. Use when the user asks about their order."""         #2
   if not order_id.startswith("ORD-"):
       raise ValueError("Invalid order id format")
   return {"status": "shipped", "eta_days": 3}


tooling_agent = Agent(
   name="ToolingAgent",
   instructions="Use tools when needed; respond with JSON.",    #3
   tools=[lookup_order],
   model_settings=ModelSettings(
       tool_choice="auto",    #3
       parallel_tool_calls=True,     #3
   )
)
print(Runner.run_sync(
   tooling_agent, 
   "What's the status of order ORD-123?"
).final_output)

注释

  • #1 通过捕获格式错误并从工具错误中优雅恢复,为失败情况做好规划
  • #2 使用单一职责的工具,并提供清晰、定义明确的文档字符串和类型化参数
  • #3 控制工具何时以及如何运行

在构建和使用工具时,关键要点是确保工具保持专注且定义清晰。这同样适用于 MCP 服务器。请记住,智能体每使用一个工具,都会为其进行的每次 LLM 调用增加额外开销。

工具膨胀

作为一条规则,应始终保持工具集合规模较小;在使用 MCP 服务器时,要注意服务器暴露了多少工具。请记住,每个工具都会携带一份关于如何使用它的描述信息,而这些信息会增加 LLM 消息中的 token 数量和上下文内容。为了避免智能体产生困惑并降低成本,应让工具始终围绕智能体的角色保持专注。

11.1.3 推理与规划

多步骤任务需要一个计划、一个循环,以及停止条件。推理与规划是智能体行为的基石,它们可能决定任务最终是成功达成目标还是失败。尽管如今大多数基础消费级模型都已经具备推理能力,但通常最好理解并控制这种推理是如何被使用的。以下技巧可以帮助你在智能体中更好地使用推理能力:

  • 针对复杂任务采用 ReAct。在思考和行动之间交替进行;检查观察结果;然后继续执行。使用 ReAct 或思维链(chain-of-thought)的提示词指令模式,引导智能体的思考过程。
  • 针对大型目标采用先规划后执行。创建检查清单,并使用顺序思考服务器等工具记录计划。允许智能体构建思考过程,可以给予它更多思考时间,从而更好地解决复杂任务。
  • 限制迭代次数。限制交互轮次,防止无限循环;在失败时安全退出,并提供有帮助的信息。
  • 在最终输出前进行自我审查。增加一个快速的反思步骤,用于捕获明显错误。

对于简单的一次性任务,你可能希望完全避免推理,可以通过在智能体指令中避免要求推理,或者在 LLM 中降低甚至关闭模型的推理能力来实现。

让我们通过一个简单的规划型智能体来看看这些技巧在实践中的应用。该智能体可以访问参考 MCP 顺序思考服务器(sequential thinking server)。

清单 11.3 03_reasoning_planning_tips.py
from typing import Literal
from pydantic import BaseModel
from agents import Agent, Runner
from agents.mcp import MCPServerStdio

MAX_TURNS = 3  #1

thinking_srv = MCPServerStdio(    #2
       name="sequential-thinking",
       params={
           "command": "npx",
           "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"],
       },
   )

class Answer(BaseModel):
   status: Literal["ok", "needs_followup"] = "ok"
   checklist: list[str] = []
   summary: str
   sources: list[str]

instructions = f"""
You are a planner-executor.
If the message starts with 'PLAN ONLY', 
return only the checklist (no actions).
1) Plan→Execute: draft a concise 3–5 item checklist, then execute.    #2
2) ReAct: Thought→Action(tool)→Observation;    #3
inspect each observation before continuing.
3) Limit: stop after at most {MAX_TURNS} tool calls;    #1
if reached, set status='needs_followup' and return best effort.
4) Self-review: before final, catch obvious mistakes,     #4
ensure the question is answered, and cite sources.
Use seq_think between actions to decide the next step. 
Return Answer JSON only.
"""

async def main():
   agent = Agent(
       name="Planner", 
       instructions=instructions, 
       mcp_servers=[thinking_srv], 
       max_turns=MAX_TURNS,
       output_type=Answer)

   async with thinking_srv:
       question = "Summarize our refund policy and cite relevant internal docs."
       plan = await Runner.run(agent, f"PLAN ONLY. Question: {question}")
       print(plan.final_output.checklist)

       result = await Runner.run(agent, question)
       print(result.final_output)

if __name__ == "__main__":
   import asyncio
   asyncio.run(main())

注释

  • #1 限制智能体可以执行的迭代次数
  • #2 针对大型目标先规划再执行
  • #3 采用 ReAct、CoT 或其他推理策略
  • #4 在最终输出前执行自我审查

能够鼓励并控制推理与规划,可以限制 LLM 的外部思考过程。如今,大多数商业 LLM 都具备内置推理能力,这对于通用问题通常表现良好。但如果没有得到充分控制,推理模型在自主运行时可能会变得非常不可预测。不要认为具备推理能力的 LLM 总会做出正确的决策;很多时候,它并不会。

11.1.4 知识与记忆

检索对于知识和记忆至关重要,因为两者都依赖于快速定位相关信息的能力。当我们讨论如何优化知识或记忆时,通常都会从检索开始。以下是在处理检索以及寻找相关信息这一实践过程中,需要遵循的一些关键原则:

  • 将 RAG 视为一项核心能力。在知识库之上添加一个检索工具(向量存储加元数据过滤器),而不是简单地将文档全部塞入提示词中。
  • 区分会话与长期记忆。使用会话线程保存短期上下文;将用户事实和偏好持久化存储到向量数据库/数据库中,并根据需要选择性检索。积极进行清理和裁剪。
  • 在大规模场景下优化检索。使用 ANN 索引(HNSW/IVF),按照领域进行分片,并通过元数据进行过滤(例如产品、版本、日期)。
  • 采用混合搜索模式。不要依赖单一模式,例如向量(语义)搜索。相反,应结合关键词搜索、图层级搜索以及其他检索模式来增强你的策略。
  • 有意识地选择 Embedding。在质量、维度和成本之间进行权衡;考虑使用领域专用或多语言版本来优化方案。Embedding 的大小在存储空间分配时可能是关键因素。
  • 合理切分并确保答案有依据。优先使用语义连贯的文本块,并要求回答生成器_仅使用_检索到的上下文内容(如果答案不存在,则明确表示“不知道”)。
  • 保持数据新鲜,并按照角色和类型进行分区。定期更新知识库,并隔离敏感部门、内容或租户。不要允许用户访问他们不应该看到的上下文内容。即使你认为基础的单索引 RAG 可以满足当前使用场景,也应该预期需求会发生变化,并提前规划实现基于角色/类型的分区。

清单 11.4 通过一个简单直接的单智能体示例展示了这些最佳实践,该示例使用了知识和记忆能力。代码假设使用的是一个通用向量数据库,因此你的具体实现可能会根据实际情况有所不同。

清单 11.4 04_knowledge_memory_tips.py
from agents import Agent, Runner, SQLiteSession, function_tool

kb = load_vector_kb(     #1
   embedding_model="text-embedding-3-small",       #2
   index="HNSW",                                
   shards=["product", "policy", "engineering"], )

@function_tool
def retrieve(query: str, product: str|None=None, version: str|None=None,
            date: str|None=None, role: str|None=None, tenant: str|None=None) -> str:
   """Return grounded snippets using metadata filters."""
   return kb.query(query=query, k=5,
       filters={"product": product, "version": version, "date": date,
                "role": role, "tenant": tenant})    #3

@function_tool
def remember(user_id: str, fact: str) -> str:
   """Persist long-term user fact in vector memory; prune aggressively."""
   kb.upsert(text=fact, metadata={"user_id": user_id, "kind": "preference"}, 
   ttl_days=90); return "ok"    #3

agent = Agent(
   name="Support",
   instructions=("Use ONLY retrieved context; cite chunk ids; if absent #4
   say 'I don't know'."),    #4
   tools=[retrieve, remember],
)
session = SQLiteSession("u42-chat")    #5

kb.ingest("docs/*.md", chunking="semantic",
         partition_by=["role", "tenant", "product", "version"])    #3
print(Runner.run_sync(
   agent, 
   "Can I use the beta API on v3.2? (tenant=acme)", 
   session=session))

注释

  • #1 将 RAG 作为智能体的一项核心能力
  • #2 选择并优化 Embedding
  • #3 保持数据新鲜,并按角色进行分区
  • #4 确保答案有依据,并让智能体使用检索到的知识
  • #5 同时使用会话记忆和长期记忆,以获得更好的效果

记忆不仅可以作为保存用户对话内容的基础。它还可以包含来自其他智能体的记忆与经验,甚至是其他对话中的信息。知识是提升智能体上下文能力的关键,而完整的记忆解决方案则能够帮助智能体随着时间推移不断学习和适应。

11.1.5 评估与反馈

无法衡量,就无法改进。在你所做的一切工作中,都应该记录并衡量智能体内部和外部发生的所有事情。这不仅有助于调试和故障处理,还能够帮助你优化和改进智能体。你可能不需要一开始就实现这里提到的所有建议,但至少应建立一个包含基础日志、测试以及简单护栏机制的最小化方案。而更完整的指导原则则适用于生产级系统的最佳实践。

在智能体中使用评估与反馈时,可以参考以下最佳实践:

  • 追踪一切。记录提示词、工具调用、Token 数量、延迟以及执行结果。使用 SDK 内置的追踪仪表板,或使用 Phoenix 获取全局视图。将日志接入你的可观测性(observability)体系。
  • 自动化评估。维护经过精心整理的测试集,并使用“LLM 作为裁判(LLM-as-judge)”进行检查;在每次修改提示词或模型后运行评估,以跟踪准确率、安全性和任务完成率。同样,Phoenix 可以处理所有这些评估工作。
  • 人类参与(Human-in-the-loop,HITL)。人类是最好的反馈来源。构建能够收集用户反馈的系统,对这些反馈进行深入分析,并持续优化。
  • 护栏与内容审核。在输入、输出以及高风险工具周围实施内容策略和模式验证。批判型智能体(critic agents)和护栏智能体(guardrail agents)可以帮助识别不良输出,并采取相应措施。
  • 像 DevOps 一样迭代(AIOps)。衡量并记录一切,进行观察,然后持续优化。不要忘记纳入工具调用以及智能体依赖的其他系统交互,例如数据库、索引和其他相关组件。

图 11.2 展示了一个与智能体部署集成的简单而有效的评估与反馈系统。正如前面章节提到的,OpenAI Agents SDK 提供了自动追踪功能,但我们强烈建议使用像 Phoenix 这样更健壮、功能更完整的工具。

图 11.2 一个集成了最佳实践评估与反馈系统的智能体部署架构

图的中心是一个评估与安全网关(eval and safety gate)机制,用于检查所有智能体输出。正如前面章节所介绍的,这些机制可以作为由辅助智能体驱动的护栏系统,为主智能体提供纠正性反馈和批判性评估。健壮的评估/反馈系统对于生产环境中的智能体构建、调试、维护和优化都具有重要作用。

11.2 构建客户支持智能体的技巧

现在,我们将开始探讨当今最常见的智能体模式的具体实践技巧,首先从客户支持智能体(Customer Support Agent)开始。这类智能体通常用于协助客户解决支持类问题,但也可以很容易地扩展到面向内部员工的支持场景。如果设计得当,这些智能体能够通过工单分流、回答基础问题以及升级关键事件,来增强昂贵的客户支持团队的工作能力。

对于这种类型的智能体,其五个层次分别对应如下:角色设定(persona)= 支持角色;工具/行动(tools/action)= API、查询与升级;推理/规划(reasoning/planning)= 分流逻辑与 HITL 升级;知识/记忆(knowledge/memory)= 基于手册的 RAG;评估/反馈(eval/feedback)= 日志与 HITL。

支持型智能体需要基于事实的回答、安全的操作,以及优雅的升级机制。这些智能体通常处于客户支持、反馈和投诉的第一线。因此,你应该按照以下指导原则,使它们具备鲁棒性、专业性和帮助性:

  • 缩小职责范围。从有限的意图集合开始(例如订单、退货、状态查询),然后逐步扩展领域。保持智能体轻量且专注,这样它们响应更快,也更不容易出现错误或幻觉。
  • 让每个答案都有依据。在产品手册、政策文档和发布说明之上添加 RAG 工具。在提示词中加入“仅使用提供的上下文(use only the provided context)”这一指令。但更进一步,可以使用 grounding agent(事实校验智能体)来确保所有基于上下文的回答始终有据可依。
  • 将 HITL 作为功能而不是补丁。提供一个 escalate_to_human 工具,用于处理复杂、愤怒用户或大额退款等场景。提供机制,让客户能够直接反馈智能体是否有效解决了他们的问题。
  • 身份与访问控制。在暴露账户数据之前验证用户身份;工具权限遵循最小权限原则。将用户角色及其对应的工具权限授予智能体,以实现访问控制。
  • 提升韧性。实现重试、备用模型/工具、超时机制以及友好的失败提示。一个健壮的智能体能够让用户感受到系统已经充分考虑并处理了他们的问题,从而提升信任感。
  • 缓存与速率限制。缓存热门问题的答案(例如“退款政策”),并限制外部 API 的调用频率,以控制成本并提高可靠性。合理使用缓存,避免“把所有东西都缓存起来”的思维方式。
  • 透明化追踪。保留每次会话的追踪记录,以便审计智能体的决策过程,包括执行过的搜索以及引用的来源。这可能意味着从 Phoenix 中提取追踪信息,或者为了支持、审计和数据采集目的而进行额外存储。

清单 11.5 展示了一个简单的分流/升级智能体,它可以作为客户支持系统的第一接触点。该智能体负责判断用户的问题或陈述是否需要升级给人工处理,还是可以由检索型智能体直接回答。

清单 11.5 06_support_agent_tips.py
from agents import Agent, function_tool, ModelSettings

@function_tool    #1
def escalate_to_human(ticket_id: str, reason: str) -> str:
   """Escalate this conversation to a human. Use for angry users, large refunds, or unclear policy."""    
   return f"Escalated ticket {ticket_id}: {reason}"    #2

retrieval_agent = Agent(... )  #3

support = Agent(
   name="Triage Support Agent",
   instructions=(
       "Verify identity before account actions. Cite policies. "
       "If unsure or user is upset, call escalate_to_human."
       "Pass on complex queries to retrieval_agent."    #4
   ),
   tools=[escalate_to_human, retrieval_agent.as_tool()],  
   model_settings=ModelSettings(tool_choice="auto")
)

注释

  • #1 一个用于升级到人工支持的工具
  • #2 在工具内部,升级流程会按照你的系统规则进行路由。
  • #3 可以使用一个检索型智能体(未展示)基于上下文回答问题。
  • #4 智能体之间通过工具调用进行交互。

在构建分流(triage)智能体时,你通常会优先关注交接(handoff)的决策选项,然后围绕这些选项来设计提示词指令。清晰性至关重要,这样智能体才能理解何时应选择哪一条路径。

图 11.3 展示了一个标准客户支持智能体/智能体系统的实现方式。正如你所看到的,我们所说的“一个智能体”,实际上是由多个智能体组成——在这个例子中共有六个智能体,它们分别承担不同职责并协同工作。

图 11.3 一个完整的客户支持智能体系统工作流

在标准的客户支持流程中,当用户提出问题后,一个分流智能体会判断该问题是否能够被解决,还是需要转交给人工处理。如果系统能够协助用户,则流程会进入检索型智能体。该智能体会检索与用户问题相关的信息,并生成评论、摘要或对原始问题的回答。随后,这些输出会交由一个 grounding agent(事实校验智能体)进行检查,以验证输出内容是否完全建立在所提供的上下文之上。

事实校验智能体还可能判断是否需要执行某些操作;如果需要,它会调用一个受限范围的业务 API。当 API 被调用后,一个护栏智能体(guardrail agent)会验证并确认输出是否符合预期。最后,一个回答智能体(answer agent)会综合检索结果和 API 调用结果,并将其作为最终答案返回给用户。随后,用户可以根据答案质量和整体交互体验,通过点赞或点踩的方式向系统提供反馈。

11.3 构建 RAG 智能体系统的技巧

RAG 智能体通常是其他智能体系统的基础。正如上一节所看到的,RAG 智能体可以嵌入到更大的智能体系统中,例如客户支持智能体。实际上,很少会看到一个单独的 RAG 智能体直接向用户输出结果。

检索(Retrieval)是引擎,而智能体控制(agentic control)则负责围绕它提供路由、批判和工具调用能力。但与客户支持示例类似,一个 RAG 智能体系统同样由多个智能体组成,每个智能体在整个 RAG 工作流中承担特定职责。以下建议可以帮助你设计这类智能体:

  • 仅在必要时使用智能体。如果一次性的 RAG 调用就能完成任务,那么就不要引入编排器(orchestrator)。只有在需要路由(分支检索)、诊断或执行操作时,再增加智能体。重点是只保留对整体工作流真正必要的智能体。
  • 设计模块化智能体。推荐的结构是:分流(路由)智能体(triage/router)→ 检索智能体(retriever)→ 回答智能体(answerer);如有需要,还可以增加一个用于纠正性检索(CRAG,Corrective Retrieval-Augmented Generation)的批判智能体(critic)。
  • 优化检索。使用 ANN 索引、元数据过滤、领域优化 Embedding 和重排序(reranking)。同时加入混合检索能力,以支持更多样化的搜索方式。
  • 坚持 Grounding 原则。强制要求“仅使用上下文”,并要求附带引用或来源片段。引入专门的事实校验智能体来评估答案与上下文的一致性。
  • 可观测性与评估。记录检索召回率、答案准确率,以及系统本应回答“我不知道”的场景。追踪检索调用可以帮助发现数据搜索流程中的痛点。在绝大多数情况下,你都希望智能体系统回答“我不知道”,而不是自信地给出错误答案。

清单 11.6 展示了一个用于检索和答案生成的单智能体示例。在实际应用中,这样的智能体通常会嵌入到更大的智能体系统中。前面的章节已经介绍过带有事实校验智能体的 RAG 模式,这里的示例则是对构建此类智能体所需关键实践技巧的一次简要回顾。

清单 11.6 07_rag_agent_tips.py
from agents import Agent, function_tool

@function_tool
def retrieve(query: str, corpus: str = "product_docs", top_k: int = 5) -> list[dict]:
   """Return top-k passages with metadata from the selected index."""
   # ... ANN + metadata filtering ...
   return [{"text": "...", "source": "KB-123", "section": "Refunds"}]

answerer = Agent(
   name="RAG-Answerer",
   instructions=(
       "Use ONLY the passages provided via `retrieve`. "    #1
       "If none answer the question, reply: "
       "'I don't know based on the available documents.' "    #2
       "Return a short summary and cite sources."    #3
   ),
   tools=[retrieve],
)

注释

  • #1 将答案严格限制在检索到的上下文范围内
  • #2 不要让你的智能体害怕承认自己不知道某件事。
  • #3 始终确保答案附带引用的上下文来源。

图 11.4 展示了一个更复杂的 RAG 智能体系统,它能够查询多个上下文来源,以回答问题或完成搜索请求。

图 11.4 一个更高级的 RAG 智能体系统,它能够查询多个上下文来源,以回答问题或满足搜索请求

我们可以在上下文排序智能体(context-ranking agent)之前放置一个事实校验智能体(grounding agent),用于判断检索到的文档是否足以回答用户的问题。如果这些文档无法充分回答问题,排序智能体会将查询传递给一个查询优化智能体(refinement agent)。查询优化智能体会修改查询内容,并重新发起文档检索。为了防止这一循环无限执行,我们通常会设置一个循环计数器作为退出条件,不过图中并未展示这一部分。

当然,你还可以采用许多其他模式来构建 RAG 智能体系统。只需牢记这里提供的关键实践建议,它们能够帮助你更好地设计自己的智能体化 RAG 工作流。

11.4 构建深度研究(Deep Research)智能体系统的技巧

深度研究智能体结合了开放式 Web 探索、迭代规划以及来自多个来源的信息综合。几乎所有主流 LLM 提供商(ChatGPT、Claude 和 Gemini)都支持这类智能体。如果你希望深度研究智能体能够搜索知识库、内部系统数据库和文档存储等其他信息源,我们通常建议构建一个内部版本。

构建内部深度研究智能体可能非常复杂,并且需要开发一个由多个专业智能体组成的团队。只有当你确定更简单的智能体系统无法解决复杂问题时,才应考虑使用这类智能体。以下是我们在开发此类智能体系统时总结的一些通用最佳实践:

  • 双层编排——由一个研究规划器(research planner,负责“大脑”)来调度多个无状态工作者(workers,例如 Web 搜索器、信息提取器、分析器和摘要器)。尽量让工作者保持工具化(tool-like),不要让它们拥有长期记忆。
  • 工具策略控制——强制规划器调用检索工具和 Web 工具来获取事实。禁止在没有来源支撑的情况下直接回答事实性声明。
  • 自我批判与事实核查——在最终输出之前增加一个批判步骤,用于检查覆盖范围、发现矛盾点以及识别缺失的视角。
  • 流式用户体验——实时输出阶段性发现和不断演化的提纲。这不仅可以改善用户对延迟的感知,还能够支持“检查点(checkpoint)”审批。输出内容既可以是简单的状态消息(例如“正在搜索”或“正在思考”),也可以是智能体当前产生的推理过程。
  • 缓存与预算——缓存重复查询,并为每次运行设置 Token 预算及告警机制。同时限制 Web/API 调用频率。当智能体反复执行相同查询时,缓存会非常有价值,但它也可能影响搜索结果的新鲜度。因此,务必定期清理缓存,以避免返回过时数据。

清单 11.7 提供了一个简单示例,其中包含一个智能体“大脑”(研究规划器)以及多个“手”(工作者智能体)。在代码开头,我们创建了若干用于搜索、提取和分析搜索结果的工具。这些既可以是直接工具,也可以是将智能体封装为工具的包装器(wrapper)。判断某个组件应该被设计为工具还是智能体,关键取决于它是否需要做出决策。需要决策能力的“工具”,本质上就是使用工具的智能体。

清单 11.7 08_deep_research_agent_tips.py
import asyncio

from agents import Agent, Runner
from openai.types.responses import ResponseTextDeltaEvent

web_search_tool = ...   #1
extract_tool = ...   #2
analyze_tool = ...   #2

critic = Agent(name="Critic", instructions="Check completeness, bias, contradictions.")
writer = Agent(
   name="Writer", instructions="Synthesize into a concise brief with citations."
)    #3

researcher = Agent(
   name="ResearchPlanner",
   instructions=(
       "Break down the research goal. Always use web/doc tools for facts. "
       "Track sources and avoid unsupported claims."
   ),
   tools=[
       web_search_tool,
       extract_tool,
       analyze_tool,
       critic.as_tool(),
       writer.as_tool(),
   ],
)

# Stream results to your UI
stream = Runner.run_streamed(
   researcher, "Map the 3 best open RAG rerankers and compare."
)    #D

async def main():
   result = Runner.run_streamed(
       researcher, input="Map the 3 best open RAG rerankers and compare."
   )
   async for event in result.stream_events():    #4
       if event.type == "raw_response_event" and isinstance(
           event.data, ResponseTextDeltaEvent
       ):
           print(event.data.delta, end="", flush=True)

if __name__ == "__main__":
   asyncio.run(main())

注释

  • #1 这里可以是一个智能体、WebSearchTool(),或者你自己的 @function_tool
  • #2 它们既可以是工具,也可以是专业化智能体,具体取决于实现细节以及是否需要决策能力。
  • #3 首先使用批判智能体(critic)和写作智能体(writer)检查上下文是否能够回答问题,然后再生成答案。
  • #4 在“大脑”(researcher)智能体指导和完成任务的过程中,以流式方式输出其执行状态。

当研究智能体(research agent)搜索并找到足够的上下文之后,它会将结果交给一个批判智能体(critic agent)。随后,批判智能体会判断检索到的上下文是否足以回答最初的问题。如果批判智能体认为可以回答,它就会将搜索上下文传递给写作智能体(writer agent)生成最终答案;如果批判智能体认为仍然缺少上下文,则会将搜索任务返回给研究智能体。

图 11.5 展示了前述代码示例中的完整深度研究智能体模式。在该模式中,研究规划智能体(research planner agent)充当“大脑”,负责调度多个“手”(Web 搜索智能体、信息提取智能体和分析智能体)。当这些工作智能体找到相关上下文后,会将结果返回给规划器。随后,规划器将结果交给批判智能体,由后者判断当前信息是否足以回答原始问题。如果信息不足,批判智能体会将结果退回给规划器,并请求获取更多上下文。

图 11.5 深度研究智能体系统的实现

同样地,如果批判智能体认为已经收集到足够的信息,它会将结果传递给最终报告智能体(final report agent)。该最终智能体负责构建答案或报告,并确保此前返回的所有参考资料都能够在最终输出中得到正确引用。要求智能体规范地引用来源,也能够为答案提供直接的事实依据,从而与前面检索到的上下文形成有效关联。

总结

  • 将智能体设计视为五个堆叠的层次:角色设定(persona)、工具与行动(tools and actions)、推理与规划(reasoning and planning)、知识与记忆(knowledge and memory)以及评估与反馈(evaluation and feedback)。分别优化每一层,然后再进行集成。
  • 将角色设定编写成 API 契约,而不是一段描述性文字:明确角色、边界,并提供一个显式的“我不知道”退出机制。
  • 保持范围狭窄(单一职责),以获得更高的准确性,并更容易进行测试和成本控制。
  • 优先使用结构化、类型化输出(Pydantic/dataclass JSON),以消除解析脆弱性问题。
  • 在运行时使用动态指令(注入日期、组织、用户信息),避免使用过时事实。
  • 构建单一职责的工具,并提供清晰的名称、文档字符串和类型化参数。模型会通过这些模式(schema)学习如何使用工具。
  • 控制工具的调用时机(例如 tool_choice、并行工具调用);当一次调用已经足够时,应尽早停止。
  • 在适当情况下使用预构建或托管工具,但要有选择性。
  • 为失败情况做好规划:超时、重试、优雅的错误负载(error payload)以及降级方案。
  • 对于复杂任务,采用 ReAct 或思维链(Chain-of-Thought)模式。
  • 先规划,再执行:在行动之前先拟定一个简短的检查清单;可以考虑引入轻量级的“思考”服务器或步骤。
  • 限制迭代次数,以防止失控循环。在达到预算或轮次限制时,发出 needs_followup 信号。
  • 在最终输出前增加一次快速自我审查,以捕获明显错误。
  • 将 RAG 视为一项核心能力:使用知识库上的检索工具,而不是将文档全部塞入提示词中。
  • 将会话记忆(短期线程上下文)与长期记忆(向量数据库/数据库)分离,并积极进行裁剪。
  • 优化检索:使用 ANN 索引、按领域分片、通过元数据过滤,并考虑使用领域专用或多语言 Embedding。
  • 使用混合搜索(语义搜索 + 关键词搜索 + 层级/图搜索)来提升召回率和准确率。
  • 按语义进行文本切分,严格基于检索到的上下文生成答案,并能够坦然地回答“我不知道”。
  • 保持数据新鲜,并按照角色/租户进行分区,以实施访问边界控制。
  • 追踪一切:提示词、工具调用、Token 数量、延迟和执行结果;将这些数据接入你的可观测性体系。
  • 自动化评估(LLM-as-judge + 精选测试集),并在每次提示词或模型发生变化时执行评估。
  • 引入 HITL(Human-in-the-loop):收集用户反馈,并对复杂案例进行人工审查。
  • 在输入、工具调用和输出环节添加护栏与内容审核机制;并执行模式(schema)验证。
  • 践行 AIOps:衡量 → 观察 → 迭代,同时纳入智能体依赖的下游系统(索引、API 等)。
  • 优先缩小职责范围(订单、退货、状态查询),然后再逐步扩展。
  • 通过 RAG 为每个答案提供依据;必要时增加专门的事实校验智能体(grounding agent)。
  • 提供 HITL 升级机制(escalate_to_human),并收集用户反馈。
  • 在公开账户数据或调用业务 API 之前,实施身份验证和最小权限访问控制。
  • 构建韧性:实现重试、降级、超时机制;缓存热门答案,并实施速率限制。
  • 为每次会话保留透明的追踪记录,以满足审计需求。
  • 现实世界中的“支持智能体”通常是多个协作智能体的组合(分流 → 检索 → 事实校验 → 行动/护栏 → 回答)。
  • 仅在必要时引入智能体编排器;如果一次性 RAG 已经足够,就保持简单。
  • 优先采用模块化角色:路由/分流 → 检索 → 回答;必要时增加批判智能体(CRAG)进行纠正性检索。
  • 优化检索(ANN、过滤器、重排序器、HyDE),并通过引用机制强制执行事实依据。
  • 监控召回率、准确率以及“本应回答‘我不知道’”的案例,并通过评估机制形成闭环。
  • 对于高级系统,可支持多个语料库、排序/优化循环以及受限重试机制。
  • 使用双层编排:由规划器(大脑)将任务委派给无状态工作智能体(双手),负责 Web 搜索、信息提取、分析和写作。
  • 实施工具使用策略:没有引用来源就不能提出事实性声明;所有事实必须来自检索工具或文档工具。
  • 在生成最终报告之前增加自我批判/事实核查步骤(critic pass)。
  • 提供流式用户体验,随着任务推进实时展示阶段性发现和提纲。
  • 管理缓存和预算:去重重复查询、限制 Token 和调用次数,并定期清理缓存以避免数据陈旧。
  • 生产级智能体系统来源于:严格定义的角色设定、职责明确的工具、受控的推理过程、规范的检索机制以及持续不断的度量。这些元素最终会被组装成模块化、可审计、具备清晰升级路径且输出有据可依的工作流。