3.3 工具的定义、注册与调用


3.3 工具的定义、注册与调用

工具是 Agent 伸向世界的手。没有工具,Agent 只能在语言空间里打转;有了工具,它能查数据、算指标、调接口。这一节把"定义→注册→被调用→结果回写"的完整回路跑通,并配一张时序图。

3.3 工具(Tool/Function Call)的定义、注册与调用

第一步:定义函数并写清楚签名

框架靠函数的类型标注和 docstring 生成给模型的工具描述。签名越清楚,模型越不容易传错参。

def get_weather(city: str, date: str) -> str: """查询指定城市指定日期的天气。 Args: city: 城市名,如 北京 date: 日期,格式 YYYY-MM-DD """ # 这里真实场景会调天气接口,示例返回占位 return f"{city} 在 {date} 晴,气温 22 度"

第二步:注册到 Agent

把函数放进 functions 列表,框架会自动把它编进给模型的工具清单。模型看到清单后,会在合适时生成调用。

from autogen import AssistantAgent, UserProxyAgent import os cfg = {"model": "gpt-4o-mini", "api_key": os.environ.get("OPENAI_API_KEY")} assistant = AssistantAgent("assistant", llm_config=cfg, functions=[get_weather]) executor = UserProxyAgent("executor", human_input_mode="NEVER", code_execution_config={"use_docker": False})

第三步:让调用真正执行

只有 UserProxyAgent(且 human_input_mode=NEVER、开了执行)会真正运行函数。AssistantAgent 只"建议调用",不落地。下面跑一次看回路。

chat = assistant.initiate_chat(executor, message="北京明天天气怎样?", max_turns=2) for m in chat.messages: who = m.get("name") or m.get("role") body = str(m.get("content"))[:70].replace("\n"," ") print(f"{who:>10} | {body}") # 你会看到 assistant 发出带 get_weather 参数的调用,executor 发出执行结果

常见坑

  • 函数没写类型标注 → 模型不知道参数类型,容易传错。
  • 只在 Assistant 注册、没配执行 Agent → 调用永远不落地,对话停在"建议"。
  • 函数抛异常没捕获 → 整个对话断掉,建议函数内 try/except 返回错误信息。
def safe_div(a: float, b: float) -> str: try: return str(a / b) except ZeroDivisionError: return "错误:除数不能为零" # 把异常转成字符串返回,对话能继续而不是崩溃

工具设计的深层逻辑

工具回路跑通只是第一步,真正决定 Agent 会不会用对工具,藏在几个设计细节里。第一,工具名和描述就是模型做选择的唯一依据——它看不到你的函数实现,只看框架从签名和 docstring 生成的那段描述。所以 get_user_profile 比 gup 好,描述里写明"参数 city 为城市名"比只写"参数"好。这点和给人写接口文档同理:文档烂,调用方就乱传。

第二,工具粒度要"单一职责"。一个工具又查天气又下单又发邮件,模型选起来容易错配,一旦执行就是跨职责的副作用。把它拆成查天气、下单、发邮件三个,模型按意图选一个,行为可预测得多。这可以用建筑的"每个房间只干一件事"来理解:厨房不该兼当卧室,工具也不该兼当不相干的活。

第三,工具的执行结果要"自解释"。函数返回一句人能读的话(如"北京 明天 晴 22度"),比返回一堆嵌套 json 更利于模型接着对话。如果必须返回结构化数据,在 docstring 里说清字段含义,并在异常时返回错误字符串而不是抛异常——前者让对话继续,后者让对话断掉(前面 safe_div 就是这个思路)。

设计点 做好 做差
工具名 get_user_profile gup
描述 写明参数含义与返回 只写函数名
粒度 一工具一职责 一工具包多活
返回值 自解释文本或说明过的结构 裸嵌套 json
异常 返回错误字符串 直接抛出

工具不生效时的排查清单

工具回路看着顺,真不生效时新手常懵。按这个清单查最快。第一,函数有没有真的注册进 functions 列表——漏写等于模型根本看不到这个工具,它自然不会调。第二,函数签名类型标注全不全——缺标注框架生成的工具描述就缺参数类型,模型易传错或干脆不调。第三,执行端是不是配了会跑函数的 Agent——只在 Assistant 注册、没配 UserProxyAgent(human_input_mode=NEVER 且开执行),调用永远停在"建议",对话不落地。第四,参数是否被模型理解——如果函数名叫 gup、描述含糊,模型可能压根没想到该用它,换成自解释的名字常立竿见影。第五,函数内有没有未捕获异常——一旦执行抛错,整段对话断掉,加 try/except 转字符串就能看到错误继续排。

这几条覆盖了九成"工具不动"的问题。核心心法:工具是 Agent 和现实之间的管线,管线任何一节松了,话说出去就变不成事。排工具问题要把"注册—描述—执行—兜底"四节当成一条链逐环验,而不是笼统归咎"模型不行"。

本节要点回顾

  • 定义函数要带类型和 docstring,框架据此生成工具描述。
  • 注册到 Assistant,执行靠 UserProxyAgent。
  • 函数内捕获异常,避免对话整体断裂。
  • 工具名/描述/粒度/返回值决定模型会不会用对,设计要当接口文档对待。
  • 工具不生效按"注册→描述→执行→参数→兜底"逐环排查。

⚠️ 函数能执行任意代码,生产环境必须沙箱化或人工确认,详见第五章安全小节。

💡 工具回路正是"对话即编排"接通现实的那根管线——Agent 说的话,借工具变成做的事。


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