
如果你正在用大模型做 Agent、RAG 或者任何偏“生产级”的 LLM 应用最近一定被同一个问题折磨过token 不够用。不是模型能力不行而是每一轮对话、每一段工具调用结果、每一次系统提示词注入都在消耗上下文窗口。换更贵的模型能解决质量却解决不了成本做文本截断能保住窗口却往往把关键信息一起切掉。很多团队在模型选型和 RAG 方案上花了大量精力最后发现真正卡住业务规模的其实就是 token 预算。但这里面藏着一个被低估的事实大量 token 并不是“必须花”的而是模型在输出时产生了大量叙事冗余——它用好几句话表达本来一句话就能说清楚的信息。模型不是不知道该简洁而是没有人给它足够的“叙事约束”。这篇文章想讲清楚一个思路把 LLM 的输出看作一个语义系统通过热力学式的“约束”来降低语义熵让每个 token 都承载更多有效信息。这个思路对应到一个很形象的概念——Semantic Thermodynamics中文可以叫“语义热力学”。更重要的是不仅讲概念还会给出一个可以在本地跑通的完整实验让你亲自测量在同样的任务里使用叙事约束和不使用叙事约束输出 token 到底差多少。这也是“79% token reduction”这类数字最靠谱的理解方式它不是所有场景的普适结论而是在高冗余文本生成任务里约束带来的真实收益。1. 这篇文章真正要解决的问题先说痛点。假设你维护一个内部的 RAG 问答系统。用户上传一份 2000 token 的产品文档系统召回 3 个片段每个 500 token再加上 system prompt 和用户问题输入侧大概 4000 token。模型生成回答如果没有任何输出约束一次返回 800 token 是常有的事。这看起来很平常但如果你的业务是每天几千次调用这个成本就非常可观。更麻烦的是 Agent 场景。Agent 需要多轮工具调用每一轮都要把之前的对话历史、工具返回结果、中间思考塞进上下文。一个 5 轮调用的任务输入输出累计可能轻松突破一两万 token。而在这其中有很大一部分是“叙述性”的模型在复述已知信息、在重复套话、在生成格式松散的过渡句。传统优化手段有几种但都有代价换更小/更便宜的模型质量可能下降。手动截断历史可能丢失关键上下文。用摘要代替原文摘要本身产生的 token 也很高而且损失细节。这些方法都默认 token 消耗是“输入侧”的问题只要把输入压小就行。但实际上输出侧的叙事冗余同样重要而且往往被忽略。所谓叙事约束不是简单地说“请简短回答”而是从输出结构下手告诉模型必须按什么模板、什么粒度、什么顺序来生成。约束越明确模型的输出就越接近“最小语义集”废话自然减少。这篇文章适合谁正在做 LLM 应用开发想压 token 成本的技术人员。维护 RAG 或 Agent 系统被上下文窗口撑爆困扰的工程师。对“结构化输出”“输出约束”有兴趣但没时间系统整理方法的开发者。读完之后你能得到一个可以直接运行的示例以及一套判断“什么时候该用约束”的实践指南。2. Semantic Thermodynamics 的核心概念把 LLM 输出看成语义系统第一次看到“Semantic Thermodynamics”这个词大概率会觉得它很玄学。它听起来像是物理学的分支实际上它并不是一个严格的热力学理论也不是某个官方标准而是一种思考模型输出与 token 消耗之间关系的方式。通行的认识是LLM 的每个输出 token本质都是在“内容空间”中做一次选择。如果这个选择的随机性很高模型就会输出大量低价值、可预测的过渡内容如果选择被强烈约束模型就必须把语义集中到有限几个 token 上。可以用热力学里的概念来类比熵在信息论里熵表示不确定性。LLM 生成的文本中如果候选词分布很分散、上下文信息弱输出就更“混乱”表现为结构松散、重复表达。这类输出可以看作“高语义熵”。自由能热力学中系统能对外做功的部分叫自由能。对应到 LLM 输出真正对任务有价值的语义信息就是“有效语义”。大量 token 消耗在维持“句式完整”“语气自然”“表达柔和”上这些不是有效语义。约束/边界条件把一个气体系统关进容器它就不能无限膨胀。叙事约束也是一种“边界条件”把模型的表达范围限定在一个紧凑的结构里。所以Semantic Thermodynamics 的通俗解释是在模型输出能力不变的情况下通过增加叙事约束降低输出文本的语义熵让每个 token 携带更多有效语义。为什么叫“叙事”约束因为约束的对象不是单个词汇而是模型生成内容的组织方式。比如输出必须分为 3 个章节。每章只能用列表。每项不超过 20 字。不要输出总结段落。不要重复问题中的背景信息。这些约束都作用于“叙述结构”所以叫 narrative constraints。理解了这一点你就抓住了标题里“79% token reduction”背后的逻辑当任务本身冗余度高约束带来的收益就极大。反过来如果任务本身已经很紧凑比如直接生成 JSON 数据约束能带来的空间就有限。3. LLM Token 成本构成为什么“叙事冗余”会影响预算要真正理解约束的价值得先把 token 成本拆开看。3.1 Token 成本的三部分一次完整的 LLM 调用token 消耗由三部分组成组成部分含义典型来源输入 token系统提示、用户输入、检索结果、对话历史Prompt 设计与 RAG 召回输出 token模型生成的内容回答、总结、工具调用参数缓存/日志 token重复调用时的历史记录、日志分析多轮 Agent、长期会话很多优化方案只盯着第一项比如把 system prompt 写得更短、减少检索片段。但输出 token 同样重要尤其是在生成型任务中。3.2 输出侧冗余的真实场景举一个非常常见的例子。你让模型把开发记录整理成周报模型可能会这样输出“本周我们团队主要围绕订单服务展开了一系列优化工作。首先完成了日志采集系统的搭建并成功接入了 order-service 和 payment-service 两个核心服务。其次……”这段文本信息密度很低。真正有效信息只有“搭建日志采集接入两个服务”其余都是叙事连接。如果任务规模再大一点比如整理本周 20 条开发记录模型很容易生成 800 到 1000 token 的周报。而如果加上约束要求“只输出三章列表每项不超过 25 字”同一份开发记录模型可能只需要 200 token 就能表达同样的信息。这就是叙事冗余在输出侧造成的浪费。它不像输入侧那样直观但累计起来非常惊人。3.3 传统压缩方案 vs 叙事约束有人会说那直接在 Prompt 里加一句“请简短回答”不就行了区别很大。方法原理问题直接要求“简短”模型自行判断压缩程度效果不稳定容易丢失关键信息或仍然冗长截断输出切断长文本破坏语义完整性摘要/压缩中间结果用另一轮 LLM 调用压缩内容增加额外 token 消耗且摘要也可能冗余叙事约束固定输出结构限制粒度需要设计模板但效果可预测、可复用叙事约束的优势在于“可预期”。你规定模型输出 3 章、每章 3 条列表它就会接近这个结构。你可以提前估算输出 token 上限这对成本控制是非常有价值的。4. 叙事约束的四种落地方式叙事约束不是一个单一技巧而是一类方法的合集。在实际工程中常见的有四种落地方式4.1 输出结构约束这是最直接的一种。在 system prompt 里明确告诉模型输出必须包含哪些章节、章节的顺序是什么、每个章节用什么样的排版。示例请将下面的会议纪要整理为项目周报并严格遵守以下约束 1. 只输出三个章节# 本周进展、# 问题与风险、# 下周计划。 2. 每个章节使用无序列表不允许出现段落式描述。 3. 不要输出任何总结、建议或客套话。结构约束的本质是“定义模板”。模型在模板内填充内容时不会浪费 token 在过渡句上。4.2 输出格式约束把输出格式限定为 JSON、CSV、YAML 等机器可读格式。这不仅能压缩 token还能让下游程序直接解析减少额外 JSON 解析和清洗成本。示例请输出 JSON 格式字段为 { summary: 不超过50字的一句话总结, items: [不超过20字的关键进展], risk: 无则填null }需要注意JSON 的字段名本身也会消耗 token所以字段名尽量短。但可读性也不能太差团队内部需要约定一套通用的短字段命名。4.3 思维链压缩推理任务中思维链Chain of Thought能提升效果但也会带来大量中间 token。叙事约束在这里的作用是保留关键推理步骤删除重复推理过程。可以这样约束请分步骤给出推理过程但每个步骤不超过一行并且只保留与结论直接相关的推理。这属于“轻量约束”保留了思维链的能力但限制了它的膨胀。4.4 多轮上下文压缩这个更适合 Agent 场景。当对话历史超过一定长度时用一次带叙事约束的 LLM 调用把历史压缩成“关键事实列表”而不是用原始文本继续拼接。示例压缩指令请将以下多轮对话压缩为事实清单 1. 每行一个事实不超过20字。 2. 只保留对后续任务有影响的决定、结论和未解决问题。 3. 不要输出对话过程的描述。这样在后续调用中输入历史从几千 token 降到几百 token。压缩本身虽然消耗一次调用但总体上通常是节省的而且上下文精度更高。5. 完整示例用叙事约束降低 LLM Token 消耗前面讲了概念和方法这一节用一个可以实际运行的脚本验证叙事约束对 token 的影响。5.1 环境准备建议使用 Python 3.9。需要安装两个依赖pip install openai tiktoken如果你还没有设置 API Key先设置环境变量export OPENAI_API_KEY你的_API_KeyWindows 下使用set OPENAI_API_KEY你的_API_Key文中下面的示例使用 OpenAI 接口风格模型选择上建议使用你实际可用的模型比如gpt-4o-mini。脚本里通过变量统一配置方便替换。5.2 完整实验脚本场景把一段开发记录整理成技术周报。对比“无约束”和“叙事约束”两种模式下输出 token 的差异。# 文件路径narrative_constraint_demo.py import os import textwrap import tiktoken from openai import OpenAI client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) encoder tiktoken.get_encoding(cl100k_base) # 未约束版本 FREESTYLE_SYSTEM ( 你是技术团队负责人请根据开发记录整理一份技术周报。 要求表达自然、内容完整、逻辑通顺。 ) # 叙事约束版本 CONSTRAINED_SYSTEM textwrap.dedent(\ 请把开发记录整理成技术周报并严格遵守以下叙事约束 1. 只输出三个章节# 本周进展、# 问题与风险、# 下周计划 2. 每个章节使用无序列表每项不超过 25 字 3. 不输出问候语、总结段落、补充解释 4. 总列表项不超过 8 个。 ) DEV_RECORDS textwrap.dedent(\ 周一搭建日志采集接入 order-service 与 payment-service。 周二完成订单超时取消任务覆盖测试通过。 周三修复支付回调偶发超时根因是数据库连接池不足。 周四订单服务 QPS 压测由 800 提升到 1200。 周五数据看板联调完成导出按钮样式待调。 ) def call_and_count(system_prompt: str, user_content: str, model: str gpt-4o-mini): 调用模型并返回生成文本和 token 使用量 resp client.chat.completions.create( modelmodel, messages[ {role: system, content: system_prompt}, {role: user, content: user_content}, ], temperature0.3, ) output_text resp.choices[0].message.content return output_text, resp.usage def local_prompt_token_count(): 本地统计两类 prompt 的输入 token方便不调用 API 也能对比 free_prompt FREESTYLE_SYSTEM \n DEV_RECORDS con_prompt CONSTRAINED_SYSTEM \n DEV_RECORDS return len(encoder.encode(free_prompt)), len(encoder.encode(con_prompt)) if __name__ __main__: free_prompt_tk, con_prompt_tk local_prompt_token_count() print(f未约束 prompt token: {free_prompt_tk}) print(f约束 prompt token: {con_prompt_tk}) print(\n 未约束版本 ) free_text, free_usage call_and_count(FREESTYLE_SYSTEM, DEV_RECORDS) print(free_text) print(f\n[token] prompt{free_usage.prompt_tokens}, fcompletion{free_usage.completion_tokens}, ftotal{free_usage.total_tokens}) print(\n 叙事约束版本 ) con_text, con_usage call_and_count(CONSTRAINED_SYSTEM, DEV_RECORDS) print(con_text) print(f\n[token] prompt{con_usage.prompt_tokens}, fcompletion{con_usage.completion_tokens}, ftotal{con_usage.total_tokens}) if con_usage.completion_tokens and free_usage.completion_tokens: reduction (1 - con_usage.completion_tokens / free_usage.completion_tokens) * 100 print(f\n输出 token 下降比例: {reduction:.1f}%)5.3 脚本关键逻辑说明脚本分为三部分本地计算 prompt 的 token 数。通过 tiktoken 估算输入侧的差异。在这个例子里约束版 system prompt 会更长一些这是因为它包含了详细规则但这部分成本是一次性且可复用的。使用同一个开发记录分别调用两次模型。一次“自由发挥”一次“叙事约束”。输出文本本身打印出来可以直观感受两者差异。通过 API 返回的usage字段精确获得输出 token 数并计算下降比例。这里有一个容易被忽略的细节如果你在系统提示词中写了一套叙事约束它会被复用到后续所有调用中所以 prompt 本身多消耗的 token 是值得的。只要约束能让每次输出节省 200 token调用 10 次就节省了 2000 token远大于 prompt 增加的部分。5.4 运行与验证运行命令python narrative_constraint_demo.py如果一切正常你会看到两类输出文本和 token 统计。判断实验是否成功的标准两类版本都生成了周报内容。约束版的输出结构严格符合 3 章列表要求。约束版输出 token 明显少于未约束版。约束版的正文仍然覆盖了开发记录中的关键事实日志接入、超时取消、支付回调修复、QPS 提升、数据看板联调。这最后一点最重要。Token 减少的代价不能是信息损失。约束后的输出应该更精炼而不是更残缺。6. 运行结果与效果验证以示例中的开发记录和模型表现来看典型结果大致如下模式输出 token 范围优点风险未约束700 - 1100表达自然阅读体验好存在大量过渡句和重复描述叙事约束150 - 250token 少结构可预期便于程序解析需要人工确认信息覆盖度如果你运行后的下降比例恰好是 70% 左右说明任务冗余度较高如果只有 30%说明这个任务本身已经足够紧凑比如输入本身是高度结构化数据。79% 这个数字之所以会被用来做标题通常对应的是最典型的“周报/纪要/文档摘要”类任务。不要把它当作所有场景都能复现的结果。如何更严谨地验证约束是否有效建议做一个 20 条样本的小测试准备 20 个不同的输入文本。每条分别用自由模式和约束模式生成。记录输出 token。人工检查每条输出是否覆盖关键信息点。如果平均下降比例在 40% 以上且 90% 的样本没有丢失关键信息说明这套约束模板适合你的业务可以放到生产环境里复用。如果某些样本丢失信息就要调整约束规则的粒度比如把“每项不超过 25 字”放宽到“不超过 40 字”。7. 常见问题与排查方法在实际工程中叙事约束并不是加上就有效果。下面几个问题非常典型问题现象可能原因排查方式解决方案模型输出不符合指定结构约束规则模糊或相互冲突检查 system prompt是否出现“要简洁又要完整”这类矛盾表述将规则拆成可执行的序号条款并配一个输出示例信息丢失关键点没写出来约束过强列表项数量限制过严对比约束前后输出的信息点覆盖数量适当放宽字数上限或允许“超出部分单独用小字补充”模型仍然输出大段解释约束只写了“不要输出解释”但没有给出替代结构检查是否定义了“如果必须解释应放在哪个字段”增加一个comment字段把必须的解释集中放在末尾本地 token 统计与 API 计费不一致tiktoken encoding 选择与模型不匹配使用模型对应的 encoding以 API 返回 usage 为准计费与优化决策以 API 返回数据为准本地统计只做估算用了约束后质量下降temperature 设置过高输出随机性增加在约束场景下调低 temperature 到 0.2 - 0.4生产环境建议 temperature 固定不用默认值结构化输出偶尔不是合法 JSON模型没有稳定遵循 JSON 格式开启 response_formatjson_object 等结构化输出能力同时做 JSON 解析失败重试不要把格式正确性完全交给模型7.1 一个容易踩坑的案例有一种错误做法是把叙事约束写在用户消息里而不是系统提示词里。当约束出现在用户消息中时模型可能把约束和业务输入混在一起理解优先级没有 system prompt 高。生产环境建议把稳定的叙事约束模板放在 system prompt 中把每次变化的文本放在 user message 中。这样既保证约束稳定生效又方便统一更新模板。8. 最佳实践与工程建议叙事约束是一把双刃剑。约束太强模型像在“填表格”句子生硬约束太弱token 节省不明显。以下是长期实践下来比较有效的经验。8.1 设计约束的五个原则原则一结构优先于措辞。先规定章节和列表再规定每项字数不要颠倒。原则二给正例比给反例更有效。与其说“不要写总结”不如直接说“最后一个章节必须是下周计划”。原则三控制规则数量。超过 5 条时模型容易顾此失彼建议合并同类项。原则四每条规则都要可验证。比如“每项不超过 25 字”是可客观判断的“语言要流畅”不是。原则五预留一个自由字段。完全不放行会导致信息损失一个comment字段能避免模型把没说清楚的话强行塞进别处。8.2 叙事约束与结构化输出的配合如果你的项目已经使用了 structured output 或 function calling叙事约束依然有位置。它不是替代结构化输出而是专门针对“非结构化生成内容”的压缩手段。当模型必须返回一段可读文本时结构化输出无法约束文本内部的表达方式叙事约束会补上这个空缺。所以两者是互补关系不是竞争关系。8.3 生产环境部署建议在生产环境上token 优化不能只靠“改了 prompt 就上线”。建议做三件事把约束模板作为配置项而不是硬编码在代码里。这样调整字数限制或者章节名时不用改代码重新发布。对每次调用记录prompt_tokens和completion_tokens形成消耗曲线。当改版后 token 不降反升可以及时回溯。定期抽查 5% 的约束输出确认信息覆盖度没有悄悄下降。模型版本升级后行为可能变化同一套约束模板在新模型上不一定同样好用。8.4 安全与合规提醒在 prompt 中加入约束时不要将业务敏感信息直接写在 system prompt 里。尤其是约束模板是要被复用的它可能被记录在日志中也要被团队成员评审。敏感字段应该通过变量注入到 user message 或专门的上下文字段中。另外如果你的约束模板变成了团队内部的“标准做法”记得维护一份变更记录。你新增一个字段可能影响下游解析逻辑需要同步通知调用方。9. 总结与后续学习方向叙事约束的核心价值不是让你把所有 LLM 输出都压成“电报体”而是给你一种可预期、可量化的 token 控制手段。Semantic Thermodynamics 这个方向表面上是借用了热力学的概念本质上是在提醒你一件事面对 LLM约束不是创新的枷锁它是信息密度的杠杆。一句“请简短回答”是模糊的意图而一套“三章结构 列表 字数上限”的叙事约束才是真正能落到工程里的方案。建议你接下来做三件事用本文的脚本把你业务中最高频的一个生成场景测一遍拿到真实 token 下降数据。根据业务特点设计一套专属约束模板先在小流量下验证信息覆盖度。把约束模板配置化并接上 token 消耗监控观察长期收益。如果你的任务本身就是高度结构化的比如生成 JSON 或者调用工具参数叙事约束的收益会小一些。但如果你在做周报生成、会议摘要、Agent 多轮压缩、文档整理这类“高冗余文本”任务这套方法很可能是目前成本收益比最高的优化方式。