
提示词本质上是一段写给大模型看的“伪代码”运行时才被解释执行——每次调用模型都要把这段话重新读一遍、理解一遍、推理一遍。哪怕你问的是同一个问题走一遍提示词的成本一分不少。“Compile by Training”这个思路解决的事情很直接把提示词“编译”成确定性的函数让大模型在推理阶段退居二线只在训练期出场当老师。我在过去几个项目里陆续把客服意图识别、工单分类、日志异常打标这几类任务从“提示词调用”改成了“函数调用”延迟和成本都掉了一个数量级稳定性反而更好了。这篇文章我会沿着编译的视角重新拆解提示词工程把从思路到落地的全流程、参数选择和踩过的坑一起写出来适合手里有结构化提取、分类、打标类需求又不想每次推理都背着大模型跑的团队参考。1. 核心思路拆解为什么要把提示词「编译」成函数1.1 提示词模式的三个老毛病用大模型 提示词处理线上请求表面看很灵活用久了你会发现三个绕不开的问题。第一个是成本和延迟。每来一条文本模型都要把完整的提示词重新读一遍再生成几百上千个 token。遇到结构复杂的任务提示词动辄上千字输入 token 翻倍单次调用成本翻倍甚至更多。在高并发场景下排队和超时也跟着来。很多团队一开始图方便等量上来之后才发现光推理账单就吃不消。第二个是不确定性。同样一条输入同样的提示词多次调用结果可能不一样。温度调成 0 会好一些但遇到模型更新、参数版本切换结果又会漂移。对业务系统来说这是最难受的一点——线上不稳定出问题没法复盘因为同一段输入每次跑出的结果可能不同。QA 要反复回归运营不敢全自动。第三个问题平时不太被提起就是提示词本身的“可复制性”。提示词一旦被别人拿到你的业务逻辑、判断规则、提取字段全暴露了。网上隔三差五出现“某某工具提示词被套出来了”的讨论本质就是这个原因。提示词是明文资产没有编译环节谈不上保护。1.2 从编译原理看 Compile by Training如果你写过传统软件这套思路其实不陌生。传统编译过程的三个阶段源码 - 编译器 - 机器码。对应到 Compile by Training 范式中映射关系是这样的源代码提示词以及提示词里写的任务描述、输出格式、约束条件、示例。编译器训练或蒸馏过程。大模型根据提示词生成大量输入输出样本再用这批样本去训练一个小参数模型。机器码训练得到的小模型权重或者进一步固化成一段函数代码和规则的组合。传统编译完成后程序运行时不再需要编译器参与。Compile by Training 完成后线上推理不再需要带着大模型只需要跑那个“编译产物”。换句话说提示词的执行阶段从“运行时解释”变成了“编译期固化”运行代价从每次完整的 transformer 前向推理降成了一次极小的模型推理或几行规则判断。这个过程里还有个隐藏收益可测试性。传统程序编译后会报错、会 crash你能在编译期发现问题。提示词则没有“编译期检查”只有到了线上跑出错误结果你才知道。Compile by Training 把提示词转成了函数你就能为这个函数写单元测试、做边界检查、跑回归数据错误在发布前就被拦截下来。1.3 什么任务适合「编译」什么任务不适合这不是万能药适合与不适合的界限其实很清晰。适合的是任务边界固定、输出结构明确、评价标准客观的场景。典型的例子文本分类、意图识别、实体抽取、工单打标、内容审核初筛、邮件自动分拣。这类任务最大的特点是“只要判断规则清楚写代码也能做只是用提示词写起来更快”。正因如此它们适合被编译一旦通过训练把判断能力固化下来输出稳定、速度快、成本低。不适合的是开放性生成、需要常识推理、依赖上下文理解的复杂任务比如写营销文案、做代码审阅、处理长文档的深度问答。这类任务对模型的推理深度要求高小模型一旦“编译”不好能力衰减会非常明显。我在项目里验证过开放式生成的场景强行做函数化指标掉得没法看后来还是保留了大模型在线。判断标准就一条你的任务输出能不能被一个确定的 schema 完整描述能就有编译的空间不能别强求。2. 技术路径对比蒸馏、规则化与混合路由2.1 蒸馏路径大模型当老师小模型接班最常见的 Compile by Training 实现是“大模型生成数据 - 小模型训练”的蒸馏路径。具体说先写好一套高质量提示词把提示词当作“老师”的执行逻辑让它批量产出输入输出样本再拿这些样本训练一个参数量小得多的模型让“学生”在线上接管推理。这里有几个关键参数要注意。第一个是生成样本时的温度设置。我实测下来生成训练数据时的 temperature 建议控制在 0.3~0.7 之间太低容易让样本单一太高会引入大量噪声。太低的温度会让同一类输入的输出几乎一样缺少负例和边界变化学生学不到边界温度太高老师开始“自由发挥”输出格式都可能漂移后期清洗成本直线上升。第二个是模型选型。线上承载能力决定了下限。我常用的区间是 300M~3B 参数。纯粹的短文本分类任务300M~800M 就够复杂的实体抽取1B~3B 效果更稳超过 3B 之后收益明显递减但推理成本却线性上升。我的经验是先拿最小模型跑通用评估集看指标不够再往上加别一上来就上 7B。第三个是训练方式。基础模型 LoRA 微调是目前性价比最高的做法。LoRA 不改变原模型权重只训练低秩矩阵训练参数占比可以压到 1% 左右。对蒸馏任务来说LoRA 还有一个额外的好处它可以作为一次“编译快照”。每次提示词改了重新训练一个 LoRA adapter线上可以同时部署多个 adapter灰度切换非常方便比全量微调后换整个模型要灵活得多。2.2 规则化路径把提示词翻译成 if-else不是所有提示词都必须走“训练”这条路。有一部分提示词拆开看就是几组条件和对应动作完全可以翻译成代码。举个例子。提示词里如果写“如果用户留言里提到退钱、退款、退费判断为退款意图”这条逻辑完全可以用正则加关键词直接表达。再比如“如果文本长度小于 10 且包含问号标记为咨询”这也是一条确定性规则。把这些规则提取出来用代码落地运行一次是纳秒级不需要任何模型参与性能和稳定性都是最优的。实际操作中我习惯先把提示词做一次“逻辑降维”逐条拆出里面的判断条件、边界条件和输出映射能落成代码的直接落成代码落不成的才丢给模型。一个典型的工单分类任务最后可能有 40% 的逻辑是用正则和关键词覆盖的剩下的 60% 才靠训练出来的小模型兜底。这样整体效果最好而且代码覆盖的部分天然可测试、可审计。当然要承认规则化有明显的瓶颈提示词里的模糊判断很难全部翻译成硬编码规则。比如“语气有明显不满情绪”这种要求写正则很难写干净。强行写出来的规则要么漏判要么误杀。所以规则化适合作为第一步不适合作为唯一方案。2.3 混合路由置信度兜底大模型训练完小模型后你会发现一个问题总有那么 2%~5% 的输入小模型自己都吃不准。这些长尾样本是线上事故的主要来源——不该判为投诉的判成了投诉该升级处理的没有升级。我的做法是加一个路由层。小模型输出结果时同时输出置信度分数置信度高于阈值的直接返回结果低于阈值的再调大模型用完整提示词处理。这个策略在工程上叫 fallback效果立竿见影线上 95% 以上的请求走小模型成本和延迟降下来剩下 5% 的疑难杂症交给大模型准确率有保证。阈值一般设置在 0.85~0.95太低了兜不住底太高了又失去了降本意义。这个参数需要根据模型验证集的分布反复调没有固定值。路由层还能帮你做一件很重要的事数据回流。每次大模型兜底处理完的样本打上标记累积一段时间后重新加入下一轮训练数据。这就形成了“编译”的闭环线上不断产生高质量样本再训出的新“函数”能覆盖越来越多的边界情况需要回退到大模型的样本比例会逐渐下降。3. 实操流程把一条提示词“编译”成函数这一节我完整走一遍实操流程。以“客户留言工单分类”为例输入一段客户留言输出结构化结果包含意图分类、紧急程度、关联订单号如果有。这个任务在提示词模式下可能是 500 字的提示词我要把它编译成一个函数。3.1 第一步把提示词拆成可测试的规格动手写代码前先把提示词拆成规格。这一步至关重要因为它定义了“编译”的目标约束。我的规格模板是这样{ input: { type: string, max_length: 2000, description: 客户留言原文 }, output: { type: json, schema: { intent: [refund, complaint, consult, other], priority: [high, medium, low], order_id: [string, null] } }, constraints: [ intent 只能取枚举值之一, priority 为 high 的条件是留言包含投诉关键词或多次催促, order_id 提取后需要校验 18 位订单号格式匹配不到则返回 null ] }这个规格文件会被三处使用生成数据时的提示词约束、训练后的评估脚本、线上函数的前置校验。把它当成编译器的“语法定义”后续所有环节都对齐它来。3.2 第二步用“提示词编译期”生成训练数据有了规格就可以让大模型批量生成训练数据。这一步我把它叫作提示词编译期大模型在这个阶段承担全部的“理解与推理”工作但等一下编译完成它就不必再上场了。数据生成时的提示词需要包含三个部分任务描述、输出 schema、示例。我常写的模板长这样你是工单分类数据生成器。请根据以下要求生成训练数据。 任务生成客户留言及对应的分类结果。 要求 1. 留言内容要口语化风格多变长度 5~100 字。 2. 至少包含 30% 的投诉/退款类留言其中又分为订单相关和非订单相关。 3. 输出必须是合法 JSON字段为 intent、priority、order_id。 4. intent 只能取 refund、complaint、consult、other。 5. 每条数据必须带一条简短说明解释分类理由。 示例 输入我要退货订单号是 JD20250415001234都三天了还没处理 输出{intent: complaint, priority: high, order_id: JD20250415001234}生成时我会调整两个参数temperature设为 0.5max_tokens控制在 300 以内。实际跑一轮单个 500 字左右的提示词每次可以生成 10~20 条样本调用 100 次左右能拿到 1500~2000 条初始数据。注意这些数据是“老师”写的不是金标准下一步清洗必不可少。3.3 第三步数据清洗与增强大模型生成的数据你没法直接拿去训。原因很简单老师也会走神。清洗这一步我做四件事。第一格式校验。每条数据必须能通过 JSON 解析schema 必须和规格一致。这一步用脚本过滤不合格的直接丢。第二交叉验证。同一条输入让大模型用不同的温度参数生成两次结果两次不一致的样本进入人工抽检池。这个策略能筛掉一批模型自己都不太确定的样本。第三人工抽检。随机抽出 10%~20% 样本人工核对分类是否合理。抽检比例看起来高但对于 2000 条级别的数据量绝对工作量并不大一小时左右能完成收益却很大。第四反例增强。这一步经常被忽略但恰恰最重要。模型如果只在“有这个意图”的样本上学到了线上碰到“没有这个意图”的输入就会胡乱分类。我会额外构造一批边缘样本看起来像投诉但不是投诉的、提到退款但实际是咨询的、空泛的“你好在吗”这类无意义留言。把它们标注为other或对应正确类别强制模型学习边界。清洗结束后数据量会比原始生成的少一些但质量高很多。我的经验是2000 条干净数据效果比 10000 条脏数据好。3.4 第四步训练与验证训练阶段我惯用的方案是 LoRA 一个 1B 左右的基础模型。用 Hugging Face Transformers 加上 PEFT 库核心代码如下from peft import LoraConfig, get_peft_model from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments model AutoModelForCausalLM.from_pretrained(Qwen/Qwen2.5-1.5B-Instruct) tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen2.5-1.5B-Instruct) lora_config LoraConfig( r8, lora_alpha16, lora_dropout0.05, biasnone, task_typeCAUSAL_LM, ) model get_peft_model(model, lora_config) training_args TrainingArguments( output_dir./compiled_intent_model, per_device_train_batch_size8, gradient_accumulation_steps4, learning_rate2e-4, num_train_epochs3, logging_steps50, evaluation_strategyepoch, save_strategyepoch, fp16True, )几个参数我解释一下。r8是一个稳妥的起点字段分类任务不需要很大的秩learning_rate2e-4是 LoRA 的常见区间太高容易训飞太低收敛太慢num_train_epochs3对这类小数据蒸馏任务基本够用跑更多轮次会开始过拟合。训练时直接把数据组织成 prompt - response 的文本格式也就是告诉模型看到“输入 XXX”就输出对应的 JSON。训练完成后保存 LoRA adapter这样一个“编译产物”就出来了。验证阶段不能只看总准确率要按类别分别看 precision 和 recall。真实踩过的坑是总准确率 95%但投诉类 recall 只有 60%——说明模型把大量投诉漏判了这在漏检场景里是致命的。所以评估脚本里我会额外打印按类别分的混淆矩阵按业务优先级给不同类别设定最低指标线。3.5 第五步函数化封装与部署训练只是生成了“机器码”最后一步是把它封装成函数让业务方可以像调普通函数一样调用它。这一步决定了“编译”的体验好不好。我的封装接口很简单def classify_ticket(text: str) - dict: 把客户留言编译为结构化工单分类结果。 if len(text) 2000: raise ValueError(input too long) result, confidence run_lora_inference(text) if confidence CONFIDENCE_THRESHOLD: result call_llm_fallback(text) return validate_schema(result)这个函数内部做了三件事先检查输入长度然后跑小模型推理置信度低就走大模型兜底最后用规格里的 JSON Schema 校验输出格式。业务方看到的只是一个输入字符串、一个输出字典完全不用关心背后跑的是什么模型。部署时我有两个线上实践想重点强调。第一模型预热与超时控制。小模型推理也要加载权重首次调用会有延迟必须在服务启动时预留预热接口同时给推理进程设置严格的超时我一般设 500ms防止极端情况拖垮接口。第二输出结构校验必须放线上。哪怕小模型训练得再好也可能输出不合法 JSON线上多一道 schema 校验比事后发现问题强得多。4. 落地过程中的常见问题与排查技巧4.1 数据污染老师教错了学生学歪了最常见的问题出在数据生成环节。有一次我检查训练集发现“退款”类数据里混了大量“我还没收到货”这种实际上是物流咨询的样本——老师说这是退款学生就学会了把物流咨询也分类成退款。结果线上误判率飙高。排查思路不要只看训练指标要看错误样本长什么样。我后来在数据生成阶段强制要求老师输出“分类理由”每一条样本都带一句话解释为什么这么分。清洗时抽检这些理由如果发现老师的判断明显牵强直接过滤。这一招很简单但能挡掉至少一半的脏数据。另一个补充手段是多个大模型交叉生成用两个不同的模型各生成一批数据只在两者结果一致时保留样本。数据量会少一些但干净程度明显提升。4.2 覆盖率不够与边界异常小模型训练完之后线上会遇到训练分布之外的情况。比如提示词里写的输入是中文留言结果线上来了条中英混合的写的是客服场景结果用户贴了一串乱码。这类输入在小模型眼里全是“没见过的新情况”分类结果自然不可信。解决思路有两层。第一层是输入侧预检在函数入口加规则判断遇到空文本、乱码、超长文本、纯链接这类输入直接标记为other或invalid不让模型硬猜。第二层是输出侧兜底就是前面说的置信度路由低置信度的绝不强出结果。运行久了之后把这些 fallback 到大模型的样本收集起来定期回流训练数据下一轮“编译”的覆盖率就会提升。4.3 版本迭代提示词改了函数怎么办业务侧的提示词是经常变的。今天加了一个退款原因分类明天改了一下投诉优先级规则。如果每次改动都要重新走一遍生成、清洗、训练、部署团队会疯掉。我的做法是建一套数据版本管理和灰度发布机制。提示词每次变更统一记录变更原因和影响范围重新生成的数据集打上新版本号训练出的 LoRA adapter 也打上新版本号。线上部署时不直接替换旧版本而是新旧版本并存用流量灰度逐步切换。因为 LaRA 训练快、体积小这个流程可以做到当天完成完全来得及匹配业务节奏。这里要特别提醒绝不在没有评估的情况下直接替换线上版本。哪怕提示词只改了一个字都可能导致小模型的输出分布整体偏移必须先跑一轮回归测试。4.4 “编译期”太慢数据生成和训练时间优化第一次跑完整流程时我遇到过很尴尬的问题就为了把那 500 字提示词“编译”成一个函数光等数据生成就等了大半天再加上训练几乎赶不上一周一次的模型更新时间窗口。这个效率问题不解决整套方案没法在团队里推广。优化层面我试过几个有效的办法。数据生成阶段用并发调用代替串行调用同时控制单次提示词生成的条数避免一次请求生成太多导致不稳定启用结果缓存输入相同或高度相似的 prompt 模板直接复用上一次结果训练阶段先从最小模型开始跑通全流程确认指标达标后再决定要不要上更大模型。另外把数据生成和训练脚本工程化做成一条命令行就能跑完的流水线这比任何参数调优都能节省团队时间。python generate_data.py --prompt prompt_v3.md --output data_v3.jsonl python clean_data.py --input data_v3.jsonl --output cleaned_v3.jsonl python train_lora.py --data cleaned_v3.jsonl --output adapter_v3 python evaluate.py --adapter adapter_v3 --testset test_v3.jsonl这套流程跑熟之后从提示词变更到线上函数更新可以压缩到一天以内。数据量不大时训练环节甚至能用一张消费级显卡跑完成本低到几乎可以忽略。我在实际项目中最深的体会是Compile by Training 与其说是一个技术方案不如说是一种工程思维的转变。它会逼你把提示词里的每一条判断逻辑像代码一样去审视——是否可测试、是否有边界、更换后影响面是什么。做习惯之后再看那群“提示词泄露”之类的讨论你会发现把能力固化成函数和权重之后业务逻辑不再裸奔线上稳定性也不再依赖对家的模型心情。提示词时代远没有结束但把提示词当成运行时解释执行的“脚本”去用和把它当成“源码”去编译成函数是两种完全不同的工程境界。我的建议是先从你手里最疼的那一个结构化分类任务开始试跑通一次全流程你会回来感谢自己。