Agent、MCP Server与LLM交互关系全解析

2026-06-19阅读 0热度 0
MCP

内容整理自与 GLM-5.2 的深度技术对话。

Agent、MCP Server 和 LLM 三者之间的交互关系(基本科普)

首轮对话解析

MCP 协议是用于 Agent 和 MCP 服务器之间的调用吗?LLM 不具备直接调用 MCP 服务器的能力?

精准命中核心。MCP 协议本质上定义了 Agent(即 LLM 的宿主应用)与 MCP 服务器之间的通信契约。它遵循标准的客户端-服务器模型——Agent 作为宿主运行 MCP Client,与提供具体工具、数据源及服务的 MCP Server 建立双向、实时的数据通道。

你的判断完全正确:LLM 本身无法直接调用 MCP 服务器。LLM 本质上是一个纯推理引擎,缺乏发起网络请求或进程间通信的能力。实际流转中,必须由 Agent(编排层)担任桥梁角色:

  1. LLM 根据上下文生成调用意图,例如 Function Calling 请求。
  2. Agent 内的 MCP Client 捕获该意图,并将其转换为符合 MCP 协议规范的结构(底层通常采用 JSON-RPC 2.0)。
  3. MCP Client 向 MCP Server 发起请求,执行工具发现或工具调用。
  4. MCP Server 执行完毕后返回结构化结果,Agent 将该结果注入 LLM 的上下文,供其进行后续推理与回复生成。

一句话总结:MCP 解决了工具标准化接入与调用的架构问题。所有协议层面的通信与工具执行在 Agent 与 MCP Server 之间完成,LLM 仅负责理解请求与处理最终输出。

第二轮对话详解

接下来三个核心问题:

  1. LLM 如何知晓可调用的工具列表?
  2. LLM 如何向 Agent 传达具体的调用指令?
  3. LLM 如何获取工具执行后的结果?
  1. LLM 感知工具的机制
    LLM 本身不具备预知能力,完全依赖 Agent(MCP Client)提供的上下文。Agent 运行时通过 MCP 协议从已连接的 MCP Server 动态拉取可用工具清单,包括工具名称、功能描述、入参定义等。随后,Agent 将这些工具定义格式化后注入 Prompt 或 API 请求的特定参数(如 tools 列表)。LLM 读取这些结构化信息后,便“了解”当前环境中可用的工具集。
  2. LLM 传达调用指令的方式
    LLM 利用原生的 Function Calling 能力传达执行意图。接收到用户问题并分析可用工具后,若判断需要调用某工具,LLM 会在响应中生成一段结构化数据(通常是 JSON),明确指定工具名称及其入参。这段输出是机器可读的指令,而非自然语言,Agent 可直接解析并执行。
  3. LLM 获取执行结果的路径
    工具的实际执行由 Agent 与 MCP Server 协同完成。Agent 解析出 LLM 的调用意图后,通过 MCP 协议向对应 Server 发起请求。Server 执行底层函数或 API 调用,将结构化结果返回 Agent。随后,Agent 将该结果以 toolfunction 角色追加至对话历史,并再次向 LLM 发起请求。LLM 结合原始问题与刚获取的结果进行综合推理,最终生成面向用户的自然语言回复。

第三轮深度探讨

阅读第3点回答后产生新疑问:Agent 接收到 LLM 的函数调用意图并执行具体函数后,如何判断是将执行结果直接返回给用户,还是继续交由 LLM 进行二次推理?

参考以下与 LLM 交互的示例代码:

import json
from zai import ZhipuAiClient

# 初始化客户端
client = ZhipuAiClient(api_key='您的apikey')

# 定义天气查询函数
def get_weather(city: str) -> dict:
    """获取指定城市的天气信息"""
    # 此处应调用真实天气 API
    weather_data = {
        "city": city,
        "temperature": "22°C",
        "condition": "晴天",
        "humidity": "65%",
        "wind_speed": "5 km/h"
    }
    return weather_data

# 定义函数工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的当前天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,例如:北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# 发起对话请求
response = client.chat.completions.create(
    model="glm-5.2",  # 选用支持函数调用的模型
    messages=[
        {"role": "user", "content": "北京今天天气怎么样?"}
    ],
    tools=tools,         # 传入函数工具
    tool_choice="auto"   # 自动决策是否调用函数
)

# 处理函数调用
message = response.choices[0].message
messages = [{"role": "user", "content": "北京今天天气怎么样?"}]
messages.append(message.model_dump())

