大语言模型 Agent 原理与工程实践

随着 AI 快速发展,越来越多的技术被提出。一个直观感受是:刚弄清 Prompt,又遇到 MCP、Skill、和 Agent;概念越来越多,本文从一次 LLM 调用出发,把它们串联起来,说明各自解决什么问题、如何协作,以及 Prompt、Workflow 和 Agent 分别适合什么场景。

一、从一次模型调用到 Agent

1.1 LLM 的能力与边界

大语言模型的一次调用,可以抽象为:

output = LLM(context)

模型接收当前 Context,根据训练得到的参数计算 Token 概率分布并逐步生成输出。随着模型规模、训练数据和训练方法演进,模型在指令遵循、复杂推理和代码生成等任务上的表现持续提升,但一次调用的基本形态并没有改变。这意味着模型天然存在几个边界:

今天常见的大模型应用能力,几乎都可以看作对这些边界的补充:

模型提供智能,但一次模型调用本身还不是一个完整的应用。

1.2 Tools 与 Loop:Agent 如何从“想”到“做”

Agent 不是一种更聪明的模型,而是围绕模型构建的执行系统。不同框架对 Agent 的定义并不完全一致,本文采用下面的工程抽象(参见 Anthropic:Building effective agents):

最小 Agent = LLM + Tools + Loop

1.2.1 Tool 是如何工作的

模型可以生成“读取文件”“查询数据库”这样的文字,却不会真的执行这些操作。Tool 为模型提供了与外部系统交互的入口。应用会把工具名称、用途和输入 Schema 等能力描述随请求提供给模型,让模型知道“可以做什么”以及“应该怎样调用”。当模型决定使用工具时,它会返回一个包含工具名和参数的结构化调用请求;这种由模型提出工具调用请求的机制,就叫作工具调用(Tool Calling)。

模型生成 tool_call
  → Tool Runtime 校验工具名、参数和权限
  → Tool Runtime 执行真实操作
  → tool_result 写回消息队列
  → 模型根据新结果继续判断

需要特别注意:工具调用只表示模型“请求使用某个工具”,并不意味着模型亲自执行了它。真实操作由应用、SDK Tool Runtime 或模型服务商托管的工具环境执行,执行结果随后重新返回模型。因此,文件读写、命令执行、网络请求和数据库访问都发生在模型之外(参见 OpenAI:Function callingAnthropic:Tool use)。

MCP 为 Prompts、Resources 和 Tools 提供标准化的发现与调用协议,减少不同 AI 应用重复集成的成本。但统一协议本身不会替代具体系统中的鉴权、用户授权、参数校验、危险操作防护和审计(参见 MCP 2025-06-18 版官方规范)。

1.2.2 一次完整的 Agent 交互

将模型调用和工具执行放在一起,一次完整的 Agent 交互会经历以下过程:

用户提出目标
  → Harness 组织 Prompt、历史消息、工具描述和当前状态
  → LLM 根据 Context 生成响应
  → 是否返回工具调用?
      → 是:生成结构化 tool_call
          → Tool Runtime 校验并执行工具
          → tool_result 写回消息队列
          → 再次调用 LLM
      → 否:返回最终结果,结束循环

模型始终只负责根据 Context 做出判断,并生成文本或结构化的工具调用意图。对于客户端工具,应用需要把工具结果写回消息队列;对于服务端工具,这一步可能由 SDK 或服务商自动完成。无论由谁编排,模型都必须在后续调用中获得工具结果,才能感知环境变化。

1.2.3 Agent Loop

把上述过程写成代码,一个最小 Agent Loop 可以表示为:

messages = [user_request]

for step in range(max_steps):
    response = llm(messages, tools)
    messages.append(response.message)

    if response.has_tool_calls():
        results = execute_tools(response.tool_calls)
        messages.extend(results)
        continue

    return response.text

raise MaxStepsExceeded()

每次 LLM 调用仍然相互独立。所谓“Agent 正在持续工作”,来自外部系统不断重复“模型决策—工具执行—结果反馈”,并将新的环境状态交给下一次模型调用。

