LLM 有一个根本矛盾:它的输出是概率性的,同一个输入每次调用都可能得到不同的回答。而生产环境的 AI Agent 对可靠性有刚性要求——同样的操作不能这次成功下次失败,同一个查询不能这次给正确答案下次胡说八道。

这个矛盾在 2025 年之前还能靠「LLM 就是这样,将就用」来回避。但 2026 年,当企业把 AI Agent 部署到真正的业务流程里——审批合同、处理订单、生成代码——确定性就成了生死线。

确定性工程(Determinism Engineering)正在成为 AI Agent 领域的核心话题。它的目标很直接:给概率模型套上缰绳,让它在关键环节的行为可预测、可控制。

本文系统梳理 2026 年 Agent 确定性工程的完整技术栈,从 Prompt 层到系统层,从单 Agent 到多 Agent 协作。


一、为什么 LLM 的不确定性是一等公民问题

很多人把 LLM 的不确定性当作「特性」而非「问题」——creative writing 的时候有点随机性挺好。但当你把 LLM 放进一个每天处理 10 万次用户请求的系统里,「有时候对有时候错」不是创意,这是灾难。

举几个典型的不确定性灾难场景:

函数调用参数漂移。同一个意图,模型这次可能调用 create_order,下次可能调用 submit_order,参数格式也可能飘。两个不同供应商的 API 调用,参数名不一致,系统直接报错。

Agent 行为分叉。一个处理客服请求的 Agent,这次走了「先查库存再回复用户」的路径,下次同样的请求走了「直接回复已知信息」的路径。如果两条路径的后续逻辑不同,用户体验就不一致,严重时业务状态会出错。

多 Agent 协作的指数级不确定性。两个 Agent 协作,每个 Agent 有 3 种可能的行为路径,组合起来是 9 种可能。如果业务逻辑只覆盖了其中 2 种,另外 7 种就变成了未定义行为。

越狱和 Prompt 注入的蝴蝶效应。一个精心设计的恶意输入,让模型在某个环节跳过安全检查,后续整个 Agent 链的行为就完全失控。

不确定性不是小概率事件。在高并发场景下,任何非确定性都会成为必然事件——只要运行次数够多,所有可能的分叉都会被走到。


二、确定性工程的第一层:Prompt 约束

最基础的确定性工程从 Prompt 层开始。目标是:用结构化的指令边界,把模型的输出空间约束到可接受的范围。

2.1 输出格式约束

让模型只输出特定格式,是最简单也最有效的确定性手段。

from pydantic import BaseModel, Field

class OrderStatusResponse(BaseModel):
    order_id: str = Field(description="订单号")
    status: Literal["pending", "processing", "shipped", "delivered", "cancelled"]
    estimated_delivery: str | None = Field(default=None, description="预计送达时间")
    actions: list[str] = Field(
        description="用户可执行的操作列表",
        default_factory=list
    )

# 结构化输出减少了理解偏差
response = model.with_structured_output(OrderStatusResponse)
result = response.invoke("帮我查下订单 ORD-2026-0412 的状态")
# result.order_id 永远是字符串
# result.status 永远是枚举中的五个值之一

这个模式叫 Schema-Constrained Generation。模型不再自由发挥,而是被严格限制在预定义的 schema 内。OpenAI、Anthropic、Google 的主流模型都支持这个能力。

2.2 少样本约束(Few-Shot Locking)

当输出格式本身不够(比如情感分类、意图识别这类开放判断),少样本示例能把模型锚定到具体的行为模式上。

# 差的情况:只给描述,让模型自由理解
prompt = """
判断用户情感的极性:正面、负面、中性。
用户输入:我对这个功能有点失望。
"""

# 好的情况:给明确的示例覆盖边缘case
prompt = """
判断用户情感的极性:正面、负面、中性。

示例:
输入:「太棒了,完全符合预期」 → 正面
输入:「有点贵」 → 中性(不是负面,只是陈述价格)
输入:「这东西简直是垃圾,浪费我时间」 → 负面
输入:「我有点失望」 → 负面(「有点」表达了明确的负面情绪)

判断:
输入:「说实话,这个功能还算能用」 →
"""

少样本约束的本质是把判断标准显式化。模型看到足够多的示例后,会模仿示例的模式,而不是自由发挥。关键是把边界 case(neutral 不是 negative、有点失望是负面)显式写出来。

2.3 思维链约束(Chain-of-Thought Pinning)

推理模型(o1、o3、Claude 3.7 Sonnet 等)的思维链本身也是不确定性的来源——同一个问题,思维链可能不同,最终结论可能不同。

对于关键决策节点,有两个策略:

强制思维链并校验中间结论。不是让模型自己想,而是给出推理框架,让模型在框架内填空。

DECISION_FRAMEWORK = """
对于每个候选方案,按以下维度打分(1-5分):
1. 合规风险:是否可能违反公司政策或法律法规?
2. 用户价值:对用户的实际帮助有多大?
3. 执行成本:开发和维护成本有多高?
4. 可逆性:如果决策错误,恢复难度有多大?

最终决策规则:
- 如果任何维度得分≤2,必须选择风险最低的方案
- 如果合规风险=5(一票否决),直接拒绝
- 否则,选择总分最高的方案

按照上述框架分析并给出最终推荐。不要跳过任何维度。
"""

思维链事后校验。对于高风险决策,让模型先推理,再让第二个模型(或规则引擎)校验推理链的一致性。


三、确定性工程的第二层:输出校验与 Guardrails

Prompt 约束能覆盖 70% 的场景,但剩下 30% 需要运行时校验。这层的核心理念是:不要相信,要验证

3.1 结构化输出的后验校验

即使模型支持结构化输出,每次调用后都应该校验输出的合法性。

from pydantic import ValidationError

try:
    result = OrderStatusResponse.model_validate(model_output)
except ValidationError as e:
    # 结构化校验失败,降级处理
    logger.warning(f"LLM output validation failed: {e}, falling back to rule-based")
    return fallback_query(order_id)

这不是过度防御。模型在边缘输入下(比如超长文本、特殊字符、混合语言)的结构化输出失败率远高于正常情况。

3.2 语义校验(Semantic Guardrails)

结构校验只能检查格式,无法检查语义正确性。语义校验需要判断模型输出的「意思」是否与预期一致。

典型场景:用户问「今天能发货吗」,模型说「可以」,但实际上今天已经截单了。格式完全正确,但内容错了。

from chevron import template

SEMANTIC_CHECK_PROMPT = template(
    """你是一个订单系统的事实核查员。
    
    订单信息:{{order_info}}
    用户问题:{{user_question}}
    LLM回答:{{llm_answer}}
    
    请判断 LLM 回答是否与订单信息一致。
    如果一致,返回 {"consistent": true}
    如果不一致,返回 {"consistent": false, "correct_answer": "正确的回答"}
    
    只返回 JSON,不要有其他内容。
    """,
    {
        "order_info": get_order_info(order_id),
        "user_question": user_input,
        "llm_answer": raw_llm_response
    }
)

这是一个 LLM-as-Judge 模式:用第二个 LLM 调用来校验第一个 LLM 的输出。成本翻倍,但关键路径的准确性也翻倍。

3.3 规则引擎兜底

对于高确定性的业务规则,永远不要依赖 LLM 判断——规则引擎是确定性兜底。

# 永远不要让 LLM 决定这些
IMPOSSIBLE_RULES = {
    "discount_rate": lambda cart: 0.1 if cart.total > 1000 else 0,
    "shipping_cutoff": lambda: datetime.now().hour < 18,
    "blocked_users": lambda user_id: user_id in BLOCKED_LIST,
}

# LLM 只负责生成自然语言解释
explanation = llm.generate(f"解释为什么订单{order_id}适用此折扣")

规则引擎+LLM 解释的分工模式,是当前最实用的确定性架构:规则保证业务正确性,LLM 提升交互体验。


四、确定性工程的第三层:架构级约束

Prompt 和输出校验解决的是单次交互的确定性。但 Agent 的真正问题在于行为轨迹的确定性——从接收请求到完成任务,Agent 走过的那条路径必须可预期。

4.1 状态机架构:把 Agent 行为锁在预定义路径上

