
LLM 控制确定性编码器这个概念第一次看到时我第一反应是这两件事能放在一起吗vibe coding 在开发者社区里讨论很多意思是让 AI 凭感觉写代码人主要看运行结果代码细节全交给模型补全。可工程环境不能只靠感觉同一个需求今天生成和明天生成的代码应该尽量一致出了问题要有据可查。Sif 1.0 给出的思路是反过来的让 LLM 扮演 vibe coder负责把模糊需求翻译成结构化规格再由 deterministic coder 把这些规格变成稳定可复现的代码结果。这篇文章不打算做概念复述而是按照实际跑一遍的思路拆一拆它适合谁、怎么落地、有哪些边界。1. 先理解 deterministic coder 解决了什么再谈 LLM 控制1.1 vibe coding 的爽点和痛点vibe coding 最大的好处是降低起点。不会用某些库、不记得 API 名称、不想写重复的胶水代码把想法说清楚LLM 能迅速给你一段能跑的东西。很多开发者的第一版脚本就是这么出来的这也是它能在社区里快速传播的原因。但痛点是可复现性。同一个提示词换一个模型版本输出风格变了同一个模型把 temperature 调高一点函数命名、参数顺序、依赖选型可能完全不同。这种随机性在日常写脚本时可以接受在上线、评审、审计和协作场景里就很难受。代码审查不是只看“这段代码能不能跑”还要看“它为什么这么写、有没有其他影响”。如果每一次生成的代码都长得不一样审查成本会翻好几倍。所以当标题里出现 LLM control of deterministic coder 时我关注的重点不是“让 LLM 写更多代码”而是“如何让 LLM 的自由发挥止步于结构化规格层最终代码交给确定性引擎生成”。这样既保留了自然语言输入的效率又把最终产物拉回可控轨道上。1.2 deterministic coder 不等于模板引擎很多人听到确定性生成第一反应是“这不就是模板替换吗”。如果只是把占位符替换成变量确实不需要 LLM 参与。但 deterministic coder 可以做得更多根据规格选择不同的代码生成路径。根据依赖关系生成多个文件。统一格式化、统一命名规范。固定第三方库版本或生成配置文件。输入相同输出一定相同。这是它和 LLM 直接输出的最大区别。Sif 1.0 的定位更像是在 LLM 和最终代码之间加了一层“翻译协议”。LLM 不再是拍板代码的人而是把需求转换成机器可校验的规格。这样做的好处是即使换一个 LLM只要规格合法最终生成的代码也能保持稳定即使规格有微小差异也可以从 diff 里看出来而不是靠肉眼读一段完全重写的代码。1.3 什么样的人适合这套思路适合这套思路的人通常有这几个特征想在项目里引入 LLM但不希望代码随机变化。生成结果要进入评审、测试、审计流程。正在做 LLM 应用开发想把 Agent 的工具调用能力用起来而不是让 Agent 直接写一堆不可控代码。已经踩过“LLM 生成的代码能跑但不敢上线”的坑。不太适合的场景是让 LLM 做创意写作、自由问答、快速原型探索。那些场景要的是发散性不是确定性。如果你只是想快速得到一个能跑的 Python 脚本直接用聊天工具就够了不需要套一层确定性编码器。这个方案的目标是稳定产出不是让人偷懒到底。2. 本地环境准备LLM 不一定非得和编码器在同一台电脑2.1 最小运行环境清单最小运行环境可能比想象中宽松。Sif 1.0 这种设计真正的高配需求通常在 LLM 那一端而不是确定性编码器这一端。我一般会先确认四类条件操作系统Windows、macOS、Linux 都可以主要看确定性编码器有没有平台限制。Linux 服务器跑批量任务最省心。运行时如果是 Python 生态建议 Python 3.10 以上如果是 Node 生态建议 18 以上。原始材料里没有明确说明 Sif 1.0 的运行时所以落地前先看项目仓库的依赖声明。LLM 服务本地推理可以用 Ollama、llama.cpp、vLLM 起一个接口远程就准备一个 OpenAI 兼容接口地址。资源如果本地跑 7B 到 14B 模型16GB 内存是起步有 GPU 更好。如果只是调用远端 API客户端不需要大显存但要有稳定的网络和请求超时控制。这里有个经常被问的问题LLM 是不是必须和主程序在同一台电脑上不是。只要接口地址能访问模型服务可以单独放在一台有 GPU 的机器上编码器客户端放在另一台低配机器上。这也是模块化设计最值得肯定的地方你不必为了让流程跑起来把所有东西都装到一台机器里。2.2 配置 LLM 接口与安全边界建议把 LLM 接成 OpenAI 兼容接口。这样不管你本地跑的是 Ollama、llama.cpp还是云端模型服务客户端代码都可以保持一致。环境变量示例# 本地模型服务 LLM_BASE_URLhttp://127.0.0.1:11434/v1 LLM_API_KEYollama LLM_MODELqwen2.5-coder:7b # 远程服务则换成你自己的 base_url 和 key不要把密钥写死在代码里。Sif 1.0 这类工具一旦接入批量任务环境变量和配置文件是更稳的做法。安全边界这一步不能省。设计上最好让 LLM 只输出规格不直接执行命令。exec、eval、shellTrue 这类调用都要谨慎尤其当输入来自外部任务清单时。如果你要做工具调用也应该把确定性编码器封装成一个受限的工具节点由主流程做权限校验而不是把系统权限直接暴露给模型。2.3 准备好一套输入输出协议输入输出协议是整个方案最核心的设计。没有协议的确定性编码器和裸调 API 没有区别。协议层是一个中间表示。自然语言进入 LLMLLM 输出 JSON 或 DSL然后确定性引擎再把它转成代码。为什么要多这一层LLM 的自然语言千变万化但 JSON 的字段结构可以固定。字段可以校验非法值可以拦截。生成结果可以缓存、可以 diff、可以回滚。一个最小 JSON Schema 示例{ type: object, required: [action, target, params], properties: { action: { enum: [create_script, update_config] }, target: { type: string }, params: { type: object } } }协议越窄LLM 越难发挥错。协议太宽LLM 仍然会给你一堆奇怪字段。实际测试时我建议先把 schema 定义成“能覆盖 80% 任务的最小集合”跑通后再逐步扩展而不是一开始就追求通用。3. 单任务跑通从一句自然语言到一份可复现的代码3.1 第一步写一条最小指令我建议从最小样例开始。例如生成一个 Python 脚本读取 data.csv过滤出 age 大于 18 的行把结果写入 output.csv。直接把这个需求发给模型模型往往会给你一段完整 Python 代码里面还带注释和解释。如果后续用确定性引擎做渲染这段代码反而不好处理。所以提示词里要明确约束只输出结构化规格不输出代码。示例你是一个需求转译器。根据用户需求输出 JSON格式如下 { action: create_script, target: filter_csv.py, params: { input_file: data.csv, output_file: output.csv, filter_expression: age 18 } } 不要输出其他文字不要使用 Markdown 代码块。把指令边界写清楚比把需求描述得非常详细更重要。目的是让模型进入结构化输出状态而不是自由发挥。很多第一次尝试的人失败不是因为模型不理解需求而是因为提示词里没有规定输出格式。3.2 第二步让 LLM 输出结构化规格请求 LLM 时把 temperature 设低比如 0 到 0.3。max_tokens 要留足避免 JSON 被截断。如果接口支持 JSON mode 或 response_format就打开。不支持时客户端要做一层解析兜底先尝试 json.loads失败就从中提取代码块内容再解析。LLM 返回一个 JSON 后先校验再进入下一步。不要直接用。格式错误越早拦截后续问题越少。一个通过校验的示例规格{ action: create_script, target: filter_csv.py, params: { input_file: data.csv, output_file: output.csv, filter_expression: age 18, has_header: true } }3.3 第三步交给确定性编码器生成最终文件规格校验通过后确定性编码器按规则渲染最终代码。这块的代码可以是预置的模板函数也可以是一组代码块组装器。无论哪种核心是确定性同一份 JSON每次渲染的内容完全一致。假设有一个 render_create_script 函数它根据 params 生成一个完整 Python 脚本。这个函数不应该依赖随机数、当前时间、网络请求等可变信息它只做确定性的字符串组装和格式化。生成后不要急着批量跑先运行生成的文件确认它能正常跑通。一次成功标准文件生成成功。生成的脚本没有语法错误。脚本运行后 output.csv 内容符合过滤预期。如果这一条都没跑通后面的并发和批量没有意义。不要用“大模型生成的东西跑不起来正常”来安慰自己这里有确定性引擎兜底跑不起来就是规格层或渲染层有 bug。3.4 一个容易踩的坑提示词直接要求生成代码我见过很多失败的尝试问题不在模型而在提示词写成了“帮我写一个脚本”。模型确实会写但输出带有 Markdown、解释、两种实现方式而且往往和上一次输出不一致。一旦你把“生成最终代码”的任务交给 LLM确定性就很难保证。Sif 1.0 这类方案的核心正是把“生成最终代码”从模型手里拿走让模型只负责“理解并翻译需求”。所以提示词设计必须服务于协议层而不是服务于代码生成。这是一个思维转换不做这个转换后面加再多参数都白搭。4. 关键参数与接口设计如何让 LLM 只做“客户”不做“实现者”4.1 temperature、max_tokens 与输出格式约束很多人以为模型输出不稳定是因为 temperature 高把温度调到 0 就万事大吉。实际上temperature 为 0 只是更接近贪心解码不保证所有框架都严格关闭采样。有些推理服务默认还会做 top_p、top_k 采样。所以判断指标不是“temperature 设了多少”而是“重复请求同样输入输出是否 byte 级别一致”。参考参数表参数学习验证建议批量生产建议temperature0 到 0.30并确认服务端关闭随机采样max_tokens512 到 1024按规格大小估算至少留 30% 余量response_format有就开 JSON mode必须开并配合 schema 校验stop不需要设置结束标记防止多余解释重试次数12 到 3 次每次把校验错误回喂给模型max_tokens 太短是常见问题。LLM 输出 JSON 被截断解析失败但不是模型逻辑错是输出空间给少了。这时把 max_tokens 调大比换提示词更有效。4.2 输出校验不能省校验层是整个架构的保险丝。即使 LLM 返回了合法 JSON也不代表字段值一定可用。比如 action 不在白名单里或者 params 里出现了一个我们不认识的参数。强校验规则可以在早期拦截这些问题。校验失败时最好把错误信息拼到提示词里再让 LLM 重新生成。例如你上次输出的 JSON 缺少 required 字段 target。 请重新生成严格遵循以下 schema...重试不是无脑重跑。如果三次重试都失败应该停止并记录失败原因而不是无限请求。这里要注意重试时的提示词会把失败原因带进去所以你会看到“第二次输出往往比第一次更符合要求”。如果第二次还不行通常不是模型问题是 schema 本身定义得不清楚。4.3 缓存、日志和版本管理LLM 调用是有成本和耗时的。批量任务里同一份指令反复请求同一个模型是浪费。可以按“提示词内容 模型名 参数”生成缓存 key把 LLM 返回的规格缓存起来。第二次命中缓存直接跳过模型请求。确定性编码器生成的结果不需要缓存因为它本身就快、可复现。但建议把规格文件和生成代码都提交到仓库。这样哪天产线出问题你可以回答“这个代码是根据哪份规格生成的”。记录元数据时至少包含任务 ID、指令哈希、模型名称、模型版本、temperature、schema 版本、生成时间。日志里不要记录完整 API key。这些字段在排查时非常有用尤其是当某个生成结果在评审时被质疑你能快速定位到当初的模型和参数。4.4 并发和超时控制执行顺序应该是先并发 1跑通再并发 2 到 4最后根据资源决定是否继续扩大。不要一上来就开最大并发否则本地模型会被排队请求拖垮远程 API 也有可能触发限流。超时设置要看你的模型速度和网络环境。我个人一般给 LLM 请求设置 60 到 120 秒超时。如果经常超时先看模型推理速度而不是无限调大超时。超时机制不是摆设它是批量任务里防止任务卡死的关键。确定性编码器部分如果执行超过 30 秒通常不是渲染慢而是输入规格有问题比如文件路径不存在、依赖缺失。这时候先看日志再改参数。5. 批量生成把 LLM 从交互式聊天变成离线任务队列5.1 批量任务和单任务的区别单任务跑通只说明方案可用。批量任务跑通才说明方案能进生产。区别在哪单任务只需要一次成功批量任务需要处理失败不中断、任务状态可追踪、输出不互相覆盖。聊天式调试在批量场景行不通。你不能一边跑批次一边往对话里塞新指令必须把任务清单预先写好让程序顺序执行或并发执行。批量任务更像离线流水线输入是一份文件输出是一堆文件中间所有状态都要可观测。如果做不到它就只能停留在 demo 阶段。5.2 任务清单文件设计我建议用 JSONL 或 CSV 维护任务清单。每行代表一个独立任务。字段可以包含task_id全局唯一标识。instruction完整自然语言需求不要依赖前文。target_path生成文件的目标路径。params额外的结构化参数。statuspending / done / failed。示例{task_id: task_001, instruction: 生成一个 Python 脚本读取 data.csv过滤 age 18输出 output.csv, target_path: generated/filter_csv.py} {task_id: task_002, instruction: 生成一个 shell 脚本把 logs 目录下所有 .log 文件压缩为 archive.tar.gz, target_path: generated/backup_logs.sh}指令尽量写成完整句子。批量场景里没有上一轮对话上下文模型只能靠指令本身理解需求。含糊的指令会让失败率上升。如果你发现很多任务失败是因为指令不完整不要怪模型先把指令改清楚。5.3 队列、失败重试与断点续跑不要为了追求“一次性成功”而把所有任务都塞到一个进程里跑。先顺序执行一轮记录每个任务的状态。失败任务单独保存修改提示词或 schema 后重跑。断点续跑是长期批量任务的关键。每次任务完成后更新状态文件程序重启后自动跳过已完成的 task_id。否则跑到一半崩溃重来一轮浪费的不只是时间还有 API 费用。批量任务是否稳定看三个数字成功率成功任务数除以总任务数。吞吐每分钟完成多少任务。失败原因分布是超时、格式错误还是目标路径冲突。如果成功率低于预期先不要调并发回到单任务验证看看是不是指令写得太模糊。批量任务里最常见的现象是“80% 任务稳定20% 任务随机失败”这通常不是并发问题而是那 20% 任务的输入格式或语义和大部分任务不一致。5.4 批量常见的输出问题批量任务容易踩几个坑任务 ID 没有全局唯一导致结果互相覆盖。所有生成文件都放到同一个目录后续很难定位。任务失败后不留日志找不到失败原因。人工改过生成文件下次重跑被覆盖。每一条都能通过合理的目录结构和状态文件解决。建议目录结构按任务 ID 分目录generated/ task_001/ spec.json filter_csv.py task_002/ spec.json backup_logs.sh这样任务失败时你可以同时看到模型生成的规格和实际产出的文件排查效率会高很多。6. 排查链路报错不一定是模型问题更多是协议和输入问题6.1 从现象分层排查遇到问题先别急着怀疑模型能力。我通常按这个顺序查看现象是没有输出、JSON 解析失败、生成代码无法运行还是批量任务卡住看 LLM 返回原文有没有被截断、有没有多余文本、字段是否符合 schema。看规格层JSON 校验是否通过字段值是否在白名单内。看确定性引擎日志渲染时报错信息是什么。看文件系统目标目录是否存在、权限是否可写、路径是否含中文或特殊字符。很多问题都不是模型不行而是路径权限、依赖版本或提示词约束不到位。这个排序可以帮你在五分钟内把问题缩小到某一层而不是从头到尾猜。6.2 LLM 输出异常的处理如果 LLM 返回了带 Markdown 代码块的 JSON说明提示词没有强调“不要使用 Markdown”或接口没开 JSON mode。如果 JSON 被截断说明 max_tokens 不够。可以先打印返回原文的长度对比正常输出长度。如果格式合法但字段乱编说明 schema 校验和重试逻辑没有生效或者提示词里的 schema 太宽。如果相同输入两次结果不一样先确认 temperature 是否真的为 0再确认推理服务有没有做热更新、模型版本是否一致。不要第一时间怀疑代码逻辑。6.3 确定性编码器报错的处理确定性引擎报错常见原因有三个规格不匹配、类型错误、路径错误。规格不匹配最好解决回到 schema 校验层把不匹配字段拦截掉。类型错误往往是因为 LLM 把数字输出成了字符串需要在校验层做类型转换。路径错误则需要检查工作目录和权限。确定性引擎的报错越早出现越好。因为它发生在渲染阶段还没有真正写文件成本很低。如果等到运行生成代码时才报错说明规格层没有覆盖到某些运行条件。6.4 本地模型和远程 API 不一致同一个提示词在本地模型和远程模型上输出可能差异很大。这不是 bug而是不同模型训练目标、tokenizer、指令遵循能力不同。解决方案在任务元数据里记录模型名和版本。对关键任务做固定模型绑定。不要只看一次输出就判断好坏至少跑三条样例对比。如果远程 API 是 OpenAI 兼容接口本地模型也用同样格式客户端代码尽量保持一致。这样切换模型时不需要改整个流程。换个模型不是小事批次里已生成的结果可能在风格和结构上有差异尽量在切换前先跑一小批验证。7. 真正落地时的边界判断哪些事别指望 LLM 接管7.1 能覆盖的范围这类 LLM 控制确定性编码器的方案适合生成不复杂但重复性高的代码脚本、配置、测试数据、脚手架文件、批处理命令。这些场景的规格容易定义输出也容易验证。不适合用于复杂业务系统。大型系统需要架构设计、权限模型、领域建模、性能评估这些不是一句自然语言加一个确定性模板能解决的。如果硬要做结果是 LLM 产出规格确定性引擎生成一堆表面完整的模板文件但业务逻辑仍然缺失。7.2 不确定性的残留点即使把 temperature 设为 0也不等于完全确定。模型更新、服务端采样开关、网络超时重试都可能让输出发生变化。也就是说确定性主要靠“最终代码由确定性引擎生成”来保证而不是靠“LLM 每次输出完全一致”。这个边界要分清。如果你接受不了任何随机性就不要让 LLM 直接落盘代码。所有落盘内容必须经过规格校验和确定性渲染。这是这套方案和直接调用 LLM 写代码之间的分界线。7.3 什么时候该切换方案如果你发现大量生成结果需要人工修改不要继续调提示词。这说明规格层没有抽象出任务的核心。先把任务拆小把不稳定的部分留在规格层之外或者用规则代码处理再让 LLM 只负责完全可控的翻译。如果一次任务重试超过三次仍失败停止重试。看看是模型不支持复杂指令还是 schema 要求太细。后者可以适当放宽前者要换模型。如果批量任务中位耗时超过你的容忍度先加缓存再考虑换更小的模型。不要上来就加并发并发会让接口延迟更高。模型体积和精度也会影响速度如果你在用大模型跑简单任务可以试试换更小的量化版本代价是输出质量可能有变化。FP16、BF16、FP32 这些精度选项不同环境下的耗时差异明显但这不是 Sif 1.0 本身能解决的要回到推理部署层面去调。7.4 和常见 LLM 编排框架的关系Sif 1.0 这类工具可以视作 LLM Agent 的一个工具节点。Agent 负责任务规划确定性编码器负责具体产出。在 Spring AI、自建的 MCP Client、RAG 流程里都可以接。把项目代码规范、依赖约定、历史生成样例放到 RAG 里LLM 在输出规格前可以先检索到已有约定效果会更好。但这里要注意