前言🔖
大语言模型智能体(AI Agent)最容易让人误解的一点是:
模型看起来像是在“调用函数”“查数据库”“发邮件”“操作浏览器”,但实际上,大语言模型本身通常不会直接执行这些操作。
模型本质上仍然是一个文本/Token 生成系统。它能做的是:
- 理解用户目标;
- 结合当前可用工具判断下一步行动;
- 输出结构化的“工具调用请求”;
- 由外部程序真正执行工具;
- 读取工具执行结果;
- 再决定继续调用工具,还是回复用户。
这个外部程序,就是常说的:
- Agent Runtime
- Agent Orchestrator
- 智能体运行时
- 智能体编排器
- 有时也叫 Agent Executor / Tool Runner
一、先说结论:智能体调用工具的完整链路🔖
一个典型流程如下:
用户提出请求
↓
Agent Runtime 将“消息、工具定义、系统规则”发送给 LLM
↓
LLM 输出普通文本,或输出“工具调用请求”
↓
Agent Runtime 识别到工具调用请求
↓
校验工具名、参数、用户权限、风险等级
↓
Agent Runtime 真正执行工具
↓
将工具执行结果作为 Tool Message 再发给 LLM
↓
LLM 根据结果决定:
- 继续调用其他工具
- 向用户提问补充信息
- 生成最终答案
用一句话概括:
大模型负责决定“想做什么”,运行时负责决定“能不能做、怎么安全地做”,工具负责“真正执行”。
二、一个最简单的例子:查询天气🔖
用户说:
帮我查一下北京明天的天气。
假设智能体拥有一个天气工具:
def get_weather(city: str, date: str) -> dict:
# 请求真实天气 API
...
从人类视角,似乎是:
智能体 → 调用 get_weather("北京", "明天")
但实际系统内部通常是这样运作的:
用户 ↓ LLM:“我需要使用天气工具” ↓ LLM 返回结构化调用请求 ↓ Agent Runtime 接收请求 ↓ Agent Runtime 执行 get_weather(city="北京", date="2026-07-23") ↓ 天气 API 返回结果 ↓ Agent Runtime 将结果传回 LLM ↓ LLM 生成自然语言回答
三、大语言模型并不是真的“执行函数”🔖
这是理解 Agent 的关键。
假设用户问:
北京明天天气如何?
模型不会在自己的神经网络内部执行这样的代码:
get_weather("北京", "2026-07-23")
模型也不会自己:
- 建立 HTTP 网络连接;
- 请求第三方天气服务;
- 访问数据库;
- 打开浏览器;
- 操作鼠标键盘;
- 执行 Python;
- 读取本地文件;
- 使用 API Key。
相反,模型会生成一段满足某种协议的结构化结果,例如:
{
"tool_name": "get_weather",
"arguments": {
"city": "北京",
"date": "2026-07-23"
}
}
或者在一些 API 中,返回格式可能是:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_weather_001",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\",\"date\":\"2026-07-23\"}"
}
}
]
}
模型表达的含义是:
我建议调用 get_weather 工具, 参数是 city=北京、date=2026-07-23。
接下来,Agent Runtime 才会把这份“建议”转换为真正的程序调用。
四、什么是 Agent Runtime / Orchestrator?🔖
🔹4.1 定义
Agent Runtime 是运行智能体任务的控制程序或服务。
它负责把:模型的输出意图 变成 真实、安全、可控、可审计的系统操作。
它可以是:
- 一段 Python / JavaScript 代码;
- 一个后端 API 服务;
- 一个工作流引擎;
- 一个企业内部平台;
- 一个 Agent 框架的执行器;
- 一个连接 LLM 与 MCP 工具服务器的客户端。
常见框架中都存在类似组件:虽然名字不同,本质职责相似。
| 框架或系统 | 类似 Agent Runtime 的组件 |
|---|---|
| LangChain | AgentExecutor、Tool Calling Agent |
| LangGraph | Graph Runtime / StateGraph 执行器 |
| OpenAI Agents SDK | Agent Runner / Runner |
| AutoGen | Agent Runtime、Conversation Runtime |
| Semantic Kernel | Kernel、Planner、Function Invocation |
| 自研系统 | Orchestrator、Workflow Engine、Tool Executor |
| MCP 架构 | MCP Client + Agent Host |
🔹4.2 Agent Runtime 不只是“执行工具”
一个成熟的 Runtime 通常包括以下职责:
1. 管理对话状态 2. 管理工具列表和工具 Schema 3. 调用大语言模型 4. 识别模型是否请求了工具调用 5. 解析工具名和参数 6. 校验参数格式 7. 检查用户权限 8. 检查是否需要二次确认 9. 执行工具 10. 处理超时、重试和失败 11. 将工具结果返回给模型 12. 控制多轮循环和最大执行步数 13. 记录审计日志、成本和链路追踪
可以把它理解为:
LLM 是“大脑” Tool 是“手脚” Agent Runtime 是“神经系统、执行系统和安全主管”
🔹4.3 Agent Runtime 的架构位置
┌───────────────────────────────────────────────┐
│ 用户 / 前端 │
└──────────────────────┬────────────────────────┘
│
▼
┌───────────────────────────────────────────────┐
│ Agent Runtime / Orchestrator │
│ │
│ - 会话状态管理 │
│ - 提示词组装 │
│ - 工具注册与路由 │
│ - 参数校验 │
│ - 权限与确认 │
│ - 重试、超时、限流 │
│ - Agent Loop 控制 │
│ - 日志、审计、可观测性 │
└───────────────┬───────────────────┬───────────┘
│ │
▼ ▼
┌────────────────┐ ┌───────────────────┐
│ LLM │ │ Tool Executor │
│ GPT / Qwen/... │ │ 工具执行器 │
└────────────────┘ └─────────┬─────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
数据库 / CRM 搜索引擎 浏览器自动化
企业 API 邮件服务 文件系统
代码执行环境 支付系统 MCP Server
五、Runtime 是如何判断 LLM 想调用工具的?🔖
这里有一个很重要的细节:
严格来说,Agent Runtime 通常不需要“猜测”或“理解”模型是否想调用工具。
它主要是根据模型 API 返回的结构化字段来判断。
也就是说,Runtime 不是通过分析模型说的自然语言:
“我打算查一下天气……”
来猜测是否需要调用工具。
而是模型 API 会明确返回类似字段:
{
"tool_calls": [...]
}
或:
{
"function_call": {...}
}
或:
{
"finish_reason": "tool_calls"
}
Runtime 只需要检测这些字段即可。
🔹5.1 两种模型输出情况
- 情况 1:模型给出普通文本回答
例如用户问:什么是递归?
模型返回:
{
"role": "assistant",
"content": "递归是一种函数直接或间接调用自身的编程方法。",
"tool_calls": []
}
Runtime 发现没有 tool_calls:
if not response.tool_calls:
return response.content
于是直接把答案返回给用户。
- 情况 2:模型请求调用工具
用户问:今天上海天气怎么样?
模型返回:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_001",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"上海\",\"date\":\"2026-07-22\"}"
}
}
]
}
Runtime 会检查:
if response.tool_calls:
# 说明模型请求调用工具
execute_tools(response.tool_calls)
因此更准确地说:
Runtime 不是“判断模型心里想不想调用工具”,而是“读取模型输出协议中是否存在工具调用指令”。
🔹5.2 finish_reason 的作用
很多模型 API 会返回一个结束原因,例如:
{
"finish_reason": "stop"
}
可能代表:模型正常完成了自然语言回答。
而:
{
"finish_reason": "tool_calls"
}
可能代表:模型本轮输出结束的原因是:它要调用工具。
不同模型平台的字段命名略有不同,但核心概念一样。
常见状态包括:Runtime 根据这些状态进入不同处理分支。
| 状态 | 含义 |
|---|---|
stop | 模型已正常完成回答 |
tool_calls | 模型请求调用工具 |
length | 输出达到 Token 长度上限 |
content_filter | 内容被安全策略拦截 |
error | 调用模型服务失败 |
六、模型为什么知道有哪些工具可以调用?🔖
因为 Agent Runtime 会在请求模型时,把工具说明一起提供给模型。
假设系统注册了一个天气工具,Runtime 会向 LLM 提供这样的描述:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市在指定日期的天气预报。仅用于获取天气信息,不会进行任何修改操作。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海、深圳"
},
"date": {
"type": "string",
"description": "查询日期,格式为 YYYY-MM-DD"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["city", "date"]
}
}
}
模型在上下文中看到这份说明后,会知道:
工具名:get_weather 用途:查询天气 需要的参数:city、date 可选参数:unit
当用户问天气时,模型就有较高概率选择该工具。
七、工具定义本质上是一份“给模型看的 API 文档”🔖
对于模型而言,工具的定义非常像一份 API 文档。
一个好的工具定义至少应该包含:
1. 工具名称 2. 工具用途 3. 使用场景 4. 不适用场景 5. 参数名称 6. 参数类型 7. 参数格式 8. 参数范围 9. 必填参数 10. 返回结果说明 11. 风险或副作用
例如一个发送邮件工具:
{
"name": "send_email",
"description": "向指定收件人发送电子邮件。该操作会产生外部副作用。只有在用户明确确认邮件内容、收件人和发送行为后才可以调用。",
"parameters": {
"type": "object",
"properties": {
"to": {
"type": "array",
"items": {
"type": "string",
"format": "email"
},
"description": "收件人邮箱地址列表"
},
"subject": {
"type": "string",
"description": "邮件主题"
},
"body": {
"type": "string",
"description": "邮件正文"
}
},
"required": ["to", "subject", "body"]
}
}
这里的描述尤其强调:只有在用户明确确认后才可以调用。
这能帮助模型做出更安全的行为,但请注意:
工具描述只是“提示和约束”,真正的安全保障必须由 Runtime 和后端权限系统完成。
八、完整工作过程:一步一步拆解🔖
以“查询北京明天天气”为例。
🔹第 1 步:用户提出请求
用户:帮我查询北京明天的天气。
🔹第 2 步:Runtime 组装上下文
Runtime 将以下内容组合成模型输入:
系统指令: 你是天气助手。涉及实时天气时必须使用天气工具。 不要编造天气数据。 当前日期: 2026-07-22 用户消息: 帮我查询北京明天的天气。 可用工具: get_weather(city, date, unit)
概念上的请求如下:
{
"messages": [
{
"role": "system",
"content": "你是天气助手。查询天气时必须调用工具,不得编造。当前日期为 2026-07-22。"
},
{
"role": "user",
"content": "帮我查询北京明天的天气。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市和日期的天气。",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"date": {"type": "string"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city", "date"]
}
}
}
]
}
🔹第 3 步:LLM 输出工具调用请求
模型发现:
- 用户要查天气;
- 天气属于实时信息;
- 系统要求必须使用工具;
- 当前有
get_weather工具可用; - “明天”根据当前日期可转换为
2026-07-23。
于是模型输出:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_weather_001",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\",\"date\":\"2026-07-23\",\"unit\":\"celsius\"}"
}
}
]
}
🔹第 4 步:Runtime 识别工具调用
Runtime 不需要猜测,只需要检查结构化字段:
tool_calls = response.get("tool_calls", [])
if len(tool_calls) == 0:
# 普通文本回答
return response["content"]
# 存在工具调用请求
for tool_call in tool_calls:
execute_tool(tool_call)
🔹第 5 步:Runtime 校验工具调用
这是安全核心。
Runtime 不能因为模型输出了:
{
"name": "get_weather",
"arguments": {
"city": "北京"
}
}
就立刻执行。
它至少要校验:
- 5.1 工具是否存在于白名单 模型不应该能够调用没有注册的工具。
TOOLS = {
"get_weather": get_weather,
"search_web": search_web
}
if tool_name not in TOOLS:
raise SecurityError("不允许调用该工具")
- 5.2 参数是否符合 Schema 生产环境中通常会使用:
- JSON Schema;
- Pydantic;
- Zod;
- Marshmallow;
- OpenAPI Schema;
- 自定义参数验证器。
args = json.loads(tool_call["function"]["arguments"])
if not isinstance(args.get("city"), str):
raise ValueError("city 必须是字符串")
if not isinstance(args.get("date"), str):
raise ValueError("date 必须是字符串")
if args.get("unit") not in ["celsius", "fahrenheit"]:
raise ValueError("unit 参数不合法")
- 5.3 当前用户是否有权限
- 5.4 是否属于高风险操作
🔹第 6 步:Runtime 真实执行工具
经过校验之后,才真正调用后端函数:
result = get_weather(
city="北京",
date="2026-07-23",
unit="celsius"
)
工具内部可能调用一个第三方天气 API:
import requests
def get_weather(city: str, date: str, unit: str = "celsius"):
response = requests.get(
"https://api.weather.example.com/forecast",
params={
"city": city,
"date": date,
"unit": unit
},
headers={
"Authorization": f"Bearer {WEATHER_API_KEY}"
},
timeout=10
)
response.raise_for_status()
return response.json()
🔹第 7 步:工具执行结果返回给 Runtime
假设天气 API 返回:
{
"city": "北京",
"date": "2026-07-23",
"condition": "多云",
"temperature": {
"min": 23,
"max": 31
},
"wind": "东南风 2 级"
}
Runtime 会将其转换为一条工具结果消息:
{
"role": "tool",
"tool_call_id": "call_weather_001",
"content": "{\"city\":\"北京\",\"date\":\"2026-07-23\",\"condition\":\"多云\",\"temp_min\":23,\"temp_max\":31,\"wind\":\"东南风2级\"}"
}
其中:
tool_call_id = call_weather_001
用于把工具结果和之前的调用请求一一对应。
第 8 步:Runtime 再次请求 LLM
现在 Runtime 会将对话历史重新发送给模型:
系统:你是天气助手。 用户:帮我查询北京明天的天气。 助手:调用 get_weather(city=北京, date=2026-07-23)。 工具:北京,2026-07-23,多云,23~31℃,东南风2级。
模型获得了真实数据后,生成最终回答:
北京明天多云,气温 23~31℃,东南风 2 级。 白天气温较高,建议穿轻薄衣物,并注意防晒。
九、Agent Loop:为什么智能体可以连续执行多个工具?🔖
简单任务可能只调用一次工具,但真实智能体通常是多步任务。
例如用户说:
找出本月销售额最低的三个地区,分析原因,并把报告邮件发给销售总监。
这至少包含:
1. 查询销售数据; 2. 找出最低的三个地区; 3. 查询这些地区的库存、人员、活动或历史销售数据; 4. 汇总并生成报告; 5. 查询销售总监联系方式; 6. 向用户展示邮件草稿; 7. 获取用户确认; 8. 发送邮件。
这就是所谓的 Agent Loop。
┌──────────────────────────────────────┐ │ 1. 调用 LLM │ │ 2. LLM 输出最终答案? │ │ 是 → 返回给用户 │ │ 否 → 输出工具调用 │ │ 3. Runtime 校验并执行工具 │ │ 4. 工具结果写入上下文 │ │ 5. 回到第 1 步 │ └──────────────────────────────────────┘
伪代码如下:
MAX_STEPS = 10
def run_agent(user_message: str):
messages = [
{
"role": "system",
"content": "你是企业数据分析助手。"
},
{
"role": "user",
"content": user_message
}
]
for step in range(MAX_STEPS):
response = llm.chat(
messages=messages,
tools=tool_definitions
)
assistant_message = response.message
messages.append(assistant_message)
# 如果没有工具调用,说明模型已完成任务
if not assistant_message.tool_calls:
return assistant_message.content
# 如果有工具调用,则逐个执行
for tool_call in assistant_message.tool_calls:
tool_result = execute_tool_safely(
tool_call=tool_call,
current_user=current_user
)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result, ensure_ascii=False)
})
return "任务执行步骤过多,系统已停止执行。"
设置 MAX_STEPS 的原因是防止智能体:
- 无限循环;
- 反复调用失败工具;
- 大量消耗 Token;
- 产生过高成本;
- 被恶意输入诱导执行过多动作。
十、Runtime 如何决定“是否允许执行”?🔖
必须强调:模型请求调用工具,不等于 Runtime 必须执行。
模型只能提出一个调用建议:“请调用 send_email”
真正的执行决策通常由 Runtime 的策略系统决定。
一个典型决策逻辑如下:
模型请求调用工具
↓
工具是否已注册?
↓
参数是否合法?
↓
当前用户是否有权限?
↓
是否超过调用次数、预算或限流?
↓
是否访问了敏感资源?
↓
是否是写操作或高风险操作?
↓
是否已经获得用户确认?
↓
允许执行 / 拒绝执行 / 请求确认 / 降级处理
可以写成伪代码:
def execute_tool_safely(tool_call, current_user):
tool_name = tool_call.function.name
args = parse_json(tool_call.function.arguments)
# 1. 工具白名单
if tool_name not in TOOL_REGISTRY:
return {
"ok": False,
"error": "tool_not_allowed"
}
# 2. 参数 Schema 校验
validate_arguments(tool_name, args)
# 3. 权限校验
if not has_permission(current_user, tool_name, args):
return {
"ok": False,
"error": "permission_denied"
}
# 4. 风险策略
if is_high_risk(tool_name, args):
if not has_user_confirmation(current_user, tool_name, args):
return {
"ok": False,
"error": "confirmation_required"
}
# 5. 真正执行
try:
result = TOOL_REGISTRY[tool_name](**args)
return {
"ok": True,
"result": result
}
except TimeoutError:
return {
"ok": False,
"error": "tool_timeout"
}
except Exception as e:
return {
"ok": False,
"error": "tool_execution_failed",
"message": str(e)
}
十一 、为什么不能给模型一个“万能工具”?🔖
很多初学者会设计一个类似下面的工具:
def run_shell(command: str):
os.system(command)
或者:
def run_sql(sql: str):
database.execute(sql)
这是非常危险的。
模型可能:
- 生成错误命令;
- 被用户诱导生成危险命令;
- 被网页、邮件、文档中的提示注入攻击;
- 误删文件;
- 执行高成本 SQL;
- 泄露数据;
- 修改生产环境。
不推荐:
run_shell(command) run_sql(sql) execute_any_code(code)
更推荐暴露高层、具体、可控的业务工具:
get_customer(customer_id) list_project_files(project_id) get_sales_report(start_date, end_date, region) create_support_ticket(title, description, priority) run_unit_tests(repository, branch) deploy_to_staging(service, version)
工具越具体,权限越容易控制,模型也越不容易误用。
总结:最核心的流程可以再次浓缩为🔖
LLM:我建议调用某个工具,参数是这些。
↓
Runtime:这个工具是否允许?参数是否合法?用户是否有权限?是否需要确认?
↓
Tool:真正执行操作并返回结果。
↓
Runtime:把结果反馈给 LLM。
↓
LLM:根据真实结果继续行动或回答