纯 LLM 驱动的 Agent 行为是自由形式的——模型想怎么走就怎么走。状态机架构是应对这一问题的最直接方式。

from enum import Enum
from typing import Callable

class OrderAgentState(Enum):
    INITIAL = "initial"
    VALIDATING = "validating"
    INVENTORY_CHECK = "inventory_check"
    PAYMENT_PROCESSING = "payment_processing"
    FULFILLMENT = "fulfillment"
    CONFIRMATION = "confirmation"
    FAILED = "failed"

class StateMachineAgent:
    def __init__(self):
        self.state = OrderAgentState.INITIAL
        self.transitions: dict[OrderAgentState, list[OrderAgentState]] = {
            OrderAgentState.INITIAL: [OrderAgentState.VALIDATING],
            OrderAgentState.VALIDATING: [OrderAgentState.INVENTORY_CHECK, OrderAgentState.FAILED],
            OrderAgentState.INVENTORY_CHECK: [OrderAgentState.PAYMENT_PROCESSING, OrderAgentState.FAILED],
            OrderAgentState.PAYMENT_PROCESSING: [OrderAgentState.FULFILLMENT, OrderAgentState.FAILED],
            OrderAgentState.FULFILLMENT: [OrderAgentState.CONFIRMATION],
            OrderAgentState.CONFIRMATION: [],
            OrderAgentState.FAILED: [],
        }

    def transition(self, next_state: OrderAgentState) -> None:
        if next_state not in self.transitions[self.state]:
            raise AgentDeterminismViolationError(
                f"Invalid transition: {self.state} -> {next_state}"
            )
        self.state = next_state

    def process(self, order: Order) -> OrderResult:
        while self.state != OrderAgentState.CONFIRMATION and self.state != OrderAgentState.FAILED:
            self._execute_state(order)
        return self._generate_result(order)

这个状态机的关键约束:Agent 只能按照预定义的路径走,任何偏离路径的尝试都会抛出异常。LLM 仍然负责每个状态内的推理,但状态之间的流转由确定性逻辑控制。

4.2 确定性执行计划(Deterministic Execution Plan)

对于复杂的多步任务,让 LLM 在执行前先生成一个「执行计划」,这个计划经过校验后才执行。计划本身是确定性的字符串,执行时严格按计划走。

PLANNING_PROMPT = """
你是一个订单处理 Agent。现在收到用户请求:「{user_request}
请生成一个结构化的执行计划,格式如下:
1. [ACTION] <具体操作>
2. [DECISION] if <条件> then <路径A> else <路径B>
3. [CALL] <tool_name>(<参数>)
4. [RESPOND] <最终回复内容>

执行计划必须:
- 覆盖用户请求的所有分支
- 包含错误处理分支
- 每个 LLM 判断点都要有明确的决策规则

输出格式:纯文本执行计划,每行一个步骤。
"""

# LLM 生成计划
execution_plan = llm.invoke(PLANNING_PROMPT)

# 计划校验
validated_plan = plan_validator.validate(execution_plan)

# 确定性执行
result = plan_executor.execute(validated_plan)

这个模式的本质是把 LLM 的「规划」和「执行」解耦。规划阶段允许 LLM 自由推理(可以多次调用、可以反思),一旦计划被校验通过,执行阶段就是纯确定性的。

4.3 版本锚定(Version Pinning)

LLM 提供商会不断更新模型,同一个 Prompt 在新模型上可能得到不同结果。对于需要严格一致性的场景,可以在模型调用时锁定版本。

# 不推荐:始终使用最新版
client = Anthropic()

# 推荐:锁定具体版本
client = Anthropic(
    model="claude-3-7-sonnet-20261111"  # YYYY-MM-DD 格式,锁定到具体日期的版本
)

# 更保守:同时锁定模型和推理参数
response = client.messages.create(
    model="claude-3-7-sonnet-20261111",
    temperature=0.0,  # 确定性输出必须 temperature=0
    top_p=1.0,        # 禁用 nucleus sampling
    # 不传 system 则每次行为完全一致
)

temperature=0 是最基础的确定性配置,但很多人忽略了 top_p=1.0。即使 temperature 为 0,top_p 的值也会影响采样的分布——top_p=0.9 意味着模型只会考虑累积概率前 90% 的 token,这会引入隐式随机性。


