
先聊一个现象现在很多同学使用 LLM 生成代码已经习惯了“描述需求 → 拿到大段代码 → 复制 → 调试 → 再让模型修复”的循环。这种开发方式有一个很形象的说法叫 vibe coding也就是只凭“感觉”让大模型把代码写出来自己负责验收和兜底。它的效率确实很高但真正的团队项目或者正式环境里不验证生成结果、不固定生成过程、让模型反复输出风格不稳定的代码很容易埋下不可维护的隐患。本文要聊的 Sif 1.0正是这种背景下出现的一个很有意思的尝试。它的核心不是“让 LLM 生成代码”而是让 LLM 去控制一个 deterministic coder确定性编码器。也就是说把大模型的角色从“手写代码的工具人”变成“下指令、做决策、验收产物的指挥官”同时把真正产生代码的部分交给可预测、可重复、可回归的确定性组件来执行。这篇文章会围绕 Sif 1.0 的设计思路展开以下内容什么是 deterministic coder它和普通 AI 生成代码有什么区别Sif 1.0 的整体架构是怎样的LLM 在其中扮演什么角色如何用 Python 亲手搭建一个“LLM 控制确定性编码器”的最小示例实际使用中常见的问题、排查思路与工程化建议。无论你是对 LLM Agent 感兴趣的开发者还是正在做 AI 编程工具、内部代码生成平台这篇文章都能提供一个比较完整的参考。1. 背景与核心概念1.1 从 vibe coding 到 LLM AgentVibe coding 这个词这几年很流行它的核心特征是开发者不再逐行编写代码而是用自然语言描述意图把实现细节交给 LLM 完成。这种方式在快速原型、脚本编写、临时工具开发上效率极高特别适合验证想法。但它的缺点也很明显输出不稳定同样的提示词多次生成结果可能不同质量不可控模型可能一本正经地生成有逻辑漏洞的代码回归困难项目后期修复一个 bug可能把另一个模块改坏审计困难无法追溯到某段代码的生成依据和验证过程。正因为这些问题很多团队开始把目光从“让 LLM 直接写代码”转向“让 LLM 编排代码生成流程”。LLM 的任务不再是吐出大段代码而是理解需求、拆分任务、选择合适的确定性组件、传递参数、检查输出结果。这个方向逐渐演变成 LLM Agent 的一个重要分支。1.2 deterministic coder 是什么Deterministic coder即确定性编码器指的是一类“在相同输入条件下输出完全可复现”的代码生成组件。它的特点包括有明确的输入输出契约不依赖概率采样生成结果可回归验证执行过程可以通过日志完整追踪通常基于模板、DSL、代码生成器或编译机制实现。举个最简单的例子一个根据数据库表结构生成 Mapper 代码的工具输入是表结构 JSON输出是完整的 Java Mapper 文件。只要输入不变输出永远相同。这就是典型的 deterministic coder。1.3 Sif 1.0 的定位Sif 1.0 的核心思路是用 LLM 来控制一个或者多个 deterministic coder组成一套“LLM 做决策、确定性组件做执行”的代码生成流水线。如果把它和传统 vibe coding 做对比会更清晰维度传统 vibe codingSif 1.0 思路LLM 角色直接生成最终代码输出任务计划、参数和校验结果代码来源LLM 概率采样确定性编码器按计划生成可重复性不稳定同一计划产出相同代码质量保证靠人工审查计划校验 生成结果校验 人工抽查适用场景原型验证、脚本工具工程化代码生成、遗留系统改造Sif 1.0 不把 LLM 当成“万能的代码生成器”而是把 LLM 放在一个更合理的位置理解模糊业务需求转换成结构化的代码生成指令再把指令交给专门负责某一类代码产物的确定性生成器。这种设计带来的价值很直接代码风格一致生成过程可审计回归测试可以自动化不依赖某一个大模型的编码能力降低模型替换成本。2. Sif 1.0 的架构与设计思路要理解 Sif 1.0需要先拆解它的架构分层。它并不是一个单一的库或模型而是一套“控制框架”核心组件包括三个部分LLM 控制层、确定性编码引擎、验证与反馈层。2.1 LLM 控制层LLM 控制层是系统的“大脑”。它负责接收用户自然语言需求分析需求拆解为多个子任务从确定性编码器注册表中选择合适的生成器为每个生成器生成结构化参数对生成结果进行初步评估根据验证结果决定是否重新调整参数。这一层的关键不是让 LLM 直接写代码而是让 LLM 输出结构清晰的中间计划。例如用户说“帮我生成一个用户管理的 REST 接口”LLM 并不需要直接输出 Controller、Service、Mapper 的代码而是应该输出类似这样的计划{ tasks: [ { generator: rest_controller_generator, params: { entity: User, fields: [id, name, email, created_at], operations: [list, get, create, update, delete] } }, { generator: mybatis_mapper_generator, params: { entity: User, table: t_user, primary_key: id } } ] }这个 JSON 是可控的。LLM 只负责理解需求并填充结构化参数不负责生成大段代码。这样一来LLM 的“犯错空间”被大幅压缩。2.2 确定性编码引擎编码引擎由一组 deterministic coder 组成。每个 coder 都只负责一种特定类型的代码产物并且是纯函数式的输入参数 → 输出代码。常见的 deterministic coder 类型包括REST 接口生成器输入实体定义输出 Controller数据访问层生成器输入表结构输出 Mapper/RepositoryDTO/VO 生成器输入字段定义输出数据类配置类生成器输入配置项输出 YAML/properties数据库迁移脚本生成器输入变更描述输出版本化 SQL。这些生成器通常使用模板引擎、代码模型或 DSL 实现。它们必须是确定性的——这不仅是工程需求也是 Sif 1.0 这个名字想表达的核心价值LLM 负责“品味”确定性引擎负责“稳定”。2.3 验证与反馈层验证层是最容易被忽略、但实际项目中最重要的部分。Sif 1.0 中LLM 输出计划后、确定性组件生成代码后都会经过验证环节计划校验参数缺失、类型错误、字段不存在生成结果校验语法检查、编译检查、单元测试回归校验与历史生成结果对比防止意外变更人工评审最终由开发人员确认。验证失败时反馈信息会回流到 LLM 控制层由 LLM 修正计划。这就是一个完整的闭环。这样设计的另一个好处是模型可以被替换。今天用 GPT-4明天换成其他模型只要它还能输出符合约定的计划 JSON整个系统就能继续工作。确定性编码引擎完全不受模型升级影响。3. 环境准备与版本说明在动手写示例之前先说明运行环境。根据多个实际项目经验Sif 这类“LLM 确定性编码器”的组合并不依赖特定云服务只要本地能调用 LLM API 即可。本文示例的开发环境如下操作系统Windows 10 / macOS / Linux 均可Python3.10依赖库openai、pydantic、jinja2、pyyamlLLM 接口兼容 OpenAI API 格式的服务或本地部署模型IDEVS Code / PyCharm或直接用命令行。这里需要特别强调不同项目的依赖版本差异较大如果你本地的 openai 库版本较新部分 API 参数可能发生变化。本文示例代码以“配置 base_url api_key”的方式调用兼容大部分 OpenAI 兼容接口。如果你没有可直接使用的 LLM API也可以先用一个小型本地模型替代只要它支持 JSON 格式输出。下面所有示例都把 LLM 输出严格约束为 JSON方便后续解析。建议创建一个独立的虚拟环境python -m venv sif-demo source sif-demo/bin/activate # Windows 使用 sif-demo\Scripts\activate pip install openai pydantic jinja2 pyyaml项目结构建议如下sif-demo/ ├── main.py # 入口组合控制层和生成引擎 ├── coder/ │ ├── __init__.py │ ├── registry.py # 确定性编码器注册表 │ ├── rest_coder.py # REST 接口生成器 │ └── model_coder.py # 数据类生成器 ├── llm/ │ ├── __init__.py │ ├── controller.py # LLM 控制层 │ └── prompts.py # 提示词模板 ├── plans/ │ └── plan.json # LLM 输出的中间计划 └── output/ # 生成的代码输出目录不用完全照搬这个结构但建议保持“控制器、注册表、生成器”三部分分离。4. 实战让 LLM 驱动确定性代码生成器下面我们动手实现一个最小可运行的 Sif 1.0 流程。为了让例子更直观我会让 LLM 根据一段自然语言需求输出一个 JSON 计划然后由两个 deterministic coder 分别生成 Python 数据类和 REST 接口骨架。4.1 定义确定性编码器先写一个基础的生成器抽象。这里使用一个很简单的接口每个生成器内部实现generate(params) - str方法返回代码字符串。# 文件路径coder/base.py from abc import ABC, abstractmethod from typing import Any, Dict class BaseCoder(ABC): 确定性编码器抽象基类 name: str base abstractmethod def generate(self, params: Dict[str, Any]) - str: 根据参数生成代码必须保证相同参数产生相同输出 pass接下来实现一个数据类生成器。它的输入是实体名称和字段列表输出是 Python dataclass。# 文件路径coder/model_coder.py from typing import Any, Dict, List from coder.base import BaseCoder class ModelCoder(BaseCoder): 生成 Python dataclass 的确定性编码器 name model_generator def generate(self, params: Dict[str, Any]) - str: entity: str params[entity] fields: List[Dict[str, str]] params[fields] lines [from dataclasses import dataclass, ] lines.append(fdataclass) lines.append(fclass {entity}:) if not fields: lines.append( pass) else: for field in fields: fname field[name] ftype field[type] lines.append(f {fname}: {ftype}) return \n.join(lines) \n再实现一个 REST 接口生成器把实体名转换成 Controller 骨架。这里不依赖任何 Web 框架只是生成一个类体现确定性代码生成的过程。# 文件路径coder/rest_coder.py from typing import Any, Dict, List from coder.base import BaseCoder class RestCoder(BaseCoder): 生成 REST 接口骨架的确定性编码器 name rest_controller_generator def generate(self, params: Dict[str, Any]) - str: entity: str params[entity] base_path: str params.get(base_path, f/{entity.lower()}) lines [f# {entity} REST Controller, ] lines.append(fclass {entity}Controller:) lines.append(f base_path {base_path!r}) lines.append() lines.append( def list(self):) lines.append( raise NotImplementedError) lines.append() lines.append( def get(self, id):) lines.append( raise NotImplementedError) lines.append() lines.append( def create(self, data):) lines.append( raise NotImplementedError) lines.append() lines.append( def update(self, id, data):) lines.append( raise NotImplementedError) lines.append() lines.append( def delete(self, id):) lines.append( raise NotImplementedError) return \n.join(lines) \n这两个生成器都是完全确定性的输入相同参数输出永远一致。它们甚至不依赖 LLM。4.2 实现注册表接下里把生成器放进注册表。注册表的作用是让 LLM 控制层按照名字查找生成器而不需要感知具体类。# 文件路径coder/registry.py from coder.base import BaseCoder from coder.model_coder import ModelCoder from coder.rest_coder import RestCoder class CoderRegistry: 确定性编码器注册表 def __init__(self): self._coders: dict[str, BaseCoder] {} self._register(ModelCoder()) self._register(RestCoder()) def _register(self, coder: BaseCoder): self._coders[coder.name] coder def get(self, name: str) - BaseCoder: if name not in self._coders: raise KeyError(fUnknown coder: {name}) return self._coders[name] def available_coders(self) - str: return , .join(self._coders.keys())注册表在这套架构里的价值很清楚如果要新增一种代码生成能力只需要新增一个 BaseCoder 子类并注册LLM 控制层不需要任何改动。4.3 编写 LLM 控制层LLM 控制层是整个系统的关键。它的任务是把自然语言需求转换为 JSON 计划。为了让输出稳定提示词中必须明确 JSON 结构并限制可选生成器。以下是一个简化版本# 文件路径llm/prompts.py SYSTEM_PROMPT 你是一个代码生成计划器。你不直接写代码而是根据用户需求输出一个 JSON 计划。 计划中每个任务包含 generator 和 params 两个字段。 可选生成器model_generator、rest_controller_generator model_generator 参数 - entity: 类名 - fields: 字段数组每个元素包含 name 和 type rest_controller_generator 参数 - entity: 类名 - base_path: 可选REST 基础路径 只输出 JSON不要输出任何解释。 .strip()然后是实现控制器的代码。这里要求 LLM 返回严格的 JSON并用response_format{type: json_object}保底。# 文件路径llm/controller.py import json from openai import OpenAI from llm.prompts import SYSTEM_PROMPT class LLMController: LLM 控制层把自然语言需求转换为代码生成计划 def __init__(self, base_url: str, api_key: str, model: str): self.client OpenAI(base_urlbase_url, api_keyapi_key) self.model model def plan(self, user_requirement: str) - dict: response self.client.chat.completions.create( modelself.model, temperature0, response_format{type: json_object}, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_requirement}, ], ) content response.choices[0].message.content return json.loads(content)这里有一个非常重要的设置temperature0。虽然 deterministic coder 已经保证了代码生成阶段可复现但 LLM 规划阶段的不稳定性仍然会传导到最终产物。把 temperature 设为 0可以最大程度减少规划阶段随机性。4.4 主流程串联现在把控制器、注册表、生成引擎串起来。主流程如下接收用户需求LLM 输出计划 JSON遍历计划中的任务从注册表取出对应生成器调用生成器得到代码写入 output 目录。# 文件路径main.py import json import os import sys from coder.registry import CoderRegistry from llm.controller import LLMController def run(requirement: str, output_dir: str output): controller LLMController( base_urlhttp://localhost:8000/v1, api_keyEMPTY, modelyour-model-name, ) registry CoderRegistry() plan controller.plan(requirement) print(生成的计划) print(json.dumps(plan, ensure_asciiFalse, indent2)) os.makedirs(output_dir, exist_okTrue) for task in plan.get(tasks, []): generator_name task[generator] params task[params] coder registry.get(generator_name) code coder.generate(params) entity params.get(entity, Generated) file_name f{entity.lower()}_{generator_name.replace(_generator, )}.py file_path os.path.join(output_dir, file_name) with open(file_path, w, encodingutf-8) as f: f.write(code) print(f已生成文件{file_path}) if __name__ __main__: requirement sys.argv[1] if len(sys.argv) 1 else 创建一个 User 数据类包含 id、name、email 字段并生成对应的 REST 接口 run(requirement)如果 LLM 返回的计划如下{ tasks: [ { generator: model_generator, params: { entity: User, fields: [ {name: id, type: int}, {name: name, type: str}, {name: email, type: str} ] } }, { generator: rest_controller_generator, params: { entity: User, base_path: /users } } ] }那么 output 目录下会生成两个文件user_model.pyfrom dataclasses import dataclass dataclass class User: id: int name: str email: struser_rest.py# User REST Controller class UserController: base_path /users def list(self): raise NotImplementedError def get(self, id): raise NotImplementedError def create(self, data): raise NotImplementedError def update(self, id, data): raise NotImplementedError def delete(self, id): raise NotImplementedError这就是一个完整的 Sif 1.0 最小流程。LLM 没有直接生成这些代码它只负责把“创建一个 User 数据类包含 id、name、email 字段并生成对应的 REST 接口”这句话拆解成结构化计划。真正产出代码的是确定性生成器。4.5 运行与验证结果运行命令很简单python main.py 创建一个 Product 数据类包含 id、name、price 字段并生成对应的 REST 接口预期输出类似生成的计划 { tasks: [ { generator: model_generator, params: { entity: Product, fields: [ {name: id, type: int}, {name: name, type: str}, {name: price, type: float} ] } }, { generator: rest_controller_generator, params: { entity: Product, base_path: /products } } ] } 已生成文件output/product_model.py 已生成文件output/product_rest.py由于生成器是确定性的多次运行相同计划得到的结果完全一样。这比直接让 LLM 生成代码更接近工程化要求。5. 常见问题与排查思路这一节汇总我在实际搭建类似系统时遇到过的高频问题按问题现象、原因、解决思路整理成一张速查表。问题现象常见原因解决思路LLM 返回的不是合法 JSON提示词约束不足或模型不支持 JSON 模式在提示词中给出完整 JSON 示例优先使用 response_format增加异常重试逻辑LLM 输出了未注册的生成器名可选生成器列表没有写进提示词把注册表的 available_coders() 动态拼进 system prompt生成代码出现空字段LLM 计划中 fields 数组为空在计划校验阶段增加参数非空校验并让 LLM 重新生成参数类型与生成器预期不一致LLM 没有严格遵守字段类型约束使用 Pydantic 定义计划结构解析失败时返回错误信息给 LLM 重新规划连续多次输出结果不一致LLM 规划温度过高显式设置 temperature0必要时对 LLM 输出做归一化排序新增生成器后仍然报 Unknown coder注册表未注册或文件未导入检查注册表构造器里是否创建了对应实例生成代码存在语法错误模板拼接逻辑有边界问题增加一次性 python -m py_compile 校验并打印出错文件如果你希望更稳健可以在 LLM 控制层外面包一层校验函数。下面给出一个用 Pydantic 做计划校验的示例。# 文件路径validator.py from typing import List, Optional from pydantic import BaseModel, Field class FieldSpec(BaseModel): name: str type: str class TaskSpec(BaseModel): generator: str params: dict class PlanSpec(BaseModel): tasks: List[TaskSpec] def validate_plan(raw_plan: dict) - PlanSpec: 校验 LLM 输出的计划不合法时抛出异常 return PlanSpec.model_validate(raw_plan)然后修改 main.py增加一步 validate_planplan controller.plan(requirement) validated_plan validate_plan(plan)这样做的好处是字段缺失、类型错误会直接抛出异常而不会等到生成代码时才暴露。另一个常见坑是LLM 容易把 params 里不需要的字段也带出来。比如 model_generator 并不需要 base_path但 LLM 可能顺手加上。生成器内部应该忽略多余字段而不是报错。上面两个生成器实现已经做到了这一点因为它们只从 params 取出自己关心的字段。6. 最佳实践与工程建议如果只是做一个 Demo上面 4 个步骤已经完全够用。但如果你想把“LLM 控制 deterministic coder”这套模式落地到真实项目下面这些工程建议非常值得重视。6.1 先定义好“代码生成协议”LLM 和 deterministic coder 之间的协议是整个系统最核心的约束。协议一旦定义清楚LLM 的规划自由度、coder 的参数校验、验证层的回归测试就都有据可依。建议在项目里维护一份协议文档至少包含生成器名称列表每个生成器的必填参数和可选参数参数类型输出文件命名规则已知限制。这套协议就相当于 LLM 的“API 文档”。在提示词里注入精简版在验证层实现参数校验在注册表实现生成器查找。6.2 把 LLM 输出限制在“小决策”范围内Sif 1.0 的核心不是让 LLM 做更多而是让 LLM 做更少但更准确。实际设计中可以让 LLM 决策以下内容选择哪个生成器给生成器填充哪些参数生成结果是否满足原始需求哪些任务可以并行生成出现验证错误时如何调整参数。不要让它决策代码缩进风格、注释格式、导入顺序、框架选型——这些都应该由 deterministic coder 内部逻辑固定。这样做的收益是模型能力不会成为代码质量的上限。今天用开源模型明天换商业模型只要它还能输出结构合理的计划 JSON整体系统质量就保持稳定。6.3 结果缓存与回归测试确定性生成器带来的直接好处就是可以缓存。如果 LLM 输出了相同计划理论上不需要重新生成代码。可以按计划的哈希值做结果缓存import hashlib import json def plan_hash(plan: dict) - str: raw json.dumps(plan, sort_keysTrue, ensure_asciiFalse) return hashlib.sha256(raw.encode(utf-8)).hexdigest()同时每次生成的结果都应该纳入回归测试。最简单的做法是在生成代码后执行编译检查或测试断言更进阶的做法是建立 golden file基线文件机制比较本次生成结果与基线文件的差异。只要不是有意修改代码生成器任何 diff 都应该被当成异常处理。6.4 日志与审计生产环境中LLM 的每一次规划都应当记录完整上下文包括用户原始需求LLM 输出的计划 JSON计划哈希使用的模型名称与版本生成时间生成结果是否通过校验。这些日志既用于问题追踪也用于后续统计哪些生成器使用频率高、哪些需求 LLM 经常规划失败。它们会反过来帮助你改进提示词和协议。6.5 安全边界虽然本文讨论的是代码生成框架但涉及 LLM 调用时仍然要提安全边界。对于企业内部工具建议做到LLM 请求只发送必要数据避免把完整数据库结构、生产配置、敏感代码注入提示词对 LLM 输出做严格 JSON 解析不直接执行任何模型返回的脚本生成器代码不拼接 shell 命令避免注入风险API Key 使用环境变量或密钥管理服务管理不写入代码仓库如果 LLM 规划出现超时或异常应有降级策略而不是直接失败。6.6 渐进式替换 LLM 模型Sif 1.0 这类架构非常适合做模型灰度替换。同一个用户需求分别用旧模型和新模型生成计划然后把计划交给同一个 deterministic coder对比最终产物差异。由于确定性 coder 消除了代码生成阶段的随机性最终产物差异可以精确归因到 LLM 规划能力本身。这对评估模型效果非常有帮助。甚至可以批量构造测试集统计新旧模型在“计划成功率”“参数正确率”“无效生成器使用概率”等指标上的差异形成结构化的模型评测报告。7. 从 Demo 到生产的关键一步很多同学看到这里可能会有一个疑问上面示例里的 deterministic coder 太简单了真正的项目里代码生成哪有这么容易这就是 Sif 1.0 这类架构最值得深入的地方。它的核心贡献不是某个具体的代码生成器而是把“自然语言需求 → 最终代码”这个原本无法拆解的黑盒过程拆成了两层LLM 负责理解与规划输出可校验的中间表示确定性系统负责生成与执行输出可复现的最终产物。只要这个拆解成立后面的扩展就是水到渠成的事。你可以把 model_coder 换成更复杂的模板引擎把 rest_coder 换成带强类型约束的代码生成器也可以通过集成编译器和测试框架让验证层更自动化。下一步可以继续研究的方向包括用形式化 schema 定义生成器协议并自动生成校验器引入多轮反馈让 LLM 根据编译错误修正计划构建“计划日志”数据集用它微调一个更擅长规划的小模型把确定性编码器扩展到数据库迁移、API 文档生成、配置文件生成等场景。如果这篇文章对你有帮助建议先照着上面的流程搭一个最小版本把“模型规划 → 生成器执行 → 代码输出”三个环节跑通。跑通之后你一定会更清楚自己的项目里哪些部分应该交给 LLM哪些部分应该回归确定性系统。