if message.tool_calls:
    for tool_call in message.tool_calls:
        if tool_call.function.name == "get_weather":
            # 解析参数并调用函数
            args = json.loads(tool_call.function.arguments)
            weather_result = get_weather(args.get("city"))
            
            # 将函数结果回传模型
            messages.append({
                "role": "tool",
                "content": json.dumps(weather_result, ensure_ascii=False),
                "tool_call_id": tool_call.id
            })
    
    # 获取最终回答
    final_response = client.chat.completions.create(
        model="glm-5.2",
        messages=messages,
        tools=tools
    )
    
    print(final_response.choices[0].message.content)
else:
    print(message.content)

该程序直接将函数结果暴露给用户。若 LLM 需基于函数结果进一步推理总结,应如何设计?

关键逻辑在于 LLM 返回的结束信号与 Agent 编排层的循环控制。先说一个细节:你提供的代码实际上已经实现了“交由 LLM 继续推理”的流程,并未将原始结果直接输出给用户。 我们逐步拆解:

1. 机制解析:结果去向的判定依据

标准 Function Calling 机制中,LLM 的响应携带明确的完结标志(Finish Reason)或动作指令:

  • 需要继续推理(返回 tool_calls):LLM 判断需调用外部函数时,响应中返回 tool_calls 字段,完结标志通常为 tool_calls。这意味着 LLM 要求执行工具并将结果回传,以便继续处理。
  • 直接输出(返回纯文本):当 LLM 已获取工具结果或认为无需调用工具即可回答时,它直接生成自然语言文本。响应中不包含 tool_calls,完结标志为 stop。这意味着 LLM 已生成最终回复,可直接呈现给用户。

2. 代码实际运行逻辑复盘

你提供的代码片段正是标准的“交由 LLM 总结”流程:

# 步骤一:检查 LLM 是否发出工具调用指令
if message.tool_calls:
    for tool_call in message.tool_calls:
        if tool_call.function.name == "get_weather":
            # 步骤二:Agent 解析并执行函数,获取原始结果
            args = json.loads(tool_call.function.arguments)
            weather_result = get_weather(args.get("city")) 
            
            # 步骤三:将原始结果以 role="tool" 形式追加至对话历史
            messages.append({
                "role": "tool",
                "content": json.dumps(weather_result, ensure_ascii=False),
                "tool_call_id": tool_call.id
            })
    
    # 步骤四:发起新请求,让 LLM 基于完整上下文(含 tool 结果)进行总结
    final_response = client.chat.completions.create(
        model="glm-5.2",
        messages=messages,
        tools=tools
    )
    
    # 步骤五:打印 LLM 总结后的最终回复
    print(final_response.choices[0].message.content)

这段代码中,weather_result(包含温度、湿度等原始字典)并未被直接 print。程序将其回传 LLM,然后打印 final_response——这是 LLM 消化原始数据后生成的格式化自然语言回复(例如:“北京今日天气晴朗,气温22度...”)。

3. 若需“直接输出”应如何实现?

作为对比,若希望 Agent 执行函数后直接将原始结果返回用户,代码应调整为:

if message.tool_calls:
    for tool_call in message.tool_calls:
        if tool_call.function.name == "get_weather":
            args = json.loads(tool_call.function.arguments)
            weather_result = get_weather(args.get("city"))
            
            # 直接输出原始结果,不再发起二次 LLM 请求
            print(json.dumps(weather_result, ensure_ascii=False))

4. 成熟 Agent 框架的控制逻辑

在生产级 Agent 开发中(如 LangChain 的 AgentExecutor),这个判定过程被抽象为自动化循环:

  1. Agent 接收 LLM 输出。
  2. 判断输出类型:
    • 若为 AgentAction(对应 tool_calls),则执行工具,将结果拼入上下文,继续循环
    • 若为 AgentFinish(对应 stop),则跳出循环,将结果返回用户。
  3. 防死循环机制:为规避 LLM 无限请求调用工具,框架通常设置 max_iterations(最大迭代次数)。超限后,Agent 强制中断并将当前结果返回用户。

第四轮框架价值分析

据此可知编写基础 Agent 并不复杂,那么 Agent 框架的核心价值是什么?此外还提供了哪些增强功能?

确实,构建一个最小化 Agent 原型仅需 LLM 加函数调用循环。但一旦进入生产环境,工程复杂度骤升。Agent 框架的真正意义在于帮你从“原型验证”跨越到“产品级系统”。