五、关键场景的确定性实战

5.1 函数/工具调用的确定性

Function Calling 最大的不确定性来源是「选错工具」和「参数错误」。解决思路是双层校验:

# 第一层:严格的工具描述和参数 schema
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_order_status",
            "description": "查询订单状态。仅用于用户明确询问订单物流、发货状态时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "pattern": "^ORD-\\d{4}-\\d{4}$",  # 正则约束参数格式
                        "description": "订单号,格式必须为 ORD-YYYY-NNNN"
                    }
                },
                "required": ["order_id"]
            }
        }
    }
]

# 第二层:调用前用规则引擎校验参数合法性
def validate_tool_call(tool_name: str, arguments: dict) -> None:
    if tool_name == "get_order_status":
        if not re.match(r"^ORD-\d{4}-\d{4}$", arguments["order_id"]):
            raise InvalidParameterError(f"Invalid order_id format: {arguments['order_id']}")
        if arguments["order_id"] in CANCELLED_ORDERS:
            raise OrderCancelledError(f"Order {arguments['order_id']} has been cancelled")

5.2 多 Agent 协作的确定性

Multi-agent 系统的不确定性是单 Agent 的指数叠加。2026 年最有效的控制手段是 Shared Schema + choreography over orchestration

# 差的模式:Agent A 控制 Agent B 的行为(强耦合)
class AgentA:
    def delegate_to_b(self, task):
        response = agent_b.invoke(task)
        if "reject" in response:
            # Agent A 需要猜测 Agent B 的行为
            return self.handle_rejection(response)

# 好的模式:共享状态机,Agent 在预定义状态下自主行动
SHARED_STATE = {
    "current_phase": "order_validation",  # 所有 Agent 都读这个状态
    "validation_result": None,
    "approved_tools": ["get_order_status", "apply_discount"],  # 白名单
}

class OrderValidationAgent:
    def act(self, state: dict) -> dict:
        if state["current_phase"] != "order_validation":
            return {}  # 静默跳过,不是我该管的时候
        result = self.validate_order(state["order"])
        return {"validation_result": result}

class FulfillmentAgent:
    def act(self, state: dict) -> dict:
        if state["current_phase"] != "fulfillment":
            return {}  # 等待状态机推进到这里
        if not state["validation_result"]["approved"]:
            return {}  # 校验没通过,不执行
        return self.fulfill_order(state["order"])

每个 Agent 读共享状态,根据状态决定自己的行为,而不是被另一个 Agent 直接指挥。这样即使某个 Agent 行为异常,也不会破坏整体协作框架。


六、确定性工程的边界

确定性工程有明确的适用边界,不是所有问题都值得用工程手段消除不确定性。

值得追求确定性的场景:财务计算、法律合规、安全敏感操作、API 调用、医疗判断——这些场景的错误成本远高于额外工程投入的成本。

不值得追求的场景:创意写作、头脑风暴、情感陪伴、开放式问答——这些场景的「确定性」反而是体验的敌人。用户问「帮我写封情书」,每次输出都不一样才是对的。

2026 年的工程实践越来越倾向于在架构层面区分这两种场景:关键路径用确定性工程严防死守,探索路径给 LLM 足够的自由度。这种「确定性+探索性」的双轨架构,正在成为 AI Agent 系统设计的主流范式。


总结

确定性工程不是消除 LLM 的概率本质,而是承认这个本质,然后用工程手段把关键行为约束到可接受的确定范围内。

核心要点:

  • Prompt 约束是成本最低的第一道防线——结构化输出、少样本锚定、思维链框架化
  • 输出校验是必要的运行时安全网——结构校验、语义校验、规则引擎兜底
  • 架构约束解决行为轨迹的不确定性——状态机、执行计划、版本锁定
  • 多 Agent 协作用共享状态机替代直接指挥,避免指数级不确定性叠加
  • 确定性有边界:关键路径严防死守,探索路径保持自由

LLM 的概率性是它的优势,不是缺陷。确定性工程的目标是:在需要确定性的地方消灭不确定性,在需要创造性的地方保留它