重新认识Agent:一个基于自然语言的操作系统
什么是Agent,给LLM套个壳吗?这个问题你会怎么回答,把答案背一遍吗?
最近我在思考一个问题:随着大模型能力越来越强,Agent 系统的形态也在发生变化。它不再只是一个“会调用工具的聊天机器人”,而越来越像一个由大模型驱动的运行时系统。
如果做一个简单 Demo,Agent 可能只是:
LLM + Prompt + Tools + while loop
但如果要做一个完整可用、能接入真实业务系统、能稳定运行的 Agent,它的复杂度会迅速上升。此时,它已经不只是一个应用层功能,而更像一个小型操作系统。
换句话说:
Demo Agent 是脚本;
产品级 Agent 是运行时系统;
复杂 Agent 平台则接近一个 Agent OS。
一、LLM 越来越像“自然语言处理器”
可以把大模型理解成一种“自然语言处理器”。
传统 CPU 执行的是机器指令,比如:
LOAD R1, [address]
ADD R1, R2, R3
JMP label
而大模型处理的是自然语言任务,然后生成工具调用、推理步骤和结果整合方案。例如:
{
"name": "search_documents",
"arguments": {
"query": "context compression in agent systems",
"top_k": 5
}
}
从这个角度看,LLM 不只是负责回答问题,它更像一个控制核心:理解任务、规划步骤、选择工具、生成参数、解释结果、整合答案。
但 LLM 又不是传统 CPU。CPU 执行指令是确定性的,而 LLM 的输出是概率性的。因此,它可能选错工具、生成错误参数、漏掉步骤,或者错误解释工具结果。
所以更准确地说,LLM 像一个:
自然语言解释器
+ 任务调度器
+ 模糊控制器
+ 工具调用生成器
它具有很强的语义理解和规划能力,但并不天然可靠。因此,工程上不能完全相信它,而要用 Runtime、状态机、权限控制和结果校验来约束它。
二、Tool 像 Agent 的“指令集”
如果把 LLM 看作处理器,那么 Tool 就很像它的指令集。
CPU 通过指令集定义自己能做什么;Agent 通过 Tool Schema 定义模型能调用哪些外部能力。
例如,一个研究型 Agent 可能有这些工具:
search_paper()
parse_pdf()
retrieve_chunks()
update_knowledge_graph()
summarize_experiment()
一个办公 Agent 可能有这些工具:
read_email()
send_email()
create_calendar_event()
query_database()
generate_report()
这些工具就是 Agent 能执行的“外部操作原语”。
更进一步,Tool 可以分成两类:
普通工具 ≈ 函数 / 普通指令
外部工具 ≈ 系统调用
有副作用工具 ≈ 特权指令
比如:
calculate()
format_json()
extract_entities()
这些更像普通函数或普通指令。
而:
send_email()
delete_file()
create_order()
modify_database()
charge_payment()
这些就更像系统调用甚至特权指令,因为它们会影响外部世界,具有副作用,不能随便执行。
这意味着,设计 Tool 不只是“写几个函数给模型用”,而是在设计 Agent 的指令集。
一个好的 Tool 设计应该满足:
语义清晰
参数明确
粒度适中
返回结构化
副作用可控
权限边界清楚
错误信息可处理
如果 Tool 设计得很混乱,比如功能重叠、描述模糊、参数复杂、返回结果太长,模型就很容易选错工具、填错参数,Agent 的整体行为也会变得不稳定。
所以,Agent 工程中一个非常重要的观点是:
Tool design is instruction set design.
工具设计,本质上就是为大模型设计一套可执行的外部指令集。
三、Agent Runtime 像操作系统
LLM 本身并不能真正访问数据库、文件、浏览器、邮件、日历或业务系统。它只是生成“我要调用什么工具,用什么参数”。
真正负责执行的是 Agent Runtime。
因此,Agent Runtime 很像操作系统。
操作系统负责管理:进程、内存、文件、权限、设备、调度、异常、资源、安全
Agent Runtime 也要管理类似的东西:
| 操作系统 | Agent 系统 |
|---|---|
| 进程调度 | Agent workflow / node 调度 |
| 指令执行 | Tool calling |
| 内存管理 | Context / Memory 管理 |
| 文件系统 | 文档库 / 向量库 / 知识库 |
| 设备驱动 | API / MCP Server / 外部工具 |
| 权限控制 | Tool permission / Human confirmation |
| 中断处理 | 用户打断 / 工具异常 / 超时 |
| 日志系统 | Trace / Audit log |
| 资源限制 | Token budget / Cost budget / Rate limit |
| 沙箱机制 | Tool sandbox / 安全边界 |
| 任务队列 | Async jobs / Background execution |
| 错误恢复 | Retry / Fallback / Rollback |
从这个角度看,Agent Runtime 的职责远远不只是“帮模型执行函数”。
它需要负责:工具注册、工具调度、参数校验、权限控制、并发执行、超时处理、错误恢复、状态保存、成本限制、日志追踪、结果压缩、安全隔离
真正难的不是让模型调用工具,而是让模型在复杂环境中稳定、安全、低成本地调用工具。
四、Context 和 Memory 像 Agent 的内存系统
在传统计算机系统里,CPU 需要 RAM、Cache、Disk 等多级存储系统。
Agent 也一样。
Agent 的“内存”可以分成几层:
当前上下文窗口:短期工作内存
对话历史:会话级记忆
向量数据库:语义记忆
结构化数据库:事实记忆
知识图谱:关系记忆
文件系统:长期资料存储
不过,LLM 的内存有一个特殊问题:它不像 RAM 那样可以随机访问。模型每次调用时,只能看到上下文窗口里的内容。
这就导致一个重要问题:
模型 API 通常是无状态的。
如果每次调用都需要 50KB 上下文,那么客户端通常每次都要重新发送这 50KB 内容。
虽然部分模型服务可能提供 prompt caching 或 context caching,但工程上不能默认服务端会自动帮你保存上下文。默认应该按照无状态 API 来设计。
因此,长上下文 Agent 最容易出现的问题是:
每次模型调用都重复发送大量上下文
工具调用轨迹越来越长
RAG 结果越塞越多
多轮推理成本持续膨胀
最终答案不长,但后台 token 消耗巨大
所以,产品级 Agent 必须有上下文管理模块。
它要决定每次模型调用时到底带什么内容:
哪些历史必须保留?
哪些工具结果可以压缩?
哪些中间步骤可以丢弃?
哪些事实应该写入长期记忆?
哪些 RAG 结果真正相关?
这很像操作系统里的内存管理、缓存淘汰、分页和换入换出。
一个好的 Agent 不能无脑把所有历史塞进 prompt,而应该做到:
只传当前步骤真正需要的信息。
五、长上下文下,为什么要减少高频 LLM 调用?
Agent 的调用成本不能只看输出 token,而要看每次调用的完整输入和输出。
一次 LLM 调用的 token 成本大致包括:
系统提示词
开发者提示词
工具定义
对话历史
agent工作记忆
检索上下文
用户当前
工具调用结果
模型输出
如果上下文已经达到几十 KB,那么每多一次 LLM 调用,就可能意味着大量上下文被重复发送。
可以用一个简单公式估算:
总成本 ≈ 调用次数 × 固定上下文成本
+ 动态输入总量
+ 工具返回总量
+ 模型输出总量
也就是:
Total ≈ N × C_fixed + ΣC_dynamic + ΣC_output
其中:
N = LLM 调用次数
C_fixed = 每轮重复发送的上下文
C_dynamic = 每轮新增输入、工具结果、检索内容
C_output = 模型输出
如果固定上下文很大,那么高频小调用会非常昂贵。
比如每次调用都要带 20k tokens 输入:
调用 1 次:约 20k input tokens
调用 5 次:约 100k input tokens
调用 20 次:约 400k input tokens
用户可能只看到几百 tokens 的最终答案,但后台已经消耗了大量 token。
因此,长上下文 Agent 的核心优化方向是:
减少 LLM 调用次数
减少重复上下文
压缩工具结果
只传必要状态
把确定性逻辑交给代码
把低价值判断交给小模型或规则
六、工具并行:像指令级并行
既然 LLM 调用很贵,而很多工具调用相对便宜,那么实际工程中就可以让模型一次性规划多个工具调用,由后端并行执行。
这类似于处理器中的指令级并行。
低效的 Agent 流程可能是:
LLM 判断是否需要检索
↓
调用检索工具
↓
LLM 看结果后决定是否查知识图谱
↓
调用知识图谱工具
↓
LLM 看结果后决定是否查数据库
↓
调用数据库工具
↓
LLM 生成答案
这种模式会导致多次 LLM 调用,每次都重复发送上下文。
更好的流程是:
LLM 一次性生成工具计划
↓
后端并行调用向量库、BM25、知识图谱、数据库
↓
工具层去重、过滤、rerank、压缩
↓
LLM 基于压缩结果生成答案
这样可以减少:
LLM 调用次数
重复上下文输入
网络往返延迟
Agent action-observation 轮数
不过要注意,工具并行不等于把所有工具结果都塞回模型。
并行工具调用后,必须做结果处理:
去重
过滤
排序
截断
摘要
结构化提取
只保留有价值证据
否则你虽然减少了 LLM 调用次数,却可能让工具结果撑爆上下文窗口。
七、投机式工具调用:Agent 里的“分支预测”
还可以进一步借鉴 CPU 的分支预测思想。
在 CPU 中,分支预测是指:
我还不知道 if 分支会走哪边,
但为了不让流水线停下来,
先预测一个分支并提前执行。
Agent 中也可以有类似策略。
假设有四个工具:
ToolA
ToolB
ToolC
ToolD
正常流程是:
先调用 ToolA
↓
根据 ToolA 的结果,再决定是否调用 ToolB / ToolC / ToolD
ToolB、ToolC、ToolD都有可能被调用。但如果 ToolB、ToolC、ToolD 都是只读、低成本、无副作用的工具,而 LLM 调用很贵,那么可以采用投机式工具调用:
一次性调用 ToolA + ToolB + ToolC + ToolD
↓
等待结果返回
↓
根据 ToolA 的结构化结果,决定采用 B / C / D 中哪些结果
↓
丢弃无效分支结果
本质上,这是用便宜的工具冗余调用,换取昂贵的 LLM 调用次数减少。
这可以称为:
Speculative Tool Execution
投机式工具执行
它适合的场景是:
工具是只读的
工具调用成本低
工具无副作用
工具结果量可控
多个分支有较高概率被用到
分支选择规则可以结构化判断
例如订单查询场景:
ToolA:查订单状态
ToolB:查物流信息
ToolC:查退款记录
ToolD:查客服工单
用户问:“我的订单怎么回事?”
可以同时查:
订单状态 + 物流 + 退款 + 客服工单
然后根据订单状态决定主要采用哪部分结果:
已发货 → 使用物流信息
已退款 → 使用退款记录
有争议 → 使用客服工单
但投机式工具调用有一个重要边界:不能用于有副作用的工具。
比如:
发送邮件
删除文件
创建订单
修改数据库
发起付款
预约会议
这些工具不能因为“可能会用到”就提前执行。否则即使最后不用,副作用也已经发生了。
因此,投机工具调用只适合:
查询型工具
只读工具
幂等工具
可取消工具
低风险工具
八、Agent Workflow 像程序,LangGraph 像运行时框架
简单 Agent 循环通常是:
while not done:
LLM decides next action
execute tool
append observation
这种方式适合 Demo,但不适合复杂系统。原因是:
流程不可控
状态不清晰
容易死循环
难以调试
难以插入规则节点
难以做权限控制
难以恢复失败
对于复杂 Agent,更好的方式是用图结构或状态机来控制流程。
例如 LangGraph 这类框架提供了:
节点:执行阶段
边:状态转移
State:显式状态
Conditional Edge:条件分支
Checkpoint:状态保存
Human-in-the-loop:人工确认
这就更像一个 Agent Runtime。
一个复杂研究型 Agent 可以设计成:
Planner Node
↓
Retriever Node
↓
Reranker Node
↓
Evidence Compressor Node
↓
Reasoning Node
↓
Critic Node
↓
Answer Generator Node
或者对于投机工具调用,可以设计成:
Planner Node
↓
Speculative Tool Executor Node
↓
Branch Selector Node
↓
Result Compressor Node
↓
Answer Generator Node
这里的关键思想是:
模型负责语义理解和不确定性判断;
代码负责确定性流程控制;
Graph 负责状态流转;
Runtime 负责工具执行、安全边界和错误恢复。
也就是说,产品级 Agent 不能完全依赖模型自由发挥,而应该把模型放在受控的运行时系统中。
九、产品级 Agent 至少需要哪些模块?
如果把 Agent 当作一个小型操作系统,那么一个产品级 Agent 至少需要以下模块。
1. 任务调度模块
负责决定 Agent 当前处于什么阶段:
理解任务
制定计划
调用工具
观察结果
修正计划
生成答案
结束任务
2. 上下文管理模块
负责决定每次 LLM 调用时带什么内容:
系统提示词
当前任务
历史对话
工具调用轨迹
RAG 结果
长期记忆
中间状态
目标是:
只带当前必要的信息,不把所有内容都塞进上下文。
3. 工具管理模块
负责:
工具注册
工具描述
参数校验
权限控制
超时控制
重试机制
结果格式化
副作用隔离
4. 状态管理模块
Agent 不能只靠 prompt 记住自己做了什么,必须有显式状态:
{
"task_id": "xxx",
"goal": "分析几篇论文",
"current_step": "retrieval",
"retrieved_docs": [...],
"used_tools": [...],
"pending_questions": [...],
"token_budget": 12000,
"errors": []
}
显式状态可以让流程更稳定,也方便调试和恢复。
5. 错误处理模块
真实工具一定会失败:
API 超时
数据库连接失败
检索无结果
权限不足
参数格式错误
返回内容为空
模型输出 JSON 解析失败
因此需要:
retry
fallback
skip
ask user
degrade gracefully
rollback
6. 成本控制模块
Agent 很容易失控:
模型调用过多
上下文越来越长
工具结果越来越多
循环无法停止
RAG 返回太多 chunk
多 Agent 互相对话膨胀
所以要设置:
最大调用次数
最大 token budget
最大工具调用次数
最大执行时间
最大递归深度
明确终止条件
7. 可观测性模块
产品级 Agent 必须能 debug。
需要记录:
每次 LLM 输入输出
每次工具调用参数
工具返回结果
节点执行路径
token 消耗
耗时
失败原因
最终答案来源
否则 Agent 出错时,很难判断问题到底出在哪里:
是检索错了?
工具选错了?
参数错了?
模型理解错了?
上下文污染了?
还是状态流转错了?
可观测性不是锦上添花,而是 Agent 工程化的基础设施。
十、Agent 工程的核心不是 Prompt,而是 Runtime
很多人刚开始做 Agent,会把重点放在 Prompt 上:
怎么写提示词?
怎么让模型听话?
怎么让模型规划?
但做深以后会发现,真正难的是:
状态怎么管理?
上下文怎么裁剪?
工具怎么注册?
权限怎么控制?
错误怎么恢复?
调用链怎么追踪?
成本怎么限制?
结果怎么验证?
任务什么时候终止?
这已经不是单纯的 NLP 问题,而是系统设计问题。
因此,Agent 可以用一个公式表示:
Agent = LLM + Runtime + Tools + Memory + Workflow + Guardrails + Observability
LLM 是核心,但不是全部。
真正决定 Agent 是否可用的是:
Runtime
状态管理
工具治理
上下文管理
权限控制
错误恢复
成本控制
可观测性
十一、Agent 与操作系统的完整类比
可以用下面这张表总结:
| 计算机系统 | Agent 系统 |
|---|---|
| CPU | LLM |
| 指令集 ISA | Tool Schema / Function Calling |
| 程序 | Prompt / Workflow / User Task |
| 操作系统 | Agent Runtime |
| 进程调度 | Agent Node 调度 |
| 内存 | Context / Short-term Memory |
| 硬盘 | Vector DB / Document Store / Knowledge Base |
| Cache | Prompt Cache / Retrieved Context |
| 系统调用 | External Tool / API Call |
| 特权指令 | 有副作用工具 |
| 设备驱动 | MCP Server / API Adapter |
| 中断 | 用户打断 / 工具异常 |
| 分支预测 | Speculative Tool Execution |
| 流水线 | Multi-stage Agent Workflow |
| 多核 | Multi-Agent / Parallel Tool Execution |
| 权限环 | Tool Permission / Human Confirmation |
| 日志系统 | Trace / Audit / Observability |
| 资源限制 | Token Budget / Rate Limit / Cost Control |
这个类比并不是为了炫技,而是能帮助我们更清楚地设计 Agent。
当我们把 LLM 看成处理器,把 Tool 看成指令集,把 Runtime 看成操作系统,很多工程问题就变得清晰了:
工具设计 => 指令集设计
上下文管理 => 内存管理
工具调用 => 系统调用
权限控制 => 特权管理
调用链追踪 => 系统日志
成本控制 => 资源调度
投机工具调用 => 分支预测
多工具并行 => 指令级并行
多 Agent 协作 => 多核系统
这说明,Agent 工程不是简单地“调 Prompt”,而是在构建一个由大模型驱动的运行时系统。
十二、结论:未来的 Agent 更像一个操作系统
我越来越觉得,未来的复杂 Agent 不会只是一个聊天框,也不会只是几个 API 工具的拼接。
它更像一个新的计算系统:
LLM 是自然语言处理器;
Tool 是它的指令集;
Agent Runtime 是操作系统;
Context 是内存;
Memory / RAG 是外部存储;
Workflow 是程序;
Guardrails 是安全机制;
Observability 是调试系统。
但是和传统操作系统不同,Agent OS 的核心是不确定的。
传统 CPU 执行指令是确定性的,而 LLM 的指令生成是概率性的。因此,Agent Runtime 必须比传统运行时承担更多责任:
约束模型行为
校验工具参数
控制副作用
验证工具结果
压缩上下文
限制资源消耗
处理异常路径
保留审计日志
也正因为如此,一个完整可用的 Agent,难度并不低。它不是“LLM 加几个工具”就能完成的,而是一个系统工程问题。
一句话总结:
Demo Agent 是脚本,产品级 Agent 是运行时系统,复杂 Agent 平台则接近一个小型操作系统。
如果未来大模型继续发展,Agent 的能力可能会逐渐内化进模型本身。但在真实工程中,只要 Agent 需要访问外部世界、调用工具、管理状态、保证安全和控制成本,Runtime 就依然不可或缺。
因此,真正有价值的 Agent 开发能力,不只是会写 Prompt,也不是只会接几个工具,而是能够从系统角度设计:
工具体系
状态流转
上下文管理
权限边界
错误恢复
成本控制
可观测性
这才是从 Demo Agent 走向产品级 Agent 的关键。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)