行业资讯
📅 2026/8/28 4:38:45
别让LLM写“主题行”:构建可控AI应用的边界设计
我最近在调试一个自动化邮件通知流程时把主题行交给了 LLM 生成。它很快产出了这样一句话“重要信息关于我们合作的几点建议。”看起来通顺语法也完整但读起来像是一封没人愿意打开的群发邮件。我接着让它再生成几个备选它给出一堆“您好您的方案已更新”“关于合作事项的说明”之类的表达每一句都很正确每一句也都毫无辨识度。那一刻我突然意识到问题不在模型能力而在任务分配。我把本该由人定义的“主题行”让渡给了模型。主题行是什么它不只是邮件标题在任何 LLM 应用里它代表的是任务的方向、意图和边界。如果一个流程允许模型自由定义这个部分那么后续所有生成内容都会跟着漂移。后来我在自己的开发笔记里记下一句话Never let the LLM write the subject line。这句话不是让模型别写标题而是提醒自己——方向必须由人定执行可以交给模型。1. 这一句话其实是 LLM 应用里最容易被忽略的分工原则很多人把这句 “Never let the LLM write the subject line” 理解成一句偏激的吐槽AI 写的标题不行所以别用它写。但放在 LLM 应用开发里它有更实际的含义不要让模型决定任务的目标不要让模型定义任务的范围更不要让模型替你做价值判断。为什么这条原则容易被忽略因为现在的 LLM 太擅长“接话”了。你给它一个模糊指令它能帮你自动补全细节。比如你说“帮我写一封挽回客户的邮件”它会自己判断客户是谁、挽回的理由是什么、语气应该怎样。它甚至会自动构造一个看起来很合理的主题行“真诚道歉期待继续合作”。看起来没问题但它替你做了所有关键假设。如果假设错了整封邮件的基础就是错的。在开发流程里这个问题的破坏力更大。LLM 生成了主题行后面的正文、操作、工具调用都会围绕这个主题行展开。如果主题行本身跑偏整个链路都会跑偏而且你还很难定位问题到底出在哪一层。因为模型给出的理由总是“看起来合理”的。所以我会把“主题行”理解成一个流程的锚点。它可以是系统提示词里的任务定义一封邮件的标题行一条用户指令的核心意图一个 Agent 执行任务时的边界描述一个 RAG 查询的原始问题。这些内容的共同点是它们定义了“做什么”和“为什么做”。而模型擅长的是“怎么做”。一旦把“做什么”和“为什么做”也交给模型你实际上失去了对任务的控制权。这不是一个可以靠模型能力提升来解决的问题。即使以后模型更强它依然无法替你判断“这个场景到底需要什么语气”“这件事在你们业务里的优先级是什么”“哪一层信息可以暴露给用户”。这些判断依赖上下文和长期目标而模型在生成时通常只有有限的上下文窗口没有长期记忆也没有和业务绑定的价值体系。因此在设计任何 LLM 应用时第一步不是选模型、写 Prompt而是先划清边界哪些部分永远由人来定义哪些部分可以让模型生成。我建议把“主题行”这一层永远留在人类或代码控制下。2. 为什么模型总把主题行写“糊”了先看一个常见现象让 LLM 生成标题它倾向于生成“通顺但不出错”的表达。这背后是语言模型的本质——它根据训练数据中的概率分布逐个词预测最可能出现的下一个词。训练数据里的主流表达是什么是那些大量存在、广为使用的普通句子。于是模型会倾向于输出安全、平均、高频的组合。但主题行需要的是另一回事。优秀的主题行要能传递具体信息要诱发注意要有针对性甚至要制造微小悬念。它追求的是判断力和差异化。这就和模型输出最安全文本的倾向发生了冲突。举一个例子。同一封促销邮件让模型写主题行它可能写“我们为你准备了一个特别优惠”这个标题没有错但它没有信息量。如果换成人工写会写“最后 48 小时你的购物车里有 3 件商品即将失效。”后者的关键差别在于它知道目标用户、场景、利益点并且把稀缺感放进了主题行。模型不是不会写这样的句子而是在缺少目标定义时它不会自动选择这个角度。因为“哪个信息最重要”是一个决策不是一个纯语言生成问题。类似的现象还出现在其他领域。让模型生成一篇文章的导语它可能给你一段信息完整但毫无节奏感的开场。让模型生成代码提交信息它经常写“Fix bugs”或“Update code”因为你没有告诉它这次变更的真实价值是什么。把这类问题的根源归纳起来有三层模型不是读者它不知道谁会看到这句话。模型没有立场它不知道你想达到什么目的。模型倾向于平滑而主题行需要锐利和取舍。这也是为什么单纯堆 Prompt 技巧很难彻底解决。你可以让模型写“吸引人的标题”但什么是吸引人模型理解的是训练数据中高频出现的“吸引人套路”比如惊讶式、悬念式、数字式。它不知道你的受众是否吃这一套。你真正要做的是把目标、读者、边界、禁用表达都定义清楚然后再让模型在一个很小的候选空间里发挥。下面是一个我常用的判断方式写主题行的人它会关注什么结果倾向LLM 自由发挥词语搭配、句法流畅、通用吸引力平均、安全、可能泛化人工定义约束后让 LLM 生成目标信息、语气、长度、禁用词更聚焦、更符合当前场景人工直接写过往经验、读者反馈、业务目标最有针对性但成本高所以更好的方案不是“让人替代模型”而是“让人定义约束让模型生成候选让规则和人来决策”。这样既能利用模型的生成速度又能控制方向。3. 在 Agent 和 RAG 里“主题行”失控的三种典型表现“主题行”失控不只是邮件标题写得差在复杂 LLM 应用里它会表现为更隐蔽的问题。这里以 Agent 和 RAG 两种典型场景为例。3.1 Agent 过度自治模型自己决定操作范围Agent 是很多人关心的方向。它让模型可以调用工具、访问数据、执行操作。但越自由失控风险越大。一个常见问题就是 excessive agency也就是模型获得了超出任务需要的操作权限。比如一个客服 Agent它的任务本来是查询订单状态。但如果提示词和工具权限没有约束好模型可以调用退款接口、修改用户备注、甚至批量导出数据。模型在生成“下一步操作”的时候它并不知道哪个操作会造成不可逆影响。它只是基于对话历史选择了一个看起来合理的动作。本质上这就是主题行失控的工程版模型自己决定任务的走向。解决办法是给 Agent 设定明确的状态机和动作边界。模型只能在当前状态下选择允许的动作而不能自由定义下一步。你可以用 LangGraph、Spring AI 这类编排框架去实现也可以自己写简单的状态判断逻辑。关键不是用什么框架而是不让模型跨越边界。3.2 RAG 中用户意图被“优化”跑偏RAG 应用里经常做 query 改写。为了让检索效果更好很多流程会先让 LLM 把用户问题改写成一个更利于检索的查询。这个思路本身没问题但风险在于模型可能把原始意图改偏。比如用户问“上个月上海地区的退款订单有多少”模型可能改写成“如何统计退款订单数量”从语义上好像相关但原来的约束条件“上个月”“上海地区”都被丢掉了。检索出的结果自然不完整。这个例子里原始用户问题就是“主题行”。如果让模型随便改写等于让模型重新定义任务。正确做法是尽量保留原始问题的结构和约束LLM 只做同义扩展或补充关键词而不是重新创造问题。3.3 输出格式不稳定模型自由发挥导致下游无法解析还有一个更具体的问题当模型生成 JSON 或代码时如果允许它自由定义字段名下游程序可能无法解析。比如要求返回title模型偶尔返回heading或subject要求返回布尔值模型可能为了“表达更准确”返回字符串。这类问题看起来是格式错误实际上也是主题行失控。模型在生成时没有严格遵循固定 schema。稳定做法是用结构化生成或强校验。模型负责填充值字段名、类型、枚举值都应由代码锁定。这也是一个“模型提供内容人控制框架”的典型案例。这三种表现都指向同一个结论模型在框架内工作时最有价值一旦让它自由定义框架系统的稳定性就会下降。你把主题行攥在自己手里表面上限制了模型实际上是在保护整个流程的下限。4. 把主题行攥在手里一个可复用的 LLM 工作流设计顺序既然要控制主题行具体该怎么落地我总结了一套适合大多数 LLM 应用的工作流设计顺序。这套顺序不依赖特定框架适合从单次调用到复杂 Agent 的各种场景。第一步写主题行声明。先不要写 Prompt先把任务的目标描述清楚。可以这样写用户是谁当前场景是什么我们希望模型帮用户完成什么绝对不能做什么成功标准是什么这个声明不一定要给模型看但它会指导你后面怎么写 Prompt、怎么设边界。很多开发者在写 Prompt 时喜欢堆细节但恰恰忘了先想清楚“这个任务的边界”。第二步定输出协议。明确模型的输出格式。如果只是文本定义长度、语气、是否允许 markdown。如果是结构化输出定义 JSON schema、字段类型、枚举值。一定要在代码里校验这些格式不能只靠 Prompt 提示。第三步让模型只在你划定的范围内生成内容。把主题行声明和输出协议写进系统提示词并把可选范围压缩到最小。比如主题行生成场景不要只说“生成标题”而是给模型几个固定字段核心信息目标读者语气长度上限禁用词列表模型负责把这些信息组合成候选标题但“哪个信息最重要”这个判断由你预先完成。第四步加一层代码或规则闸门。模型生成后不要直接使用先用代码校验。比如长度、关键词、敏感词、格式。如果不满足就重试或丢弃。这一步能把偶发错误挡在外面。第五步记录日志追踪每个节点。尤其是大型应用每次模型生成之前和之后都要把输入输出记录下来。这样才能在结果跑偏时定位是主题行定义错了还是模型生成问题还是下游处理问题。下面是一个最小化的邮件标题生成示例结构帮助你理解这套流程from pydantic import BaseModel from typing import Literal class EmailSubjectLine(BaseModel): subject: str tone: Literal[urgent, friendly, professional] key_info: str def generate_subject_line(): # 1. 主题行声明由人工定义不作为模型自由生成字段 task_context { recipient: 最近一个月内有加购但未下单的用户, goal: 提醒用户购物车中还有商品, scenario: 电商促销收尾, must_include: [购物车, 数量, 时间限制], forbidden: [震惊, 必看, 免费], } # 2. 输出协议模型只允许返回符合 schema 的结构 prompt f 根据以下信息生成邮件标题候选。 目标读者{task_context[recipient]} 场景{task_context[scenario]} 必须包含的信息{task_context[must_include]} 禁止出现的词{task_context[forbidden]} 只返回 JSON结构为{{subject: ..., tone: ..., key_info: ...}} # 3. 调用模型获取响应 response call_llm(prompt) # 4. 代码校验字段名、长度、禁用词 subject_data EmailSubjectLine.parse_raw(response) assert len(subject_data.subject) 30 assert 震惊 not in subject_data.subject return subject_data这里的关键是主题行里的“约束”由人工定义模型只负责把这些约束组合成自然语言。即使生成结果不够惊艳它也至少不会跑偏。5. 不要只在 Prompt 里写“请生成一个好标题”有一个想法很诱人既然 LLM 能理解自然语言我只要在 Prompt 里说清楚“请生成一个吸引人的标题”就够了。短期看确实有效尤其是你只需要零星几个标题时。但长期看这种方法不稳定因为“好”“吸引人”“有力”这类词对模型来说是模糊的。你可以通过补充例子来改善但更可靠的是把判断标准代码化。举个例子你要求标题不能超过 30 个字符就在代码里检查长度。你要求必须包含关键词“购物车”就在代码里检查关键词。你要求不能出现“绝对”“第一”等词就在代码里检查禁用词列表。这些校验逻辑比让模型“理解”你的要求要稳定得多。模型可以生成候选但决策权必须放在人工或规则层。一个人可以快速从 10 个候选中挑出最合适的规则可以过滤掉明显不合格的。这个逻辑可以扩展到更复杂的环节。比如在 Agent 中模型可以提出候选动作但最终执行哪些动作必须由状态机决定。在 RAG 中模型可以给出同义改写但核心约束必须由原始问题保留。所以“不要只在 Prompt 里写”这句话的本质是Prompt 控制的是模型的生成倾向代码控制的是系统的保证。两者结合前面的系统才可靠。也许你会问那我还是希望模型写得好一点怎么平衡我的建议是把“主题行”做成一等公民而不是让它在 Prompt 里自由飘着。你可以专门设计一个候选生成模块让模型生成 5 个标题然后用一个评分规则筛选比如是否包含核心词、长度是否合适、语气是否匹配最后让用户或业务人员确认。这样既发挥模型的多样性又避免了让模型承担最终决策。6. 从“主题行”到工程化LLM 应用的真正瓶颈不是模型能力而是控制能力这几年我观察到很多团队评估 LLM 应用时最先问的是“效果好不好”。但没有把“效果”拆成两层一层是生成质量一层是系统稳定性。生成质量由模型能力决定系统稳定性由控制能力决定。一个很常见的项目推进过程是模型在 Demo 里表现惊艳一到生产环境就出问题。今天返回格式不对明天上下文超长后天模型自己改了任务目标。这些问题大多数不是模型变笨了而是工程侧没有把“主题行”控制住。我比较看重的几个控制点上下文控制固定 system prompt限定知识检索范围避免无关内容混入。状态控制用状态机或编排框架让模型只能在预设状态间跳转。输出控制用结构化生成、schema 校验、回归测试保证输出可解析。权限控制Agent 工具调用最小化需要身份验证和高风险操作必须人工二次确认。人工审核环对外发布的文案、涉及资金的操作必须有人工审核节点。很多人会纠结模型推理精度比如 fp16、bf16、fp32 的区别。这当然值得关注但不要本末倒置。精度主要影响内存、速度和极少见的数值稳定性它不会帮你解决“主题行”失控的问题。换句话说你可以在技术选型时优化精度但流程可靠性才是决定应用能不能长期跑起来的核心。还有一个值得关注的方向是 Karpathy 提到的 LLM wiki 概念。简单说就是把知识库整理成更结构化、更可引用的形式让模型在回应时有更明确的主题和来源。这个思路本质上也是把“主题行”固定下来知识条目的边界、引用方式、改写范围都由人设计模型在框架内回答问题。这个方向对长期维护 LLM 应用很有参考价值。把“主题行”工程化之后LLM 就不再是一个偶尔聪明的黑盒而是一个可预期、可干预、可审计的组件。它也许不会每次都给出惊艳的答案但它会持续给你一个可靠的结果。对于大多数业务系统来说稳定可靠比偶尔惊喜重要得多。7. 适用边界什么时候可以让 LLM 自由发挥前面强调控制但我不想把它变成“必须锁死一切”的教条。至少有三类场景我建议让 LLM 自由一点。第一类内部创意头脑风暴。比如你想给新产品起名想让模型给你 20 个方向这时越开放越好。甚至可以说“给我一些夸张离谱的想法”。模型的安全表达反而能打开你的思路。你拿到这些候选后再人工提炼和筛选。第二类内容预处理。比如把一段长文本改写成口语化表达、提取关键词、生成多个摘要。这些任务的方向已经由输入文本限定了不会跑太远。可以允许模型在表达层面自由发挥因为主题行等价于原始文本。第三类低风险、可回滚的场景。比如生成社交媒体文案草稿生成代码注释生成测试数据。即使错了代价很低。这种场景可以不给太多约束及时观察反馈即可。那什么时候必须牢牢抓住主题行呢我的判断标准很简单如果这个输出要对外发布、要影响用户决策、要操作不可逆行为就一定要把主题行留在自己手里。比如邮件主题行用户每天看到的第一屏信息就是它主题行决定邮件打开率。如果生成错了影响是全局的。再比如 Agent 中的退款操作、知识库中的权威回答、报表中的关键结论这些都不应该让模型自由定义。如果实在想兼顾自由和风险可以走一个折中流程模型生成 5 到 10 个候选。规则过滤掉不合格项。人工在候选里选一个或做轻微修改。记录选择结果用于后续优化。这个流程不是增加负担而是把决策权放在值得信任的地方。说到底“Never let the LLM write the subject line”并不是一句否定模型价值的口号。它是在提醒我们一个可靠的 LLM 应用必须有一个牢固的方向层。这个方向层可以由人定义可以由规则定义也可以由代码硬编码但不能让模型在每一次生成时都重新定义一次。主题行是锚点是坐标是那个让你和模型都知道“我们到底要去哪里”的东西。把主题行攥在手里不是限制模型而是让模型在正确的方向上跑得更远。