ByteNoteByteNote
给不确定性加边界:概率性系统工程哲学
字

字节笔记本

2026年10月24日 · 约 11 分钟读完

给不确定性加边界:概率性系统工程哲学

API中转
¥120

给不确定性加边界:沙箱

本文是《从 0 写 Agent》系列第 42 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「概率性系统的工程哲学:给不确定性加边界」。

传统软件是确定性的,同样的输入永远产生同样的输出。但 Agent 不是。

同一个 Agent、同一个问题、同一个时间,跑两次可能得到不同答案。这种"概率性"是 Agent 工程化的核心难题,怎么让一个不确定的系统,产出可靠的结果?


引子:确定性 vs 概率性

传统软件的假设,同样的输入 → 同样的输出。这是整个软件工程的基础,单元测试能跑通,是因为"今天通过明天也通过"。

Agent 打破了这个假设:

go
agent := &agents.Agent{Name: "助手", Model: "gpt-4o"}

// 第一次跑
agents.Run(ctx, agent, "帮我分析这段代码的问题", opts)
// → "第 3 行有空指针风险..."

// 第二次跑(完全相同的输入)
agents.Run(ctx, agent, "帮我分析这段代码的问题", opts)
// → "这段代码的主要问题是..."(不同的措辞、可能不同的问题点)

同一个输入,不同输出。 这让传统的测试、调试、质量保证方法部分失效,你怎么写断言"输出应该等于 X"?输出根本不固定。

Agent 工程化的核心,就是"给概率性系统加确定性边界"。


成本管理

五个边界:驯服不确定性

边界一:结构确定性(即使内容不同,结构要对)

Agent 的输出内容可能变,但结构必须确定。这就是第 3 章的"结构化输出"(OutputType[T]):

go
type CodeReview struct {
    Issues     []Issue `json:"issues"`
    Summary    string  `json:"summary"`
    Severity   string  `json:"severity"`
}

agent.OutputType = agents.OutputType[CodeReview]()

不管模型怎么说,输出一定是个 CodeReview 结构,有 issues 数组、有 summary、有 severity。内容变,结构不变。

这是"用类型约束概率",模型自由发挥内容,但框架强制结构。下游代码可以安全地访问 result.Issues[0].Line,不用担心"这次输出有没有这个字段"。

边界二:流程确定性(步骤可预测)

Agent 内部"想什么"不可控,但流程步骤可控。这就是第 4 章的工具调用生命周期 + 第 12 章的 HITL:

text
任何 Agent Run,都走这个确定流程:
  循环 {
    调模型(可能产生不同的 Thought)
    如果调工具 → 执行工具(工具行为是确定性的)
    如果危险工具 → 暂停审批(HITL 是确定性的人为边界)
    如果最终输出 → guardrail 检查(确定性的安全检查)
  }

流程是骨架(确定),内容是血肉(概率)。 骨架固定,血肉才能在安全范围内自由变化。

边界三:验证确定性(结果可校验)

概率性输出不能"信就用",必须验证。agents-go 提供多层验证:

  • 输出 guardrail,第 14 章,检测输出是否含 PII、是否超长
  • 结构化输出校验,第 3 章,输出必须匹配 schema
  • HITL 审批,第 12 章,人验证高风险输出

"信任但验证",允许 Agent 自由生成,但在关键节点加验证门。不验证的不能进生产。

边界四:幂等与可重放(失败可恢复)

概率性系统更容易失败(模型可能生成坏数据),所以恢复能力更重要:

  • Session 持久化,第 8-10 章,状态可恢复
  • RunState 序列化,第 13 章,HITL 暂停可跨进程恢复
  • 工具幂等,重试不会产生副作用(比如"创建订单"工具要防重复创建)

概率性系统必须假设"会失败",并设计好恢复路径。 这是和确定性系统的根本差异,确定性系统可以"失败就报错",概率性系统要"失败可重来"。

边界五:可观察(出问题能查)

