2.4 自治工位:代理与工具


2.4 自治工位:代理与工具

本节摘要:代理把"人写死的调用顺序"换成"模型自己决定下一步用什么工具",核心是一个思考、行动、观察的循环。本节讲工具的封装规范、代理执行器的运转方式、常见代理类型选型,以及自治工位的失控风险与止损手段。

从固定工序到自治工位

2.3 节的传送带再灵活,工序顺序也是装配时写死的。但有一类任务天生没法预排工序:"查一下这位演员现任女友的年龄,再算它的 0.43 次方"——要先搜索、再取数、再计算,顺序只有模型看到中间结果才知道。这就是代理的用武之地:给它一柜子工具,让它自己决定拿哪件、什么时候拿。

这个"思考、行动、观察"的循环叫 ReAct 模式,是目前绝大多数代理的运转内核。注意它和普通链的本质区别:链的执行图是静态的,代理的执行图是运行时生成的。

工具:外协夹具的封装

工具就是"模型可以调用的函数",但必须按规范封装——模型只能看到名字、描述和参数说明,封装质量直接决定它会不会被正确选用:

from langchain_core.tools import tool # 装饰器封装:函数文档字符串就是给模型看的说明书 @tool def word_count(text: str) -> int: """统计一段文本的字数。当需要知道内容长度时使用。""" return len(text) print(word_count.name) # 输出:word_count print(word_count.description) # 输出:统计一段文本的字数。当需要知道内容长度时使用。 print(word_count.invoke({"text": "彩色袜子"})) # 输出:4

写好描述有三条军规:说清"什么时候用"而不只是"是什么";参数含义写进文档字符串;返回值保持简短——工具返回会被塞进上下文,啰嗦的工具会挤爆 token 预算。框架也内置了一批常用夹具:

from langchain.agents import load_tools from langchain_openai import OpenAI llm = OpenAI(temperature=0) # 内置工具按名字加载,llm-math 需要挂一个计算引擎 tools = load_tools(["llm-math"], llm=llm) print([t.name for t in tools]) # 输出:['calculator']

把工位转起来:执行器

光有工具柜还不是自治工位,要有一个执行器替模型真正去调用工具、把结果喂回去。新旧两代写法都过一遍:

from langchain.agents import initialize_agent, AgentType from langchain_openai import OpenAI llm = OpenAI(temperature=0) tools = load_tools(["llm-math"], llm=llm) # 旧式一键装配:类型选 zero-shot-react-description agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, # 打印每一步思考,调试期必开 ) print(agent.run("计算 3.5 的 2.8 次方是多少")) # verbose 输出(节选): # Thought: 我需要计算这个幂 # Action: calculator # Action Input: 3.5**2.8 # Observation: 20.92... # Final Answer: 约 20.92

新式写法把"代理"与"执行器"拆成两件,循环参数明着配,控制力更强:

from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.tools import tool @tool def get_word_length(word: str) -> int: """返回单词的字母数。""" return len(word) llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) tools = [get_word_length] # 工艺单里必须留工具调用与中间步骤的插槽 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个严谨的助手"), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ]) agent = create_tool_calling_agent(llm, tools, prompt) # 执行器:循环上限、单步耗时都在这里装配 executor = AgentExecutor(agent=agent, tools=tools, max_iterations=5, verbose=True) print(executor.invoke({"input": "单词 strawberry 有几个字母"})) # 输出示例:{'input': '单词 strawberry 有几个字母', 'output': '10'}

max_iterations 是自治工位的保险丝:模型陷入"再搜一次"死循环时,五次后强制熔断,返回中间结果。生产环境这个参数永远要设。

代理类型选型与止损

类型 特点 适用
工具调用型 依赖模型原生函数调用能力 新项目默认选它,最稳
Zero-shot ReAct 纯文本协议,兼容老模型 模型不支持函数调用时
结构化对话型 带对话记忆的代理 多轮协作场景
自定义循环 自己写 while 加工具分发 特殊控制流,3.1 节展开

⚠️ 两个高频翻车点:一是工具描述含糊,模型反复挑错工具——先改描述再怀疑模型;二是代理权限过大,删除、支付类工具直接交给自治循环——凡是不可逆操作,工具内部要加确认门槛或干跑模式。

💡 什么时候不用代理:如果工序顺序其实固定,只是分支多,用 2.3 节的路由链就够了。代理的灵活性是用延迟和不可预测性换来的,固定能解决的工序绝不安自治工位。

动手实验建议

把上面的执行器换上两个自定义工具(一个查汇率、一个查天气,先用假数据),问一个需要连续调用两者的问题,观察 verbose 日志里的思考链。你会亲眼看到"执行图是运行时生成的"这句话的含义——同一台执行器,问题不同,走出的工序序列完全不同。下一节解决另一个流向问题:多轮对话中,产线怎么记得上一轮说过什么。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U