利用大语言模型实现确定性编程


基本信息


导语

随着大语言模型(LLM)在代码生成领域的应用日益深入,如何将模型固有的概率性转化为工程所需的确定性,已成为技术落地的关键挑战。本文探讨了将 LLM 纳入确定性编程范式的可行路径,分析了从提示工程到结构化输出控制的技术手段。通过阅读本文,开发者可以了解如何构建可靠的 LLM 应用逻辑,从而在保持模型灵活性的同时,显著提升系统的可控性与稳定性。


评论

基于文章标题《Deterministic Programming with LLMs》(利用大语言模型进行确定性编程),以下是从技术架构、工程落地及行业趋势角度的深度评价。

一、 核心观点与论证结构

中心观点: 文章主张通过引入结构化约束、逻辑验证层或特定工作流,将概率性的LLM转化为具备确定性输出能力的编程组件,从而使其从“聊天机器人”进化为可靠的“软件工程基础设施”。

支撑理由:

  1. 工程可靠性的必然要求:在金融、医疗、工业控制等领域,系统的输出必须是可复现和可验证的。纯概率模型无法满足“单元测试”级别的确定性要求,必须通过外部架构(如ReAct框架、Tool Use)来锁定输出边界。
  2. 从“补全”到“函数”的范式转变:传统的Prompt Engineering倾向于自然语言交互,而确定性编程要求将LLM视为函数——即输入特定结构化数据(如JSON Schema),输出必须严格符合类型定义,拒绝幻觉。
  3. 成本与性能的可控性:确定性编程通常意味着更少的Token消耗(通过减少冗余对话)和更低的延迟,这对于将LLM集成到高频交易或实时系统中至关重要。

反例与边界条件:

  1. 创意生成场景失效:在营销文案、头脑风暴等需要“意外性”和“多样性”的任务中,过度追求确定性会扼杀LLM的核心优势——涌现能力。
  2. 复杂推理的黑盒困境:即便输出格式是确定的(如JSON),其内部推理过程(Chain of Thought)仍可能受温度参数或隐式状态影响,导致逻辑错误。格式确定 $\neq$ 逻辑正确。

二、 深度评价(基于指定维度)

1. 内容深度:观点的深度和论证的严谨性

  • 评价:如果文章仅停留在“使用System Prompt强制输出JSON”,则深度一般。若文章深入探讨了如何通过Pydantic/TypeScript定义与LLM神经元对齐,或者讨论了测试覆盖率在LLM应用中的新定义,则具备较高深度。
  • 事实陈述:目前的业界共识是,纯提示词约束的通过率约为80%-95%(取决于模型能力),要达到99.99%的确定性,必须引入代码层校验。
  • 批判性分析:文章可能低估了“长尾”复杂性。在处理极度复杂的业务逻辑时,LLM的确定性往往会退化,除非引入Agent规划器或迭代修正机制。

2. 实用价值:对实际工作的指导意义

  • 评价:极高。这是目前LLM应用开发从“玩具”走向“生产”的关键一步。
  • 实际案例:LangChain、LlamaIndex等主流框架均已全面转向结构化输出;OpenAI的Function Calling和最新的Structured Outputs功能正是这一趋势的体现。
  • 指导意义:文章若能提供具体的错误处理模式(如当LLM输出不满足Schema时,是重试还是回退到硬编码逻辑),将具有极高的参考价值。

3. 创新性:提出了什么新观点或新方法

  • 评价:“确定性编程”本身并非全新概念,它是传统软件工程原则在AI时代的复用。
  • 作者观点:文章可能提出的创新点在于定义了**“LLM作为协处理器”**的架构模式,即LLM不直接控制业务流,而是作为确定性逻辑的一个子程序被调用。
  • 你的推断:文章可能隐含了对“概率编程”的修正,提出了一种混合架构——确定性代码编排 + 概率性模型执行。

4. 可读性:表达的清晰度和逻辑性

  • 评价:通常此类技术文章会采用“问题-方案-验证”的结构。
  • 逻辑性:关键在于是否清晰界定了“形式上的确定性”(格式正确)与“语义上的确定性”(结果准确)的区别。如果混淆这两者,文章逻辑将存在重大漏洞。

5. 行业影响:对行业或社区的潜在影响

  • 评价:该观点正在重塑RAG(检索增强生成)和Agent的开发标准。
  • 趋势:企业级应用不再满足于“大概正确”的聊天机器人,而是要求LLM能够无缝对接ERP、CRM等数据库。这推动了Software-Defined AI的发展,即用软件工程的标准(强类型、幂等性)来要求AI模型。

6. 争议点或不同观点

  • 核心争议过度约束是否会导致模型性能(Intelligence)下降?
  • 不同观点:部分研究者认为,LLM的创造力源于其随机性。强行通过Grammar-based sampling(如LLM.cpp中的语法约束)来限制输出空间,可能会导致模型在复杂任务上的“智商”下降,或者陷入局部最优解。
  • 技术债:为了实现确定性,开发者需要编写大量的验证代码和Pydantic模型,这实际上是将复杂性从Prompt转移到了代码层。

7. 实际应用建议

  • 防御性编程:永远不要信任LLM的输出,即使在设置了System Prompt之后。必须在代码层设置严格的Schema验证。
  • 混合策略:对于核心逻辑,使用确定性代码(Python/C++);对于模糊逻辑和语义理解,使用LLM,并通过清晰的API边界隔离两者。
  • 评估指标:不要只看Loss,要看“结构化输出成功率”和“语义一致性”。

三、


代码示例

 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
# 示例1:结构化数据提取
import json
from openai import OpenAI

def extract_structured_data(user_input):
    """
    从非结构化文本中提取结构化数据(如订单信息)
    使用JSON模式确保输出格式固定
    """
    client = OpenAI()
    
    response = client.chat.completions.create(
        model="gpt-3.5-turbo-1106",
        messages=[
            {"role": "system", "content": "你是一个数据提取助手,请从用户输入中提取订单信息"},
            {"role": "user", "content": user_input}
        ],
        response_format={"type": "json_object"},  # 强制JSON输出
        temperature=0  # 设置最低温度确保确定性
    )
    
    return json.loads(response.choices[0].message.content)

# 使用示例
order_text = "我要订购3个iPhone 15 Pro,收货地址是北京市朝阳区,联系电话13800138000"
print(extract_structured_data(order_text))
 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
# 示例2:确定性文本生成
from openai import OpenAI

def generate_code_review(code_snippet, language="python"):
    """
    生成确定性代码审查意见
    通过固定seed和top_p参数确保结果可复现
    """
    client = OpenAI()
    
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": f"你是一个{language}代码审查专家"},
            {"role": "user", "content": f"请审查以下代码:\n{code_snippet}"}
        ],
        temperature=0,  # 完全确定性输出
        top_p=1,        # 限制词汇选择范围
        seed=42         # 固定随机种子(需要模型支持)
    )
    
    return response.choices[0].message.content

# 使用示例
code = """
def calculate_sum(numbers):
    total = 0
    for num in numbers:
        total += num
    return total
"""
print(generate_code_review(code))
 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
# 示例3:工具调用确定性
from openai import OpenAI
import json

def get_weather(location):
    """模拟天气查询工具"""
    return f"{location}今天晴天,温度25°C"

def run_deterministic_tool_call(user_query):
    """
    确定性工具调用示例
    通过function calling确保工具调用的一致性
    """
    client = OpenAI()
    
    tools = [{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定地点的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市名称"
                    }
                },
                "required": ["location"]
            }
        }
    }]
    
    response = client.chat.completions.create(
        model="gpt-3.5-turbo-1106",
        messages=[{"role": "user", "content": user_query}],
        tools=tools,
        tool_choice={"type": "function", "function": {"name": "get_weather"}},  # 强制调用特定工具
        temperature=0
    )
    
    tool_call = response.choices[0].message.tool_calls[0]
    function_args = json.loads(tool_call.function.arguments)
    return get_weather(function_args["location"])

# 使用示例
print(run_deterministic_tool_call("北京今天天气怎么样?"))

案例研究

1:Cognition 公司的 Devin(全球首个 AI 软件工程师)

1:Cognition 公司的 Devin(全球首个 AI 软件工程师)

背景: Cognition 公司致力于通过 AI 自动化软件工程流程。在开发 Devin(一个能够自主完成复杂编程任务的 AI 智能体)时,团队面临的核心挑战是如何让大语言模型(LLM)执行多步骤的推理和代码编写,而不仅仅是生成单个代码片段。

问题: 传统的 LLM 调用是概率性的,存在幻觉和非确定性输出。在软件开发中,如果模型生成的代码在执行失败后无法进行自我修正,或者无法严格遵循特定的工程流程(如先写测试再写代码),系统就会陷入死循环或产生不可靠的结果。团队需要一种方法来约束模型的输出,使其能够像人类工程师一样进行“确定性的逻辑思考”和工具调用。

解决方案: Cognition 构建了一个确定性的规划与执行系统。Devin 并非直接输出最终代码,而是将任务分解为一系列确定的“思维链”步骤。系统使用特定的沙箱环境和工具链,强制模型在每一步输出具体的、可验证的指令(如特定的 Shell 命令或文件编辑操作)。如果模型出错,系统会根据确定的反馈机制要求模型重新生成该特定步骤,而不是重新生成整个上下文。这实际上是一种将 LLM 封装在确定性循环中的工程实践。

效果: Devin 成功通过了 Upwork 的实际工程测试,能够自主完成从需求分析、代码编写到部署的全流程。其确定性架构使得 AI 在处理复杂 Bug 修复和端到端应用构建时,具有了可审计性和可复现性,显著高于传统的随机尝试型 AI 编程助手。


2:Windsurf IDE(由 Codeium 开发)

2:Windsurf IDE(由 Codeium 开发)

背景: 随着 AI 编程助手的普及,开发者面临着“上下文漂移”的问题。开发者希望 AI 能够理解整个项目的代码库,并在修改代码时保持逻辑的一致性,而不是仅仅关注当前光标所在的几行代码。

问题: 许多基于 LLM 的补全工具在生成代码时是局部的、概率性的。当开发者在一个文件中修改了函数定义,AI 往往无法在另一个文件中同步更新对该函数的调用,导致代码逻辑不一致甚至报错。这种非确定性使得开发者不敢完全信任 AI 的自动补全。

解决方案: Codeium 在其 Windsurf IDE 中引入了“Flows”技术,这是一种结合了深度代码索引和确定性推理的 AI 工作流。它不仅仅是调用 LLM 的 API,而是构建了一个确定性的图谱,实时追踪代码库中的符号引用和依赖关系。当 LLM 试图生成代码时,系统会强制其参考确定的依赖图谱,确保生成的代码在类型、引用和逻辑上与项目其他部分严格兼容。这实际上是将 LLM 的生成能力约束在了一个确定性的项目上下文框架内。

效果: 开发者报告称,Windsurf 能够处理跨多个文件的复杂重构任务,且生成的代码在语法和逻辑上的准确率远高于传统工具。这种确定性的集成方式让 AI 从一个“聊天机器人”转变为可靠的“结对编程伙伴”,大幅减少了开发者检查和修正 AI 输出代码的时间。


3:Klarna 的客服自动化升级

3:Klarna 的客服自动化升级

背景: Klarna 是一家瑞典的金融科技巨头,每天处理海量的客户服务咨询。为了提高效率,他们大规模部署了基于 LLM 的客服助手。

问题: 在金融领域,准确性和合规性至关重要。纯粹的生成式 AI 模型可能会编造错误的退款政策或错误的利率信息,这在金融合规中是不可接受的。此外,模型需要能够精准地执行业务逻辑(如查询数据库状态),而不是仅仅生成自然语言。

解决方案: Klarna 并未直接使用裸露的 LLM 与客户对话,而是实施了一个“确定性编排层”。该系统将 LLM 作为一个推理引擎嵌入到严格的业务流程中。系统首先通过确定性算法解析用户意图,然后强制 LLM 调用经过验证的后端 API 来获取数据(如订单状态),最后再由 LLM 基于这些确定的、非生成的事实数据生成回复。所有的操作步骤都被记录在确定的日志中,以确保可追溯性。

效果: 该系统在上线一个月内处理了 230 万次对话,相当于 700 名全职客服的工作量,且直接完成了三分之二的咨询量。由于引入了确定性的数据查询和流程约束,客服的准确性与人工持平,同时将客户重复咨询的时间减少了 25%,并将错误率大幅降低。


最佳实践

最佳实践指南

实践 1:使用结构化输出约束

说明: LLM 本质上是概率性的,默认倾向于生成对话式文本。为了获得确定性的、可解析的输出,必须强制模型使用结构化格式(如 JSON、XML 或 YAML)。这不仅能减少输出的随机性,还能显著简化后端代码的解析逻辑。

实施步骤:

  1. 在 Prompt 中明确指定输出格式,例如 “You must respond only with valid JSON object."。
  2. 提供具体的 JSON Schema 示例,展示所需的键名和值类型。
  3. 在代码层面使用强类型解析库(如 Pydantic in Python)来验证输出,如果解析失败则重试或报错,而不是尝试处理非结构化文本。

注意事项: 有时模型可能会在 JSON 前后添加解释性文字(如 “Here is the JSON:")。在 Prompt 中应明确禁止 Markdown 代码块标记(```json)或任何对话填充词,或者使用支持 JSON Mode 的模型 API。


实践 2:降低采样温度

说明: 温度参数控制模型输出的随机性。在确定性编程中,目标是让模型在每次调用时对相同的输入产生尽可能相同的输出。较高的温度会导致创造性和多样性,这与确定性背道而驰。

实施步骤:

  1. 将 API 调用的 temperature 参数设置为 0。
  2. 如果 API 支持,将 top_p 设置为 1。
  3. 确保在代码配置中硬编码这些参数,避免因默认配置变化导致的不一致。

注意事项: 即使温度为 0,某些模型架构或浮点运算的微小差异仍可能导致极少数情况下的非确定性输出。因此,代码逻辑必须始终具备处理输出格式变化的能力,而不能完全依赖于 100% 的文本一致性。


实践 3:实施严格的输入验证

说明: “Garbage in, garbage out” 在 LLM 应用中尤为明显。输入数据的细微变化(如多余的空格、不同的日期格式或隐含的歧义)会导致模型生成完全不同的输出。为了获得确定性的行为,必须对进入 LLM 的输入进行标准化。

实施步骤:

  1. 在调用 LLM 之前,建立预处理层清洗所有用户输入。
  2. 标准化数据格式,例如统一日期为 ISO 8601,去除多余的空白字符,统一大小写。
  3. 对输入参数进行类型检查和边界值限制,防止注入攻击或意外的 Prompt 注入。

注意事项: 不要直接将用户原始输入拼接到 Prompt 中。即使是一个简单的错别字也可能改变模型的语义理解,从而导致输出偏离预期路径。


实践 4:采用少样本提示

说明: 仅靠指令往往不足以让模型理解复杂的任务要求。通过在 Prompt 中提供具体的输入-输出示例,可以显著提高模型遵循特定格式的准确率。这相当于给模型编写了"单元测试”,让它知道什么样的输出是正确的。

实施步骤:

  1. 挑选 3 到 5 个具有代表性的边缘案例和标准案例。
  2. 在 Prompt 中构建 Question -> Answer 的对组。
  3. 明确指示模型:“Follow the pattern of the following examples."(遵循以下示例的模式)。

注意事项: 示例必须与实际任务的难度和风格一致。如果示例过于简单,模型可能会在处理复杂实际输入时"举一反三"导致格式走样;如果示例过于复杂,可能会增加 Token 消耗并降低推理速度。


实践 5:Prompt 版本控制与隔离

说明: LLM 应用的行为高度依赖于 Prompt 的措辞。为了实现工程上的确定性,必须将 Prompt 视为代码的一部分进行管理。随意的修改会导致生产环境行为不可预测。

实施步骤:

  1. 将所有 Prompt 模板存储在独立的文件(如 .txt.yaml)或专门的 Prompt 管理系统中,而不是硬编码在业务逻辑里。
  2. 使用 Git 对 Prompt 的每一次修改进行版本控制,并记录修改原因。
  3. 在生产环境中,通过特定的版本 ID 调用 Prompt,而不是使用 latest 指针,确保回滚能力。

注意事项: 修改 Prompt 应当像修改 API 接口一样谨慎。在部署新的 Prompt 版本前,必须在保留数据集上进行回归测试,确保新版本不仅解决了旧问题,也没有破坏原有的正确行为。


实践 6:构建评估测试集

说明: 没有测试就无法保证确定性。你需要一套固定的测试用例来验证 LLM 的输出是否符合预期。这是确保应用在模型更新或 Prompt 调整后仍能保持稳定行为的关键。

实施步骤:

  1. 收集历史数据中的典型输入,构建一个"黄金测试集”(Golden Dataset)。
  2. 定义断言机制。对于确定性编程,重点检查:输出是否为有效 JSON?必需字段是否存在?数值是否在合理范围内?
  3. 将此测试集成到 CI/CD 流程中,每次代码变更自动运行。

注意事项: 避免使用


学习要点

  • 通过结构化输出和类型提示将LLM纳入确定性工作流,能显著提升其在生产环境中的可靠性和可控性。
  • 使用函数调用或工具定义强制模型返回特定格式的数据,是解决LLM输出随机性问题的核心技术手段。
  • 将复杂的推理任务拆解为多个确定性步骤,并利用中间验证机制,可以有效减少幻觉现象的发生。
  • 在Prompt中明确包含输入输出的JSON Schema或示例,是确保模型输出符合下游系统解析要求的最有效方法。
  • 即使采用确定性编程策略,仍必须在应用层面实施兜底逻辑和异常处理,以应对模型偶尔出现的非结构化输出。
  • 通过精细化的Prompt工程与代码逻辑结合,可以将大模型从单纯的“对话生成器”转变为可信赖的“数据处理组件”。
  • 确定性编程范式要求开发者转变思维,从设计Prompt转向设计包含LLM作为子模块的确定性系统或状态机。

常见问题

1: 什么是确定性编程,为什么传统的 LLM 难以实现?

1: 什么是确定性编程,为什么传统的 LLM 难以实现?

A: 确定性编程是指在相同的输入条件下,程序始终产生完全相同的输出结果,且执行过程可预测、可复现。传统的 LLM(如 GPT-3.5/4)本质上是基于概率的“下一个词预测”模型。即使将 Temperature(温度参数)设为 0,由于底层浮点运算的精度差异、并行计算中的非确定性排序以及模型固有的随机采样机制,模型在多次调用同一提示词时,仍可能生成不同的文本。这种不确定性使得 LLM 难以直接用于需要严格逻辑判断和精确输出的业务流程。


2: 如何在工程实践中提高 LLM 输出的确定性?

2: 如何在工程实践中提高 LLM 输出的确定性?

A: 虽然无法完全消除 LLM 的概率特性,但可以通过多种技术手段显著提高确定性:

  1. 参数控制:将 Temperature 设为 0,Top_p 设为 1。
  2. 种子设定:使用固定的 Seed 参数,确保模型在采样时使用相同的随机数生成器状态。
  3. 结构化输出:利用如 JSON Mode、TypeScript 定义或 Function Calling 等技术,强制模型输出符合预定义 Schema 的数据。
  4. 后处理验证:编写代码验证 LLM 的输出格式,如果失败则重试,从而在系统层面保证最终结果的一致性。

3: 什么是“LLM 编程”或“LLM 工程”?

3: 什么是“LLM 编程”或“LLM 工程”?

A: “LLM 编程”是指将大语言模型视为代码执行环境中的一个组件或函数,通过编写代码来调用模型、处理输入输出,并结合传统的确定性逻辑(如 if/else 分支、循环、数据库查询)来构建应用程序。在这种范式下,开发者不再直接与模型聊天,而是构建一个工作流。例如,一个程序可能先调用 LLM 提取用户意图,然后通过 Python 代码判断该意图是否合法,最后再决定是否调用第二个 LLM 生成回复。这种结合了概率性模型和确定性代码的混合架构,是目前实现确定性应用的主流方向。


4: 如何解决 LLM 上下文窗口限制和长对话记忆的问题?

4: 如何解决 LLM 上下文窗口限制和长对话记忆的问题?

A: 为了在长对话中保持确定性并管理有限的上下文窗口,通常采用以下策略:

  1. 滚动窗口:在 Prompt 中始终保留最近的 N 轮对话,丢弃最早的内容。
  2. 摘要系统:随着对话进行,异步调用 LLM 对旧对话进行摘要,将长文本压缩为关键信息,并在后续请求中传递摘要而非原文。
  3. RAG(检索增强生成):将长期记忆存储在向量数据库中。在每次请求时,根据当前问题检索相关的历史片段,将其作为上下文注入给 LLM。这不仅能解决 Token 限制,还能减少幻觉,提高回答的准确度。

5: 在生产环境中,如何监控和评估 LLM 应用的性能?

5: 在生产环境中,如何监控和评估 LLM 应用的性能?

A: 传统的软件监控(如 CPU/内存使用率)不足以评估 LLM 应用。需要引入针对生成式 AI 的特定指标:

  1. 延迟与吞吐量:记录首字生成时间(TTFT)和端到端延迟。
  2. Token 消耗:监控输入和输出 Token 的数量,以控制成本。
  3. 质量评估
    • 基础指标:利用模型自身或另一个更强的 LLM 给回答打分。
    • RAG 特有指标:检索准确率和检索召回率。
    • 业务对齐:通过人工标注或“黄金数据集”测试,确保输出符合业务逻辑。
  4. 可观测性工具:使用如 LangSmith、Weights & Biases 或 Arize 等工具来追踪每次请求的完整链路,便于调试和优化。

6: 目前有哪些主流框架支持构建结构化的 LLM 应用?

6: 目前有哪些主流框架支持构建结构化的 LLM 应用?

A: 为了解决直接调用 API 带来的复杂性,社区开发了多个框架来支持结构化和确定性的 LLM 编程:

  1. LangChain / LangGraph:目前最流行的框架,提供了链式调用、记忆管理和 Agent 编排的能力。LangGraph 特别擅长构建有状态、循环的流式应用。
  2. Microsoft Semantic Kernel:微软推出的轻量级 SDK,专注于将 LLM 集成到现有代码中,强调 Prompt 模板和规划能力。
  3. Vercel AI SDK:专注于前端和全栈集成的工具库,提供了流式渲染和类型安全的 Hook,非常适合 Web 应用开发。
  4. LlamaIndex:专注于数据连接,特别是 RAG(检索增强生成)场景,提供了从 PDF、数据库读取数据并索引到 LLM 的完整流程。

思考题

## 挑战与思考题

### 挑战 1: [简单]

问题**:构建一个 Python 脚本,调用大语言模型(LLM)API 将非结构化用户评论转换为结构化 JSON 对象(包含 sentimentkeywords 字段)。要求不依赖 LangChain 等框架,仅使用 requests 库,并处理当模型返回包含 Markdown 代码块等非标准 JSON 格式时的解析错误。

提示**:在 Prompt 中显式要求模型仅输出 JSON,并在代码中使用 try-except 块配合正则表达式来提取 JSON 字符串。


引用

注:文中事实性信息以以上引用为准;观点与推断为 AI Stack 的分析。



站内链接

相关文章