agent框架搭建全拆解:从框架搭建要素到完整流程的超详细解读,包括智能体定义、指令配置、工具tool集成、上下文管理、tools参数、会话管理(Session/State)、记忆存储(Memory)
大家好,这篇文章笔者将围绕agent框架的搭建核心要素进行详细展开^⌯𖥦⌯^੭ 🐋
一、核心智能体类别
❄️LlmAgent(通常也简称为 Agent)
它利用 LLM 来解释指令和上下文,动态决定如何推进、使用哪些工具(如果有)、或是否将控制权转移给其他智能体
💦定义智能体的身份和目的
首先,你需要确定这个智能体是什么以及它用来做什么,具体的创建对象身份是什么以及目标解决什么问题
❄️name(必填): 每个智能体都需要一个唯一的字符串标识符,选择一个能反映智能体功能的描述性名称(例如,customer_support_router,billing_inquiry_agent)
🫧description(可选,多智能体推荐): 提供一个简洁的智能体能力摘要,后面可在introduction、information等参数定义中对该智能体进行详细描述,这个描述主要由其他 LLM 智能体用来确定是否应该将任务路由到这个智能体,例如这样:
💦model(必填): 指定将为此智能体的推理提供支持的底层 LLM。这是一个字符串标识符,如 "gemini-2.0-flash",模型的选择会影响智能体的能力、成本和性能
capital_agent = LlmAgent(
model="gemini-2.0-flash",
name="capital_agent",
description="回答用户关于某个国家首都的问题。"
# instruction 和 tools 会在后面添加
)
二、引导智能体:指令(instruction)
🫧instruction 参数可以说是塑造 LlmAgent 行为最关键的部分。它是一个字符串(或返回字符串的函数),告诉智能体:
- 其核心任务或目标。
- 其个性或角色(例如,"你是一个乐于助人的助手","你是一个机智的海盗")
- 对其行为的约束(例如,"只回答关于 X 的问题","永远不要透露 Y")
- 如何以及何时使用其
tools。你应该解释每个工具的用途以及应该在什么情况下调用它,补充工具本身的任何描述 - 其输出的期望格式(例如,"以 JSON 形式回应","提供一个项目符号列表")
🫧有效指令的技巧:
- 清晰明确: 避免含糊不清。清楚地说明期望的行动和结果。
- 使用 Markdown: 使用标题、列表等提高复杂指令的可读性。
- 提供示例(少样本): 对于复杂任务或特定输出格式,直接在指令中包含示例。
- 指导工具使用: 不仅仅是列出工具;解释智能体何时和为什么应该使用它们
LlmAgent capitalAgent =
LlmAgent.builder()
.model("gemini-2.0-flash")
.name("capital_agent")
.description("回答用户关于某个国家首都的问题。")
.instruction(
"""
你是一个提供国家首都信息的智能体。
当用户询问某个国家的首都时:
1. 从用户的提问中识别国家名称。
2. 使用 `get_capital_city` 工具查找首都。
3. 明确地回复用户,说明该国家的首都。
示例提问:\"What's the capital of {country}?\"
示例回复:\"The capital of France is Paris.\"
""")
.build();
三、为智能体装备:工具(tools)
🫧tools(可选): 工具通常是一个模块化的代码组件——如 Python函数、类方法,甚至是另一个专用智能体——用于执行一个独立且预定义的任务,可以直接调用,也可以根据自己想要实现的功能自定义创建,提供一个智能体可用的工具列表,LLM 会根据函数/工具名称、描述(来自文档字符串或 description 字段)和参数模式,结合对话和指令,决定调用哪个工具,列表中的每一项可以是:
🫧🫧工具执行特定的、由开发者定义的逻辑,它们不像智能体的核心大型语言模型(LLM)那样具有自己独立的推理能力,LLM 会推理使用哪个工具、何时使用以及使用什么输入,但工具本身只是执行其指定的功能
- 一个原生函数或方法(在 Python 中自动包装为
FunctionTool) - 继承自
BaseTool的类的实例
# 定义一个工具函数
def get_capital_city(country: str) -> str:
"""检索指定国家的首都。"""
capitals = {"france": "Paris", "japan": "Tokyo", "canada": "Ottawa"}
return capitals.get(country.lower(), f"Sorry, I don't know the capital of {country}.")
# 将工具添加到智能体
capital_agent = LlmAgent(
model="gemini-2.0-flash",
name="capital_agent",
description="回答用户关于某个国家首都的问题。",
instruction="""你是一个提供国家首都信息的智能体...(前述指令文本)""",
tools=[get_capital_city] # 直接传递函数
)
智能体如何使用工具
🌌智能体通过通常涉及函数调用的机制动态地利用工具。该过程通常遵循以下步骤:
工具类型
函数工具: 由你创建的工具,根据你的特定应用需求量身定制。
- 推理: 智能体的 LLM 分析其系统指令、对话历史和用户请求。
- 选择: 基于分析,LLM 根据智能体可用的工具和描述每个工具的文档字符串决定执行哪个工具(如果有的话)。
- 调用: LLM 生成所选工具所需的参数(输入)并触发其执行。
- 观察: 智能体接收工具返回的输出(结果)。
- 完成: 智能体将工具的输出纳入其持续的推理过程中,以制定下一个响应、决定后续步骤或确定是否已达成目标
四、对话上下文介绍:Session、State 和 Memory
像人类一样,智能体需要记住对话历史:已经说过和做过什么,以保持连贯性并避免重复
可以将你与智能体的不同对话实例视为独立的对话线程,它们可能会利用长期知识
🌌Session:当前对话线程1.表示用户与你的智能体系统之间单次、持续的交互
2.包含该特定交互期间,智能体采取的消息和动作(称为
Events)的时间顺序序列。3.一个
Session还可以保存仅在本次对话期间相关的临时数据(State)🫧State(session.state):当前对话中的数据1.存储在特定
Session内的数据。2.用于管理仅与当前、活跃对话线程相关的信息(例如,本次对话中的购物车商品,本 Session 中提到的用户偏好)
❄️Memory:可检索的跨 Session 信息1.表示可能跨越多个过去 Session或包含外部数据源的信息存储。
2.它作为一个知识库,智能体可以检索以回忆超出当前对话的信息或上下文
五、管理上下文服务(service)
🌌SessionService:管理不同的对话线程(Session对象)负责生命周期管理:创建、检索、更新(追加
Events、修改State)和删除单个Session🫧MemoryService:管理长期知识存储(Memory)1.负责将信息(通常来自已完成的
Session)导入长期存储。2.提供基于查询检索已存储知识的方法
总结:
Session&State:关注当前交互——单次、活跃对话的历史和数据,主要由SessionService管理。Memory:关注过去和外部信息——一个可检索的归档,可能跨越多个对话。由
MemoryService管理六、回调:观察、自定义和控制智能体行为
🫧回调是自定义的标准函数,在特定的预定义点观察、自定义甚至控制智能体的行为
检查点(观察智能体在执行前后的输出是否符合预期):
在智能体开始处理请求的主要工作之前,以及完成之后: 当你要求智能体做某事时(例如,回答问题),它会运行其内部逻辑来推断响应
智能体前置回调在特定请求的主要工作开始之前执行智能体后置回调在智能体完成该请求的所有步骤并准备好最终结果之后执行,但就在结果返回之前
七、工具调用与回调函数的关系

