什么是 Agent:从工程角度理解 Agent 的最小定义

本文是「Agent 基础与工程」系列专栏的第 1 篇。专栏总览参见:《Agent 基础认知与工程架构全景》。


很多人接触 Agent 时,容易把它理解成几样熟悉的东西:更聪明的聊天机器人、一段自动调函数的 Prompt,或者串联了几个步骤的流水线。

这些理解各占一部分,但都不够准确。在实际编码和系统设计中,Agent 最核心的特征在于:它是一个围绕目标持续推进任务的闭环系统。


工程视角的 Agent 定义

从软件工程与系统控制的角度看,一个 Agent 通常具备以下四项基本特征:

  1. 目标驱动:接收一个明确的任务目标(如“排查测试用例失败原因并给出修复补丁”),生命周期由目标达成与否决定,而不局限于单轮问答。
  2. 状态维护:在内存或持久化存储中维护任务进度、已尝试路径和中间观测数据。
  3. 环境交互:通过工具读写外部系统(如文件、终端命令、网络请求),获取运行期数据并产生实际影响。
  4. 自适应调整:工具执行报错或返回不符合预期时,能把错误信息作为新的输入,在循环中调整下一步动作。

Agent 的重点不在于模型有多全知,而在于外层的执行循环能否稳定驱动任务走向终态。


核心组成:Agent = LLM + Context + Tools

一个最小 Agent 系统可以用这三要素概括:

三者在运行时的分工很明确:

部件 核心职责 工程注意点
LLM(大语言模型) 推理中枢。根据眼前上下文推断意图,决定输出文本还是发起工具调用。 模型无自带跨轮状态记忆;模型生成内容需经校验。
Context(上下文工作集) 当前推理步骤的输入。包含系统指令、近期对话轨迹、当前任务状态与工具返回值。 需控制上下文体积,避免超长导致注意力分散或超预算。
Tools(工具) 系统的感知与执行接口。包含文件读取、命令行执行、API 请求等。 参数定义要严密,输出内容要控制信噪比,避免原始日志淹没上下文。
Harness(宿主运行时) 驱动循环的代码外壳。负责发起模型请求、分发工具调用、记录状态和执行熔断。 核心控制逻辑均在 Harness 中,不能依赖模型自觉退出。

在实际工程中,Context 的编排质量和 Tools 的参数/返回设计,直接决定了系统能否稳定跑完全程。


最小运行闭环

对比普通单轮 LLM 应用与 Agent 的拓扑结构:

1. 普通 LLM 应用(单向开环)

单次输入生成单次输出。哪怕接入了单轮 RAG,也是线性的“检索 拼装 生成”,中间如果检索失败或格式错误,系统无法在当次请求内自我纠正。

2. Agent 闭环(动态反馈)

以“排查服务端口占用”为例:

  1. 第 1 轮:Agent 接收目标,调用 exec_cmd("lsof -i :8080") 工具;
  2. 第 2 轮:工具返回进程号 PID 14201,模型判断需要确认进程名,调用 exec_cmd("ps -p 14201 -o comm=");
  3. 第 3 轮:工具返回 node,证据链完整,模型决定不再调用工具,输出排查结论并给出释放建议。

每一步动作都由上一步的物理观测决定,通过连续多轮的局部推进逼近目标。


原生 Python 最小实现

下面是一个不依赖 LangChain 或其他三方 Agent 框架、直接基于原生 API 协议编写的最小 Agent 循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import json
from openai import OpenAI

client = OpenAI()

# 1. 定义工具函数
def read_file(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as f:
# 限制读取长度,避免大文件撑爆上下文
return f.read(2000)
except Exception as e:
return f"Error reading file: {str(e)}"

# 2. 定义提供给模型的工具契约
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取本地指定路径文件的文本内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "目标文件路径"}
},
"required": ["path"]
}
}
}
]

tool_map = {"read_file": read_file}

# 3. 核心执行循环
def run_agent(goal: str, max_turns: int = 10):
messages = [
{"role": "system", "content": "你是一个代码排障助手。通过调用工具检查文件,得出结论后回答用户。"},
{"role": "user", "content": goal}
]

for turn in range(max_turns):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)

msg = response.choices[0].message
messages.append(msg)

# 模型没有发起工具调用,说明得出最终结论,退出循环
if not msg.tool_calls:
return msg.content

# 遍历处理工具调用
for tool_call in msg.tool_calls:
fn_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)

tool_fn = tool_map.get(fn_name)
observation = tool_fn(**args) if tool_fn else f"Error: Tool {fn_name} not found"

# 将工具结果配对追加到消息轨迹中
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(observation)
})

return "达到最大执行轮数,任务强制终止。"

这段代码展示了 Agent 运行时的几个关键细节:

  • 模型仅负责生成调用意图:大模型并不执行代码,它只输出结构化的 tool_calls。真正调用物理函数、处理异常并把数据传回的是宿主程序。
  • 协议因果对齐:API 要求每一个 tool_call_id 必须在后续追加一个 role: "tool" 且携带相同 ID 的消息,否则上下文校验直接失败。
  • 硬性退出控制:max_turns 是宿主必须具备的熔断保护,防止模型由于非预期反馈陷入无限调用。

典型系统的模块拆分

在生产系统中,最小循环之外通常会扩展以下模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
目标与约束输入
│
▼
上下文组装 (Context) ◄── 长期记忆 / 向量知识库
│
▼
推理中枢 (LLM)
│
▼
安全与权限校验 (Guardrails)
│
▼
工具执行沙箱 (Tools) ──► 物理观测回传 ──► 更新状态机
  • 状态机(State Machine):记录 Todo 清单、当前阶段和已验证结论,方便任务恢复和断点续跑。
  • 工具沙箱(Tool Sandbox):对 Bash 或写文件操作施加目录限制和权限审批,不直接在生产宿主机裸跑。
  • 上下文修剪(Context Pruning):当轨迹过长时截断旧日志,保护模型注意力。

常见认知误区

  1. 把单轮工具调用当成完整 Agent:一次性翻译前查个词典属于工具增强型问答。没有根据返回结果进行二次决策并多步推进的,不算闭环 Agent。
  2. 脱离基础单 Agent 盲目堆叠多角色:在单 Agent 的状态追踪、工具契约和异常捕获还没写稳之前,拆分多个“角色”对话只会迅速放大通信成本与幻觉概率。
  3. 把安全和停止条件写在 Prompt 里:Prompt 无法提供 100% 确定性保证。单次能跑几轮、能否调用高危命令,必须由代码层硬编码拦截。

小结

  • Agent 的本质是由代码宿主驱动、以大模型为决策组件的闭环任务系统。
  • 核心公式是 ,系统的鲁棒性由上下文设计、工具契约与外层调度共同决定。
  • 循环反馈机制让系统具备了处理不确定性和逐步修正的能力。

下一篇我们将梳理日常系统设计中极易混淆的两个概念:工作流与智能体——《Workflow 和 Agent 的区别:理清工作流、LLM App 与智能体的工程边界》。


系列导航与参考