一、Agent 框架解决的核心痛点

  1. 状态与记忆管理
    简单脚本中,对话历史靠 messages 列表拼接。但长周期任务中,历史记录会迅速突破 LLM 上下文窗口。框架提供自动截断、滑动窗口、摘要压缩等策略,并支持接入向量数据库实现长期记忆(RAG),避免 Agent 在长对话中“失忆”。
  2. 复杂控制流与编排
    基础 Agent 是线性流程(LLM → 工具 → LLM)。真实任务往往需要更复杂的逻辑:
    • 并行调用:用户提问“对比北京和上海天气”,框架可自动触发两个并行工具调用,而非串行等待。
    • 条件分支与重试:任务失败时自动重试,或根据工具结果动态决策下一步分支。
    • 子任务分发:将复杂任务拆解为子图,分配至不同专用 Agent 处理。
  3. 健壮性与异常处理
    手写代码时,API 超时、LLM 返回的 JSON 格式错误或无限循环将直接导致崩溃。框架内置兜底机制:工具重试、格式修复、强制迭代限制等。
  4. 供应商解耦
    若手写对接智谱 API,后续切换至 OpenAI 或开源模型需重写大量底层代码。框架提供统一抽象接口,仅需修改一行配置即可无缝切换 LLM 提供商。

二、Agent 框架的增值功能

除解决上述痛点,框架还提供一系列高级能力,显著提升开发效率:

  1. 预置认知架构
    框架内置经过业界验证的推理引擎,无需从零设计 Prompt 逻辑。例如:
    • ReAct:交错生成推理轨迹与动作。
    • Plan-and-Execute:先制定全局计划,再逐步执行。
    • Tree of Thoughts (ToT):树状探索多条求解路径,支持回溯。
  2. 多智能体协作
    单个 Agent 难以覆盖所有领域。框架(如 AutoGen、MetaGPT、CrewAI)支持定义不同角色的 Agent(如“产品经理 Agent”、“开发 Agent”、“测试 Agent”),它们可在框架内自动对话、交接任务、交叉审查代码,完成端到端复杂工作流。
  3. 生态丰富的工具集成
    框架提供开箱即用的工具生态(搜索引擎、数据库查询、文件读写、代码执行器),并原生集成各类第三方 API。同时,现代框架正积极适配 MCP 协议,标准化接入无限外部工具源。
  4. 人机交互机制
    执行敏感操作(如发送邮件、删除数据库)前,框架可暂停流转,等待人工审批后再继续。
  5. 可观测性与调试工具
    Agent 行为常被视为“黑盒”。框架提供 LangSmith 等追踪工具,可可视化每次 LLM 调用的输入输出、工具调用耗时、Token 消耗情况,对 Prompt 调优与成本控制至关重要。

总结
手写 Agent 如同用原生 Socket 手写 HTTP 请求——能跑通,但繁琐且难维护;使用 Agent 框架则类似采用 Web 框架(FastAPI 或 Spring Boot),它帮你处理路由、中间件、并发、异常处理等基础设施,让你聚焦于业务逻辑(工具定义与 Prompt 设计)。

第五轮技术细节问答

最后三个问题:

  1. Agent 与 LLM 间的网络请求使用何种协议?长连接还是无状态短连接?
  2. 随着对话历史增长,每次请求的 body 是否持续膨胀?
  3. 超出上下文窗口时,compact 操作由 Agent 还是 LLM 执行?

以下是针对三个问题的详细解析:

1. 协议类型与连接模式

Agent 与 LLM 的通信协议取决于模型提供商及底层架构,当前正处于从无状态短连接有状态长连接的演进阶段:

  • 主流现状:基于 HTTP 的请求-响应模式(应用层无状态)
    目前大多数大模型 API(如 OpenAI、智谱标准接口)基于 HTTP/1.1 或 HTTP/2。尽管 HTTP/1.1 默认开启 Keep-Alive 复用 TCP 连接以减少握手开销,但应用层逻辑仍为无状态的短连接交互。即:Agent 发送 POST 请求 → 服务器流式或非流式响应 → 逻辑上本次事务结束。Agent 必须在下次请求中重新携带完整上下文(历史记录),服务器端不保存会话状态。
  • 前沿趋势:WebSocket 模式(有状态长连接)
    随着 Agent 工作流变长(涉及多步工具调用),HTTP 每次请求携带大量 Header 与重复上下文导致的“控制平面延迟”愈发显著。2026年初,OpenAI 等厂商已为 Responses API 推出 WebSocket 模式。该模式建立持久化有状态连接,Agent 与 LLM 可双向、持续推送消息,服务器维护会话状态,大幅优化 20+ 次工具调用等长链 Agent 场景的执行效率。