概率性系统更难调试,所以可观察性是刚需:

  • Tracing,第 25-27 章,每一步都记录
  • Lifecycle Hooks,第 19 章,关键节点可挂钩
  • 全文日志,第 4 章的工具输出回喂,可审计

确定性系统出问题,看错误日志就够。概率性系统出问题,要看完整 trace,"模型当时想了什么、调了什么、结果是什么"。


设计权衡:温度与确定性

模型的"随机性"由 temperature 控制:

  • temperature = 0,最确定(贪婪解码,每次选最高概率的 token)
  • temperature = 1,较随机
  • temperature > 1,非常随机(可能胡言乱语)

生产环境建议 temperature = 0(或接近 0),最大化确定性。但注意,即使 temperature=0,不同 GPU/不同版本仍可能不同(浮点精度差异)。所以 temperature=0 不是 100% 确定,只是"尽量确定"。

go
agent.ModelSettings = &agents.ModelSettings{
    Temperature: ptr(0.0),  // 生产环境用 0
}

创意场景(写诗、起标题)用高温度。工程场景(分析、判断、结构化输出)用低温度。


测试概率性系统的特殊方法

传统单元测试(assertEqual)对 Agent 部分失效。替代方法:

方法一:结构断言

go
// 不断言具体内容,断言结构
result := agents.Run(...)
review := parseOutput(result)
assert.NotNil(t, review.Issues)      // 有 issues 字段
assert.IsType(t, []Issue{}, review.Issues)  // 是数组
// 不断言具体内容(因为会变)

方法二:属性测试

go
// 断言"属性"而非具体值
result := agents.Run(...)
assert.LessOrEqual(t, len(output), maxLength)  // 不超长
assert.False(t, containsPII(output))            // 不含 PII
assert.True(t, isValidJSON(output))             // 是合法 JSON

方法三:LLM-as-Judge

go
// 用另一个模型评判输出质量
score := llmJudge("这个代码审查报告质量如何(1-10分)?", output)
assert.Greater(t, score, 7.0)  // 质量分 > 7

这对应前面 SkillOpt 那篇讲的"validation gate",用模型验证模型。

方法四:回归测试集

收集"已知好的输出",新版本跑同样的输入,比较输出质量有没有退化。不要求完全相同,要求"一样好或更好"。


设计权衡:什么时候该用 Agent

概率性系统的工程成本高(验证、恢复、可观察都要额外做)。所以不是所有场景都该用 Agent。

场景该用 Agent 吗理由
用户登录❌ 不该确定性逻辑,用传统代码
计算订单总价❌ 不该确定性计算
客服问答✅ 该需要理解和推理
数据分析✅ 该边查边推理
发送通知❌ 不该确定性触发
代码生成✅ 该创造性任务

原则,能用确定性代码解决的,别用 Agent。 Agent 是"最后手段",当问题太模糊、太复杂、需要推理时,才值得承受概率性系统的成本。


小结

读完这一章,你应该理解:

  1. Agent 是概率性系统,同输入可能不同输出,打破传统软件的确定性假设
  2. 五个边界驯服不确定性,结构确定、流程确定、验证确定、可恢复、可观察
  3. temperature 控制随机性,生产用低温度
  4. 测试方法要变,结构断言、属性测试、LLM-as-Judge、回归集
  5. 不是所有场景都该用 Agent,能用确定性代码就别用

核心洞察,Agent 工程化的本质,是"用确定性的边界,框住概率性的内核"。 边界(结构/流程/验证/恢复/观察)确定,内核(模型生成)才能在安全范围内自由发挥。


下一篇

第 43 章,LLM 作为大脑:为什么它适合做 Agent 的决策核心。不是所有 AI 都适合做 Agent,传统 ML 模型做不了,规则引擎做不了。LLM 的什么特性让它成为理想的"Agent 大脑"?


本文是《从 0 写 Agent》系列第 42 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。

相关文章

分享: