数据截至 (上游 commit 101f0313b0dd)
工具系统:自定义工具 / MCP / HITL / 编排
30 秒导读: 模型只会"说话"——它能吐出一句"请调用
get_weather(city='东京')",但它自己碰不到任何真实函数。本章讲的就是 Upsonic 的"手脚":把模型说的那句话,可靠地落到你写的那个 Python 函数上,拿到结果再回填给模型。核心是四步流水线(归一化 → 注册 → 包裹 → 执行)加上三种把控制权交还给人的"暂停"。
本章聚焦工具子系统本身。整条运行管线(24 步)见 02-execution-pipeline.md;安全策略引擎(Policy/Rule/Action)见 05-safety-engine.md;Agent 与 Task 两个主抽象见 01-agent-and-task.md。这里只讲"工具"这一支。
1. 这是什么(零基础也能懂)
一句话定义: 工具系统是一层"翻译 + 传送带"——把你的普通 Python 函数,翻译成模型能理解的 JSON 描述(schema),再在模型决定调用时,把参数传回函数、执行、拿结果。
它解决什么问题: 大语言模型本身是个"纯文本机器",它不能查数据库、不能发邮件、不能读文件。要让 agent 真的"干活",必须给它一组工具,并解决三件事:
- 模型怎么知道有哪些工具、每个工具要什么参数?(→ 生成 schema)
- 用户可以用多少种方式"给"工具?一个函数?一整个类?一个 MCP 服务器?另一个 agent?(→ 归一化)
- 模型说要调用某工具后,谁去真正执行、执行前后要不要缓存/重试/暂停问人?(→ 执行闭环 + HITL)
用起来什么样: 最小的自定义工具就是一个带类型标注和 docstring 的函数,直接丢给 Agent(tools=[...]):
# 示意,非源码 —— 演示"一个函数如何变成 agent 的工具"
from upsonic import Agent, Task
def get_weather(city: str) -> str:
"""查询某个城市的当前天气。
Args:
city: 城市名,例如 "东京"
"""
return f"{city} 现在晴,26°C"
agent = Agent(model="openai/gpt-4o", tools=[get_weather])
agent.do(Task("东京天气怎么样?")) # 模型会自动决定调用 get_weather(city="东京")
你没写任何 schema、没做任何注册——框架从函数签名和 docstring 里把这些都推导出来了。重点看:类型标注 city: str、返回标注 -> str、docstring 三者缺一不可(下文 §3.2 会看到,缺了会直接报错)。
一句话直觉: 把工具系统想成一个"电话总机"。模型是打电话的人,它只知道分机号(工具名)和"该说什么"(参数)。总机(ToolManager)负责:开机时把所有分机登记造册(注册),接到呼叫时接通对应的真人(执行),必要时说一句"请稍等,我去问下负责人"(HITL 暂停)。