观察状态 → 做出决策 → 执行动作 → 获得反馈 → 更新状态

如果没有最大步数、停止条件和结果验证,Loop 不会带来自治,只会带来重复操作和失控的资源消耗。

二、组成大模型应用的基础能力

2.1 Prompt:表达目标,而不是替代程序

Prompt 是开发者和用户向模型表达任务的方式。它不仅是聊天框中的一句话,也可能包含长期规则、任务背景、示例和输出约束。

一次模型请求中常见的指令与约束包括:

例如,一个电商退款助手收到“帮我退货退款”时,一次模型请求可以包含以下四部分:

Structured Output 可以用于约束面向用户的响应,也可以配合 Function Calling 约束工具参数;两者用途不同(参见 OpenAI:Structured Outputs)。好的 Prompt 能提高模型理解目标的概率,却不能把概率系统变成确定性程序。把“必须鉴权”“金额不能为负数”这类规则写入 Prompt,并不等于系统已经实现了鉴权和参数校验。真正影响安全与正确性的规则,仍然需要由代码、权限系统、Schema 校验和测试兜底。

2.2 Context:模型这一轮真正看到的内容

Prompt 和 Context 经常被混为一谈。Prompt 是我们主动表达的指令,而 Context 是一次请求中模型能够看到的全部信息。Context 可能包括:

继续以电商退款助手为例。在准备处理用户的退款确认时,应用可能把下面这些内容共同组织进一次模型请求。以下只是概念示意,不代表某个 API 的固定格式:

[System Instruction]
你是电商售后助手。调用退款工具前必须取得用户确认。

[历史对话]
用户:订单 20250801001 收到时已经破损,我想退款。
助手:我先为你查询订单和退款规则。

[检索到的售后政策]
签收后 7 天内发现商品质量问题,可以申请退款;审核通过后退回原支付渠道。

[可用工具]
get_order(order_id):查询订单归属、实付金额和当前状态
create_refund(order_id, refund_amount, reason):创建退款申请

[工具执行结果]
get_order → 订单属于当前用户,实付 99 元,状态为“已签收”,尚未退款

[当前用户输入]
退回 99 元吧,我确认退款。

这些内容合在一起,才是模型在这一轮能够看到的 Context。聊天框里的最后一句只是其中一部分;售后政策、工具说明或订单查询结果如果没有被应用放进这次请求,模型就无法凭空知道它们。

很多看起来像“模型拥有的新能力”,本质上是应用在调用模型之前,把更合适的信息组织进了 Context。模型不知道本地仓库的内容,但应用可以先读取文件,再把内容交给模型;模型不会永久记住用户偏好,但应用可以在下一次请求前重新检索并注入这些偏好。

2.3 RAG 与 Memory:信息从哪里进入 Context

上一节示例中的售后政策和用户偏好,并不是模型凭空知道的。应用需要先从外部存储中找到它们,再放入当前 Context。RAG 和 Memory 都完成了这个“找回并注入”的过程,但两者寻找的内容不同:

能力回答的问题典型来源典型内容
RAG当前任务需要哪些外部知识知识库、代码库、数据库、实时数据产品文档、业务规则、代码、订单记录
Memory过去有哪些信息值得继续使用历史对话和历史任务用户偏好、项目约束、已确认的决策

例如,用户问:“破损商品退款时,运费由谁承担?”应用根据这个问题检索最新售后政策,把相关条款放入 Context,再让模型回答。这是 RAG:它为当前问题寻找外部知识。政策发生变化后,下一次检索到的内容也会随之变化。

如果用户曾经说过:“退款默认退回原支付渠道,不要退到平台余额。”系统可以将其提取为用户偏好。几个月后再次处理退款时,应用按需找回这条偏好并放入 Context。这是 Memory:它复用的是从过去交互中提取出的稳定信息。

Memory 并没有让模型获得人类意义上的永久记忆。真正保存信息的是模型之外的存储系统;没有被重新放入 Context 的记忆,模型这一轮仍然看不到。一个简化的 Memory 流程是:

历史交互
  → 提取值得复用的信息
  → 保存并更新
  → 根据新任务检索
  → 注入当前 Context

因此,RAG 的重点是“当前问题需要什么知识”,Memory 的重点是“过去有什么信息值得带到现在”。两者的效果都不只取决于存了多少内容,更取决于能否检索到正确、相关且没有过期的内容。RAG 的经典定义可参见 Lewis 等:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks

2.4 Skill:把处理方法带入 Context

RAG 和 Memory 解决了“模型需要知道什么”,但知道相关信息并不等于知道应该怎样完成任务。Tool 和 Skill 的区别可以概括为:

能力提供什么退款场景中的例子
Tool一项可以真实执行的操作查询订单、搜索政策、创建退款
Skill完成一类任务的方法先核对订单,再查询政策,取得确认后创建退款

继续以电商售后为例,get_ordersearch_policycreate_refund 都是 Tool。它们分别完成一次操作,却不说明应该先调用哪个、缺少信息时怎么办,以及什么情况下必须停止。团队可以把这些经验整理成“破损商品退款” Skill:

适用场景:用户反馈商品破损并要求退款

1. 收集订单号、破损情况和退款诉求
2. 使用 get_order 核对订单归属、状态和实付金额
3. 使用 search_policy 查询当前适用的售后规则
4. 信息不足时向用户补充询问
5. 说明退款金额和渠道,并取得明确确认
6. 调用 create_refund
7. 再次查询退款状态,返回真实处理结果

停止条件:订单不属于当前用户、超过退款期限或校验结果冲突时,
不得继续退款,应说明原因或转交人工客服

这份 Skill 没有增加新的底层操作能力,而是告诉 Agent 如何组合已有 Tool。它更像一份可复用的操作手册,通常包含适用场景、执行步骤、领域约束、可用工具、停止条件和验收标准。

Skill 不是所有平台都遵循的统一标准。工程上常采用渐进式加载:启动时只让模型看到 Skill 的名称和简介,确定任务相关后,再加载完整说明以及可选的脚本和资源。这样既能复用方法,也能避免无关说明占满 Context(参见 Anthropic:Agent Skills)。

三、让 Agent 可靠地完成长任务

前面的退款示例只需要少量步骤,相关信息可以放进当前 Context。任务持续几十分钟甚至几天后,消息和工具结果会越来越多,后台操作也可能在模型调用结束后才完成。此时仅靠对话历史已经不足以维持任务,需要在模型之外保存真实进度,并为失败和恢复设计机制。

3.1 Plan 与 State:记录任务真正进行到哪里

讨论长任务时,几个概念很容易混在一起:

概念回答的问题是否必须长期保存
Context模型这一轮看到了什么否,每次调用都会重新组织
State任务实际上进行到哪里是,应该由运行时持久化
Transcript过去具体发生了什么按需保存,用于恢复、审计和调试
Memory哪些历史信息值得未来任务复用按价值保存,不等于完整历史

Plan 可以看作 State 的一部分。对于长任务,它不宜只是模型临时生成的一段文字,而应该成为模型和运行时共同维护的结构化数据。例如:

id
description
status: pending / in_progress / completed / failed
blocked_by
artifacts
verification

模型可以提出任务拆分和状态更新,运行时则负责保存状态、检查依赖和调度执行。安装依赖、编译、测试等耗时操作可以在后台运行;完成后,运行时再把结果作为新事件交给 Agent Loop。

State 外置之后,即使发生上下文压缩、模型切换或会话恢复,系统也可以从真实进度继续工作,而不是要求模型从一长串历史消息中猜测任务做到哪里。

3.2 上下文压缩:保留信息,而不是保留所有文字

随着 Agent 不断读取文件和执行工具,Context 会持续膨胀。全部保留会增加成本并分散模型注意力,直接删除又可能丢失目标和关键证据。处理这些信息时,可以先判断它们能否恢复:

可以重新获取       → 保存位置或标识,需要时重新读取
可以结构化表达     → 写入 State
未来任务仍有价值   → 写入 Memory
只能从对话中理解   → 保留原文或进行有损摘要

例如,完整测试日志可以保存到文件,Context 中只保留失败摘要和文件路径;任务进度可以写入 State,不必反复携带所有讨论过程;已经确认的长期编码约束可以进入 Memory。只有无法从其他位置恢复的对话信息,才需要谨慎摘要。

因此,上下文压缩的首要目标不是“尽量短”,而是“压缩后仍然可以继续正确执行”。目标、约束、关键证据和下一步计划一旦丢失,Agent 即使继续运行,也可能已经偏离原任务。

3.3 错误恢复与停止条件

真实系统需要面对模型输出截断、上下文超限、限流、服务过载和工具失败。不同错误的处理方式不同:

失败类型常见处理方式
输出被截断提高输出预算或续写,并限制重试次数
上下文超限外置大结果、压缩历史、保留关键 State 后重试
限流或过载指数退避、随机抖动、模型降级或切换
工具失败根据错误类型决定重试、修改参数或停止
重复调用检测相同动作,达到阈值后熔断

所有重试都应该有上限,所有 Loop 都应该有停止条件。权限不足、关键输入缺失、结果相互冲突时,Agent 应明确暂停并请求人工处理,而不是不断换一种说法重复尝试。

Agent 的可靠性并不来自“从不失败”,而来自失败能够被识别、记录和恢复,并且系统能在无法安全继续时停止。

四、从单 Agent 到复杂系统

4.1 Multi-Agent:只有可拆分的任务才值得并行

当一个 Agent 已经能够可靠完成任务后,才有必要考虑 Multi-Agent。增加 Agent 并不会自动提高质量;它首先带来更多 Context、通信、任务分配和结果合并成本。

Multi-Agent 比较适合以下情况:

一种常见结构是协调者—工作者模式:主 Agent 拆分任务,多个工作 Agent 在隔离的 Context 中执行,主 Agent 收集结果并统一验收。

用户目标
  → 主 Agent 拆分并分配任务
      → Agent A:查资料
      → Agent B:实现代码
      → Agent C:独立检查
  → 主 Agent 汇总、处理冲突并验收

真正需要共享的是结构化的任务状态和产物,而不是让所有 Agent 看见同一份超长对话。任务所有权、依赖关系和完成状态也应该由运行时记录,不能只依赖 Agent 在自然语言中声称“正在处理”或“已经完成”。

4.2 持续沉淀:从任务中提取 Memory 与 Skill

Agent 在执行任务时会产生大量对话、工具结果和中间决策。其中一部分可以转化为以后继续使用的能力:

记录任务过程
  → 提取稳定事实、用户偏好和操作方法
  → 分别写入知识库、Memory 或 Skill
  → 去重、处理冲突并清理过期内容
  → 在后续任务中按需检索和加载

三类内容应该分开处理:容易变化的业务事实进入可更新的知识库;跨任务仍然有效的偏好和决策进入 Memory;经过验证、可以重复执行的方法进入 Skill。

这个过程不会改变模型参数,改进的是模型之外的知识、记忆和操作规范。需要警惕的是错误经验被反复固化:只有已经验证、相对稳定并且具备验收标准的方法,才适合沉淀为 Skill。

结语

一次 LLM 调用只负责根据当前 Context 生成文本或工具调用意图。围绕这次调用,Prompt 表达目标,RAG 和 Memory 补充信息,Tool 连接外部系统,Skill 复用方法,Workflow 固化流程,Agent 根据反馈动态推进,State 保存真实进度,Evaluation 验证结果。

Agent 因此不是一种脱离大语言模型的新技术,而是一套围绕模型能力边界建立起来的执行系统。模型能力还会继续演进,但工程上的基本问题不会消失:如何提供恰当的信息,如何约束真实操作,如何维护任务状态,以及如何让系统在失败时安全停止。把这些边界设计清楚,比给系统贴上“Agent”标签更重要。