- 函数/方法: 在你的代码中定义标准同步函数或方法(例如,Python def)。
- 智能体作为工具: 使用另一个可能专门的智能体作为父智能体的工具,即将agent当作tool用,这里的agent作为子agent,执行某个流程中的分支任务,可以无限嵌套
- 长时运行函数工具: 支持执行异步操作或需要大量时间完成的工具。
- 内置工具: 框架提供的可直接使用的常见任务工具。 示例:Google 搜索、代码执行、检索增强生成(RAG)。
- 第三方工具: 无缝集成来自流行外部库的工具。 示例:LangChain 工具、CrewAI 工具
🫧1. 工具调用作为触发点
- 当智能体决定调用工具时,会生成一个工具调用请求(包含参数、工具标识等)
- 同步工具:直接阻塞等待结果(如本地数学计算)
- 异步工具:立即返回控制权,通过回调函数处理结果(如API请求)
❄️2.回调函数作为结果处理器
想象你(智能体)想吃披萨,但自己不会做,这时候你需要:
- 本质是智能体暴露给工具的事件监听器
- 关键处理逻辑:结果验证(检查数据格式/错误码)、状态更新(记录工具执行结果)、决策续传(触发后续推理步骤)
1. 工具调用 = 打电话订外卖
你:拿起手机(调用工具)给披萨店下单
披萨店(工具):收到订单开始制作
2. 回调函数 = 外卖小哥敲门
你下单时说的话:
-
"做好后打我电话,号码是138xxxx(回调函数地址)"
-
披萨店动作:做好披萨 → 用你留的号码打电话(触发回调) → 你开门取餐
- 关键点:你不用傻等(非阻塞)、你知道什么时候该处理披萨(回调时机)
- 如果外卖送错了(错误结果),你可以拒收(错误回调)
- 同步模式:你一直举着电话等,直到小哥说"做好了"(卡住啥也干不了)
- 异步模式:你说"做好了回电话给我"(挂电话继续刷剧)
八、运行智能体
运行的核心是一个事件循环。这个循环促进了
Runner组件与你定义的"执行逻辑"(包括你的智能体、它们进行的 LLM 调用、回调和工具)之间的来回通信❄️Runner 的角色(协调者)
🫧Runner作为单个用户调用的中央协调器。其在循环中的责任包括:启动: 接收终端用户的查询(
new_message),通常通过SessionService将其追加到会话历史。启动事件流: 通过调用主智能体的执行方法(如agent_to_run.run_async(...))开始事件生成过程。接收与处理: 等待智能体逻辑
yield或emit一个Event。收到事件后,Runner 立即处理它。这包括:-
向上游产出: 将处理后的事件转发(如发送到应用或 UI 渲染)。
迭代: 通知智能体逻辑已完成当前事件的处理,允许其恢复并生成下一个事件
九、核心组件总结
❄️1.执行者Runer
角色: 单次用户查询(
run_async)的主入口和协调者。功能: 管理整个事件循环,接收执行逻辑产出的事件,与 Services 协作处理并提交事件操作,并将处理后的事件转发到上游(如 UI)。它本质上根据产出的事件逐轮驱动对话
🐋2.执行逻辑组件
Agent(BaseAgent,LlmAgent等):主要逻辑单元,处理信息并决定操作。实现_run_async_impl方法,产出事件Tools(BaseTool,FunctionTool,AgentTool等):智能体(通常是LlmAgent)用于与外部世界交互或执行特定任务的外部函数或功能,执行并返回结果,然后这些结果被包装在事件中Callbacks(函数):附加到智能体的用户定义函数(例如,before_agent_callback,after_model_callback),挂钩到执行流程中的特定点,可能修改行为或状态,其效果被捕获在事件中🌊3.事件
Event角色: 在
Runner和执行逻辑之间传递的消息功能: 表示一个原子性事件(用户输入、智能体文本、工具调用/结果、状态变更请求、控制信号)。它携带事件内容和预期副作用(如
actions里的state_delta)🐋4.服务
Services角色: 负责管理持久性或共享资源的后端组件。主要由
Runner在事件处理期间使用组件:
SessionService(BaseSessionService,InMemorySessionService等):管理Session对象,包括保存/加载它们,将state_delta应用到会话状态,以及将事件追加到事件历史ArtifactService(BaseArtifactService,InMemoryArtifactService,GcsArtifactService等):管理二进制制品(Artifacts)数据的存储和检索。虽然save_artifact是通过执行逻辑期间的上下文调用的,但事件中的artifact_delta确认了 Runner/SessionService 的操作MemoryService(BaseMemoryService等):(可选)管理用户跨会话的长期语义记忆🐚5.会话
Session角色: 保存单次会话用户与应用交互状态和历史的数据容器
功能: 存储当前
state字典、所有历史events(事件历史)和相关制品引用。它是交互的主记录,由SessionService管理❄️6.调用/触发
Invocation角色: 从
Runner接收单个用户查询到智能体逻辑为该查询产出事件的全过程功能: 一次调用可能涉及多个智能体运行(如智能体转移或
AgentTool)、多个 LLM 调用、工具执行和回调执行,所有这些都通过InvocationContext中的单个invocation_id联系在一起十、完整流程拆解
1.用户输入: 用户发送查询(如"法国的首都是哪里?")
2.Runner 启动:
Runner.run_async开始。它与SessionService交互加载相关Session,并将用户查询作为第一个Event添加到会话历史。准备好InvocationContext(ctx)3.智能体执行:
Runner调用根智能体(如LlmAgent)的agent.run_async(ctx)4.LLM 调用(示例):Agent_Llm判断需要信息,可能通过调用工具。它准备 LLM 请求。假设 LLM 决定调用MyTool5.产出 FunctionCall 事件:Agent_Llm收到 LLM 的FunctionCall响应,将其包装为Event(author='Agent_Llm', content=Content(parts=[Part(function_call=...)])),并yield或emit该事件6.智能体暂停:Agent_Llm执行在yield后立即暂停7.Runner 处理:
Runner接收 FunctionCall 事件,传递给SessionService记录到历史。然后 Runner 将事件产出到上游(如用户或应用)8.智能体恢复: Runner 通知事件已处理,
Agent_Llm恢复执行9.工具执行:
Agent_Llm内部流程继续执行请求的MyTool,调用tool.run_async10.工具返回结果:MyTool执行并返回结果(如{'result': 'Paris'})11.产出 FunctionResponse 事件: 智能体(Agent_Llm)将工具结果包装为包含FunctionResponse部分的Event(如Event(author='Agent_Llm', content=Content(role='user', parts=[Part(function_response=...)])))。如果工具修改了状态(state_delta)或保存了制品(artifact_delta),该事件也会包含这些操作。智能体yield该事件12.智能体暂停:
Agent_Llm再次暂停13.Runner 处理: Runner 接收 FunctionResponse 事件,传递给
SessionService应用state_delta/artifact_delta并添加到历史,Runner 产出事件到上游14.智能体恢复:
Agent_Llm恢复,知道工具结果和状态变更已提交15.最终 LLM 调用(示例):
Agent_Llm将工具结果返回给 LLM 生成自然语言响应16.产出最终文本事件:
Agent_Llm收到 LLM 的最终文本,将其包装为Event(author='Agent_Llm', content=Content(parts=[Part(text=...)]))并yield17.智能体暂停:Agent_Llm暂停18.Runner 处理: Runner 接收最终文本事件,传递给
SessionService记录到历史,并产出到上游(如用户)。这通常被标记为is_final_response19.智能体恢复并结束:Agent_Llm恢复。完成本次调用后,其run_async生成器结束20.Runner 完成: Runner 发现智能体生成器已耗尽,结束本次调用的循环
十一、致谢
谢谢大家的阅读,本次agent框架总结到这里就结束啦~,还有很多不足之处,欢迎大家在评论区指出^⌯𖥦⌯^੭ 🐋

-
更多推荐
所有评论(0)