2. 请求 body 的膨胀特性

在传统无状态 API 模式下确实如此。
LLM 本身无状态,Agent 必须在每次请求中将历史对话(用户提问、LLM 回复、工具调用记录及结果)全部打包进 messages 数组。随着对话轮数增加,该数组线性膨胀,导致:

  • 网络传输带宽消耗增加。
  • Token 计费成本上升(按输入与输出总 Token 计费)。
  • 推理首字延迟增加(LLM 需先处理庞大输入才能开始生成)。

(注:若采用 WebSocket 有状态连接,Agent 可仅发送增量消息,由服务器维持会话状态,避免 body 持续膨胀。)

3. Compact 操作的执行者

此操作由 Agent(及底层框架/编排逻辑)发起并控制,LLM 仅被动配合执行。
LLM 是被动文本生成器,无法感知自身上下文窗口,更无法主动触发压缩。标准 compact 流程如下:

  1. 监控与判定(Agent 完成):Agent 每次发送请求前,使用 Tokenizer 统计当前 messages 的总 Token 数。若超出目标 LLM 的上下文窗口限制(如 8K、128K),Agent 触发 compact 机制。
  2. 执行策略(Agent 控制,可能调用 LLM)
    • 简单截断(Agent 独立完成):Agent 按 FIFO 原则删除最旧的若干轮对话,或直接裁剪体积庞大的旧工具返回结果。
    • 记忆摘要(Agent 调用 LLM 完成):Agent 截取一段较早的历史对话,构造 Prompt(如:“请将以下对话总结为关键上下文信息”),发起额外、独立的 LLM 请求。获取 LLM 返回的摘要文本后,Agent 用其替换庞大旧历史,再拼接最近对话,形成新的、更紧凑的 messages 发送给 LLM 进行正常推理。

因此,Compact 是 Agent 为适应 LLM 上下文窗口限制而主动实施的“记忆管理”工程手段。

第六轮Token消耗分析

对话过程中 messages 数组线性增长,是否意味着每次提问消耗的 Token 都比上一次更多?

完全正确。在传统无状态 API 模式下,随着对话轮数增加,每次提问的输入 Token 数量确实持续增长,且总消耗呈二次方级攀升。

具体逻辑如下:

1. 输入 Token 的线性累加
LLM 无状态,Agent 每次请求必须附带全部历史记录。例如:

  • 第 1 次提问:发送 1 条消息(当前提问)。
  • 第 2 次提问:发送 3 条消息(第1轮用户提问 + 第1轮LLM回答 + 当前提问)。
  • 第 3 次提问:发送 5 条消息(前两轮历史 + 当前提问)。

2. 累计 Token 消耗呈二次方级爆炸
单次输入 Token 线性增长,但整个对话过程的总消耗呈 \(O(N^2)\) 增长。假设每轮对话固定产生 100 Token:

  • 第 1 轮:输入 100,输出 100。
  • 第 2 轮:输入 200(含第1轮历史),输出 100。
  • 第 3 轮:输入 300(含前2轮历史),输出 100。
  • ...第 N 轮:输入 100N,输出 100。

仅进行 10 轮对话,累计输入 Token 达 5500,而非 1000。这就是 Agent 场景中 Token 消耗远超预期的原因。

3. Agent 场景下的“超级膨胀”
在普通对话中,增长可预测。但在 Agent 场景中,膨胀更剧烈:每次工具调用至少新增两条消息(LLM 的调用指令 + 工具返回结果)。若工具返回长文档或代码,该部分 Token 会被完整带入后续每次循环,导致上下文体积瞬间飙升。

应对策略
为避免 Token 暴涨导致成本失控或超出上下文窗口,通常采取以下措施:

  1. Agent 端 Compact:历史记录达到阈值时,触发摘要压缩,用简短总结替换冗长早期对话。
  2. 采用有状态 API:利用新一代大模型 API(如 WebSocket 模式)的服务端状态保持能力,仅发送增量消息。
  3. 输入端提纯:提交前自动清洗工具返回结果,剔除冗余字段与格式,仅保留核心数据,控制单轮交互体积。
免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策