AI Agent 不是“加了一段超长系统提示词的 LLM”。真正的 Agent 是一个闭环系统:模型可以选择行动、观察结果、更新状态,并持续运行,直到满足明确的停止条件。模型负责判断,外围运行时负责工具、权限、状态恢复和结果验证。
很多 Agent 项目失败,并不是因为模型不会推理,而是因为系统把宽泛的权限、含糊的工具和不可追踪的状态交给了一个具有不确定性的模型,却没有可靠机制判断任务是否真的完成。
本文从工程决策出发,构建一套生产级 Agent 架构,并给出可以运行的 Go Runtime。
核心结论
- 路径确定时用固定工作流,只有需要模型动态选择路径时才使用 Agent。
- 把 Agent Loop 当作有预算、有状态、有停止条件的状态机。
- 工具必须单一职责、强类型、最小权限;工具返回值同样是不可信输入。
- 区分运行状态、会话历史和长期记忆,它们的生命周期与隐私风险不同。
- 发送消息、转账、删除和发布等高影响操作必须经过人工审批。
- 不只评估最终文本,还要评估执行轨迹与真实业务结果。
- 默认从单 Agent 开始,只有权限隔离、上下文隔离或并行收益明确时才增加 Agent。
什么是 AI Agent?
Anthropic 对两种系统做了清晰区分:**工作流(Workflow)**由代码预先定义执行路径;Agent则由模型在策略约束内动态决定步骤和工具。二者都可以包含 LLM,但运行风险不同。
| 系统 | 谁决定下一步 | 适用场景 | 主要优势 |
|---|---|---|---|
| 单次 LLM 调用 | 应用代码 | 分类、抽取、改写 | 成本与延迟最低 |
| 确定性工作流 | 应用代码 | 稳定的业务流程 | 可预测、易测试 |
| Agent | 策略约束内的模型 | 开放式调查与执行 | 能适应未知路径 |
只有同时满足以下三个条件,Agent 的复杂度才通常值得:
- 任务需要多个步骤或工具;
- 正确步骤无法提前完全确定;
- 环境能持续反馈可验证的结果。
如果一个任务可以通过单次调用、RAG 加单次调用或固定 DAG 完成,引入 Agent 往往只会增加延迟、成本和故障面。Anthropic 的官方工程建议也是从最简单的方案开始,只有效果收益能够覆盖代价时才增加自主性。
生产级 Agent 架构
可以用一个公式概括:
Agent = 模型 + Harness + 工具 + 运行环境
模型理解目标并选择动作;Harness管理指令、状态迁移、预算、护栏和异常恢复;工具提供边界清晰的能力;运行环境决定进程能够访问哪些文件、网络、凭证和服务。
这也是**有界自主性(Bounded Autonomy)**的工程含义:模型可以在允许集合内选择动作,但不能扩张自己的身份、权限、预算、网络或完成标准。AWS Agentic AI Lens 把这些边界、Trace、评测与人工监督定义为架构问题,而不是 Prompt 写作技巧。
循环本身并不复杂。真正的工程工作,是为图中的每条边定义 Schema、超时、负责人和失败策略。
1. 目标与策略
先把用户请求翻译为执行契约:
- 预期结果;
- 允许与禁止的动作;
- 数据访问边界;
- 时间与成本预算;
- 必须人工确认的动作;
- 可验证的完成标准。
“解决客户退款问题”过于含糊。更安全的契约是:“读取订单与支付状态,解释退款资格并生成退款草稿;金额超过策略配置的阈值或涉及修改订单时必须审批。”这里的阈值只是示例策略,不是通用业务规则。
提示词不是权限系统。权限必须由应用代码和工具边界强制执行。
2. 上下文与状态
上下文是模型本轮决策可见的信息,状态是运行时维护的权威记录。不要让不断增长的聊天记录同时承担两者。
| 状态类型 | 范围 | 示例 | 存储原则 |
|---|---|---|---|
| 运行状态 | 当前任务 | 订单 ID、已完成步骤、重试次数 | 关键状态迁移后 Checkpoint |
| 会话状态 | 用户线程 | 澄清信息、历史回答 | 只保留产品真正需要的周期 |
| 长期记忆 | 跨会话 | 稳定的用户偏好 | 选择性写入并记录来源 |
| 业务状态 | 源系统 | 支付、库存、物流状态 | 每次从权威系统读取 |
检索必须服务于下一步决策:只加载必要上下文,保留来源和时间戳,对变化频繁的业务事实进行实时读取。向量数据库适合语义召回,但不能成为余额、权限或订单状态的影子数据源。
3. 工具设计
工具是能力边界,而不是内部 API 的随手封装。好的工具会让正确操作简单,让危险操作困难。
package refund
import (
"errors"
"regexp"
)
var orderIDPattern = regexp.MustCompile(`^ord_[a-zA-Z0-9]+$`)
type Draft struct {
OrderID string
AmountCents int64
Reason string
}
func (draft Draft) Validate(maxAmountCents int64) error {
if !orderIDPattern.MatchString(draft.OrderID) {
return errors.New("invalid order ID")
}
if draft.AmountCents <= 0 || draft.AmountCents > maxAmountCents {
return errors.New("refund amount is outside policy")
}
switch draft.Reason {
case "duplicate", "damaged", "not_received":
return nil
default:
return errors.New("unsupported refund reason")
}
}
工具应遵循以下规则:
- 一个工具只承担一个明确职责;
- 输入强类型,输出结构化;
- 明确副作用与失败模式;
- 在工具内部执行身份、权限和租户隔离;
- 设置超时、有界重试和幂等键;
- 只返回决策需要的证据,不返回整表数据或无限日志;
- 把工具描述和工具结果都当作不可信数据处理。
MCP 规范对可互操作工具提出了相同的安全原则:用户应理解暴露给模型的能力,并对敏感调用保留控制权。MCP 解决标准化发现和传输,但不会替应用完成业务鉴权。
4. Agent Loop 与停止条件
运行时持续执行四步:
- 根据目标和状态构建上下文;
- 请求模型选择工具、输出答案或升级人工;
- 执行获准动作并记录观察结果;
- 更新状态并检查停止条件。
每个循环都必须有硬限制:
const (
maxTurns = 12
maxToolCalls = 20
maxWallSeconds = 90
maxCostMicros = 500_000
)
这些限制只是起始示例。金额应使用整数单位,避免浮点数误差。实际值应根据工作负载延迟、供应商价格、重试行为和中断的业务影响推导,并由运行时强制执行,而不能只写在 Prompt 中。
结果已验证、需要用户决策、连续重复且没有进展或预算耗尽时,系统必须停止。“一直重试直到成功”不是生产策略。
5. 护栏与人工审批
护栏需要分层部署:
- 输入层:识别不支持的请求、Prompt Injection 和敏感数据;
- 模型决策层:根据用户和任务动态限制可见工具;
- 工具输入层:校验 Schema、权限和业务规则;
- 工具输出层:清洗不可信内容并限制大小;
- 最终输出层:校验必要字段、引用与合规要求。
发送消息、资金操作、删除数据、发布内容和授予权限等操作必须设置人工审批。审批界面应展示具体动作和参数,而不是笼统询问“是否允许 Agent”。
关键词是 Runtime Enforcement。OWASP Agent Control Standard 通过框架无关的 Middleware Hook 检查并控制 Agent 的运行时行为。即使团队不直接采用该标准,架构也应在模型调用、工具分发、外部写入、记忆更新和最终交付前提供等价的拦截点。
6. 可观测性与评测
除延迟、错误率、资源和成本外,Agent 还需要记录:
- 模型请求与响应元数据;
- 工具名称和校验后的参数;
- 工具结果、耗时与错误类别;
- 状态迁移与重试原因;
- 审批事件与操作人;
- 最终结果及验证结论。
Trace 不应包含密钥或无限制的客户数据;导出前进行脱敏,并为 Trace 与业务记录分别定义保留周期。
| 评测层 | 核心问题 | 示例指标 |
|---|---|---|
| 结果 | 用户目标是否完成 | 正确解决率 |
| 轨迹 | 执行路径是否合理 | 非法调用、循环、冗余步骤 |
| 运营 | 是否高效可靠 | p95 延迟、成本、人工升级率 |
评测集应来自真实且匿名化的任务,覆盖正常案例、歧义请求、工具故障、越权操作、Prompt Injection 和数据缺失。每次修改模型、Prompt、工具 Schema 或编排逻辑都应回归。文本看起来专业并不代表真实操作正确,业务结果验证才是发布门禁。
发布完整 Agent,而不只是替换模型
Agent 的生产行为来自多个持续变化的组件,应把它们作为一个版本化发布单元:
| 发布组件 | 上线前需要的证据 |
|---|---|
| 模型与 Provider 参数 | 固定标识、Fallback 路由、用量与延迟对比 |
| 指令与上下文策略 | 版本、红队用例、Context 与数据边界测试 |
| 工具 Schema 与实现 | 契约、授权、副作用与幂等测试 |
| Runtime Policy 与身份 | Principal 来源、委派 Scope、审批、过期与撤销 |
| State 与 Memory | Schema 迁移、Provenance、留存、删除与回滚 |
| Evaluator 与 Dataset | 评估器版本、数据切片覆盖、已知限制与复议流程 |
使用 Manifest 把这些版本关联到一次部署。先运行离线回归与滥用测试,再对完整 Manifest 做 Canary,按风险切片比较真实业务结果,并保留经过演练的回滚路径。即使模型和工具各自通过测试,“新模型 + 旧 Tool Schema”仍可能在组合后失败。
身份是 Runtime 边界,不能来自模型提供的 Context。Workload Identity 用于认证 Agent 服务;Delegated User Identity 用于约束它可访问的对象与动作。完成策略判断后再签发短期、Audience-bound 的凭据,并直接交给对应 Executor,不能让模型在工具之间复制 Bearer Token。NIST 于 2026 年 2 月宣布的 AI Agent Standards Initiative 把 Agent 身份与授权列为标准化研究方向;它是一项正在推进的倡议与研究计划,不是已经完成的通用认证标准。
一个可运行的 Go Agent Runtime
理解 Agent 架构最可靠的方法,是把 Runtime Contract 直接展开。下面的程序只依赖 Go 标准库。scriptedModel 是一个确定性 Model Adapter,用作测试替身,因此授权和停止逻辑可以离线运行。接入真实模型时,只需让 Provider Client 返回同一种强类型 decision,不要把 Policy、工具执行、预算和结果验证搬进模型。
创建 support_agent.go:
package main
import (
"context"
"errors"
"fmt"
)
type actionKind string
const (
callTool actionKind = "call_tool"
finish actionKind = "finish"
)
type decision struct {
Kind actionKind
Tool string
OrderID string
Final *finalAnswer
}
type finalAnswer struct {
OrderID string
Status string
Refundable bool
}
type observation struct {
Code string
OrderID string
Status string
Refundable bool
}
type runState struct {
Goal string
Turns int
ToolCalls int
Observations []observation
Result string
}
type model interface {
Decide(context.Context, runState) (decision, error)
}
type scriptedModel struct{}
func (scriptedModel) Decide(_ context.Context, state runState) (decision, error) {
if len(state.Observations) == 0 {
return decision{Kind: callTool, Tool: "get_order_status", OrderID: "ord_1001"}, nil
}
current := state.Observations[len(state.Observations)-1]
if current.Code != "ok" {
return decision{}, errors.New("cannot produce a verified answer")
}
return decision{
Kind: finish,
Final: &finalAnswer{
OrderID: current.OrderID,
Status: current.Status,
Refundable: current.Refundable,
},
}, nil
}
type authContext struct {
TenantID string
PrincipalID string
}
type order struct {
ID string
TenantID string
Status string
Refundable bool
}
type runtime struct {
Model model
Orders map[string]order
MaxTurns int
MaxToolCalls int
Checkpoint func(runState) error
}
func (agent runtime) Run(
ctx context.Context,
auth authContext,
goal string,
) (runState, error) {
state := runState{Goal: goal}
for state.Turns < agent.MaxTurns {
if err := ctx.Err(); err != nil {
return state, err
}
next, err := agent.Model.Decide(ctx, state)
if err != nil {
return state, err
}
state.Turns++
switch next.Kind {
case callTool:
if state.ToolCalls >= agent.MaxToolCalls {
return state, errors.New("tool-call budget exhausted")
}
if next.Tool != "get_order_status" {
return state, errors.New("tool is not allowlisted")
}
result, err := agent.getOrderStatus(auth, next.OrderID)
if err != nil {
return state, err
}
state.ToolCalls++
state.Observations = append(state.Observations, result)
case finish:
if len(state.Observations) == 0 || next.Final == nil {
return state, errors.New("final answer has no supporting observation")
}
current := state.Observations[len(state.Observations)-1]
if next.Final.OrderID != current.OrderID ||
next.Final.Status != current.Status ||
next.Final.Refundable != current.Refundable {
return state, errors.New("final answer contradicts tool evidence")
}
state.Result = fmt.Sprintf(
"Order %s is %s; refundable=%t.",
next.Final.OrderID,
next.Final.Status,
next.Final.Refundable,
)
if err := agent.checkpoint(state); err != nil {
return state, fmt.Errorf("checkpoint final state: %w", err)
}
return state, nil
default:
return state, errors.New("unsupported model decision")
}
if err := agent.checkpoint(state); err != nil {
return state, fmt.Errorf("checkpoint transition: %w", err)
}
}
return state, errors.New("turn budget exhausted")
}
func (agent runtime) checkpoint(state runState) error {
if agent.Checkpoint == nil {
return nil
}
return agent.Checkpoint(state)
}
func (agent runtime) getOrderStatus(
auth authContext,
orderID string,
) (observation, error) {
if auth.PrincipalID == "" || auth.TenantID == "" {
return observation{}, errors.New("authenticated context is required")
}
item, ok := agent.Orders[orderID]
if !ok || item.TenantID != auth.TenantID {
return observation{}, errors.New("order not found or not authorized")
}
return observation{
Code: "ok",
OrderID: item.ID,
Status: item.Status,
Refundable: item.Refundable,
}, nil
}
func main() {
agent := runtime{
Model: scriptedModel{},
Orders: map[string]order{
"ord_1001": {
ID: "ord_1001",
TenantID: "tenant-a",
Status: "delivered",
Refundable: true,
},
},
MaxTurns: 6,
MaxToolCalls: 2,
Checkpoint: func(state runState) error {
fmt.Printf("checkpoint turns=%d tools=%d\n", state.Turns, state.ToolCalls)
return nil
},
}
state, err := agent.Run(
context.Background(),
authContext{TenantID: "tenant-a", PrincipalID: "user-42"},
"Can order ord_1001 still be refunded?",
)
if err != nil {
panic(err)
}
fmt.Println(state.Result)
}
运行:
go run support_agent.go
这个示例让五条边界变得可观察:模型只能输出强类型决策;Runtime 维护 Tool Allowlist;工具从模型参数之外接收已认证上下文;预算阻止无限执行;最终结构化结果必须与最新 Observation 逐字段一致,Runtime 才会渲染文本。Checkpoint Hook 在关键状态迁移后运行,生产实现应原子写入持久存储,并关联 Trace ID、模型用量、Policy Decision 与 Idempotency Key。
订单 Map 只是本地 Fixture。生产工具必须实时读取源系统,在源系统执行对象归属和 Tenant 隔离,并处理并发更新。如果要支持真实退款,不要暴露单一 issue_refund。应拆成 draft_refund、绑定精确参数的审批记录和幂等 submit_refund。
2026 年如何选择框架
先定义流程、风险和运维要求,再选择运行时。流行度不是架构需求。
| 方案 | 适用情况 | 代价 |
|---|---|---|
| 直接使用模型 API | 循环短,团队需要完全控制 | 自行实现状态、工具调度、Trace 和恢复 |
| OpenAI Agents SDK | 需要轻量 Agent、工具、Handoff、Session 和 Trace | 与 OpenAI 运行时能力结合较深 |
| Claude Agent SDK | Agent 需要文件、Shell 和类似计算机的工作空间 | 强大环境要求严格沙箱 |
LangChain create_agent |
需要多模型集成与标准工具循环 | 抽象层需要严格版本管理 |
| LangGraph | 需要持久化、可恢复执行和人工中断 | 底层图编排增加实现成本 |
| CrewAI 或 AutoGen | 产品核心就是角色协作与多 Agent | 协调成本高,责任边界容易模糊 |
LangChain v1 的标准 Agent API 是 create_agent,基于 create_react_agent 的旧教程应视为迁移资料。LangGraph 的重点是持久化、Durable Execution、流式输出和 Human-in-the-loop 等编排能力,而不是隐藏 Prompt 与架构。
单 Agent 还是多 Agent?
默认从一个 Agent 和少量工具开始。只有满足以下条件之一时才增加 Agent:
- 专家能力需要独立权限边界;
- 一个领域的上下文会显著干扰另一个领域;
- 相互独立的任务能带来真实并行收益;
- 不同团队分别拥有并评测不同能力。
常见模式有两种:由 Manager Agent 保持用户交互并把专家 Agent 当作工具调用;或由分诊 Agent 将任务和必要上下文 Handoff 给专家。
避免把连续三个 Prompt 命名为“研究员、分析师、写作者”就称作多 Agent。它增加模型调用次数,却未必增加证据质量、控制力或最终效果。
上线检查清单
- [ ] 已优先评估确定性工作流;
- [ ] 成功、升级和停止条件明确;
- [ ] 每个工具都有窄 Schema 和明确副作用;
- [ ] 权限由代码强制执行,而不只写在 Prompt 中;
- [ ] 外部内容和工具结果按不可信输入处理;
- [ ] 高影响动作需要针对具体参数的人工审批;
- [ ] 重试有界,写操作具备幂等性;
- [ ] 进程故障后可以恢复运行状态;
- [ ] Trace 能连接模型决策、工具调用与业务结果;
- [ ] 敏感数据已脱敏并定义保留周期;
- [ ] 代表性评测集是发布门禁;
- [ ] 已测试回滚或紧急停止机制。
常见问题
AI Agent 和 RAG 有什么区别?
RAG为模型检索上下文;Agent 控制循环并选择行动。Agent 可以把 RAG 当作工具,但只有检索能力的系统并不等于 Agent。
Agent 必须有长期记忆吗?
不需要。很多 Agent 只需当前运行状态和对权威系统的实时读取。只有明确的用户收益足以覆盖隐私、删除、来源追踪和信息过期风险时,才应增加长期记忆。
如何防止 Agent 无限循环?
设置轮次、工具调用、时间和成本上限;识别参数等价的重复调用;要求每次观察都产生可衡量进展;无法验证进展时升级人工。
工具错误应该返回给模型吗?
返回有界且可行动的错误类别,例如 not_found、permission_denied 或 temporary_failure。不要暴露堆栈、密钥或原始基础设施响应,由运行时决定哪些错误可以重试。
发布 Agent 时应该对哪些内容做版本管理?
在同一个 Deployment Manifest 中记录模型与 Provider 参数、指令、Context Policy、Tool Schema 与实现、Runtime Policy、Identity Scope、State Migration、Evaluator 和回滚流程。必须测试组合后的整体行为,而不只是孤立测试每个组件。
什么时候应该使用 MCP?
当工具或上下文资源需要在兼容 Host 之间提供标准接口时,可以使用 MCP。MCP 改善互操作性,但 Host 仍要负责用户同意、Server 信任、鉴权和工具动作的安全展示。
总结
Agent 工程的基本单元不是 Prompt,而是受控反馈循环。生产级 Agent 必须具备有界工具、权威状态、明确停止条件、用户对高影响操作的控制权,以及与真实结果绑定的评测。
先构建能够解决问题的最小循环,确保每个动作可观察、可验证、可恢复,再逐步增加自主性。
延伸阅读
- Agent Loop 工程实践深入讨论进展检测、预算、重试与停止条件。
- Agent Harness 架构解析进一步拆分模型判断与负责上下文、工具、状态和控制的 Runtime。
- 多 Agent 编排模式说明什么时候委派收益值得承担协调成本。
- AI Agent 与 Agent Harness 术语页提供简明定义。