AI

每天吃透一个 AI 知识点 ——智能体是如何调用方法或工具的?

前言🔖


大语言模型智能体(AI Agent)最容易让人误解的一点是:

模型看起来像是在“调用函数”“查数据库”“发邮件”“操作浏览器”,但实际上,大语言模型本身通常不会直接执行这些操作。

模型本质上仍然是一个文本/Token 生成系统。它能做的是:

  1. 理解用户目标;
  2. 结合当前可用工具判断下一步行动;
  3. 输出结构化的“工具调用请求”;
  4. 由外部程序真正执行工具;
  5. 读取工具执行结果;
  6. 再决定继续调用工具,还是回复用户。

这个外部程序,就是常说的:

  • 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 的组件
LangChainAgentExecutor、Tool Calling Agent
LangGraphGraph Runtime / StateGraph 执行器
OpenAI Agents SDKAgent Runner / Runner
AutoGenAgent Runtime、Conversation Runtime
Semantic KernelKernel、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:根据真实结果继续行动或回答