
做 LLM 应用开发时最容易被低估的问题往往不是模型能力不够而是用户意图在对话过程中持续变化。比如一个用户一开始问“你们产品价格是多少”聊着聊着变成“帮我对比不同方案的参数”最后又变成“现场实施需要准备哪些环境”。如果系统只是把每一轮对话原样拼进 prompt模型很可能被中间话题带偏丢掉最初的核心目标。这类现象在英文技术讨论中常被概括为LLMs Get Lost in Evolving User Intent意思是当用户意图不断演变时大模型容易在复杂上下文中失去关键锚点最终给出偏离真实需求的回答。本文将围绕这一主题展开先讲清楚什么是 evolving user intent再从注意力机制、上下文管理等角度分析 LLM 在意图演变中“迷失”的技术原因接着用一个可运行的代码示例演示问题然后给出意图摘要、上下文压缩、RAG 意图约束、Agent 反思等工程化解法最后整理常见问题排查清单与最佳实践。适合正在做 ChatBot、AI Agent、RAG 问答系统、智能客服等项目的开发者阅读。1. 背景用户意图在对话中天然会“漂移”1.1 什么是 Evolving User IntentEvolving User Intent直接翻译是“不断演变的用户意图”。它描述的是用户在完成任务的过程中目标、关注点或问题范围随着对话推进发生改变的现象。这种改变不一定是用户反悔更多时候是自然的信息需求升级。典型过程是这样用户带着一个模糊诉求进入对话比如“我想了解一下你们的产品”。通过几次问答用户逐渐明确自己关心的是价格、功能还是部署方式。随着信息增加用户可能追加新的要求比如“如果要支持高并发架构上有什么建议”。最终用户可能会在新旧目标之间来回切换甚至改变最终决策标准。在单轮问答场景里这种意图漂移不明显因为模型只需要针对当前 query 作答。但在多轮对话、AI Agent 或 RAG 客服系统中每一条历史消息都可能包含了用户目标的一部分如果系统不能及时判断“用户当前到底要什么”上下文就会越来越乱。1.2 为什么说 LLM 会“迷失”LLM 本身是一个概率模型它根据上下文预测下一个 token。上下文越长、话题越多模型就越难判断哪些信息是当前最重要的。举个例子用户我想了解你们的数据采集服务。 助手好的我们的服务支持多种数据源接入。 用户采集到的数据能直接做可视化吗 助手可以内置了图表分析功能。 用户那部署到内网需要满足什么条件到这个阶段用户其实已经从“了解服务”演变成了“评估内网部署可行性”。如果系统仍然保留了很多关于数据源接入、可视化能力的细节模型的注意力就会分散可能回答了一大堆用户已经知道的内容却没有回应真正的部署条件问题。这种现象在英文论文和社区讨论里有很多对应说法比如“lost in the middle”“intent drift”“context forgetting”。它们描述的都是同一个问题模型对用户目标的实时追踪能力有限尤其当目标发生演变时。1.3 为什么这个问题值得重视对于对话系统来说理解“用户当前在做什么”是体验的基石。从产品角度看如果模型总是答非所问用户会认为系统“不太聪明”从而流失。从技术角度看如果意图识别错误后续的工具调用、检索、生成都会跟着错而且错误会在多轮中累积放大。对于 RAG 系统来说意图漂移还会直接影响检索质量因为 embedding 查询是基于当前文本生成的而当前文本可能已经不再代表用户的核心诉求。因此解决 LLM 在 evolving user intent 下迷失的问题不是锦上添花而是对话类应用从“demo 能用”走向“生产可用”必须跨过的一关。2. 从技术原理看“迷失”的根因2.1 注意力机制对长上下文的稀释Transformer 架构中的自注意力机制会给序列中的每个 token 计算重要性权重但权重是相对值不是绝对值。当上下文里混入大量无关或弱相关信息时真正重要的意图信号会被稀释。在多轮对话中用户早期表达的核心目标往往只占全部上下文的一小部分。随着历史消息增多模型需要在这些信息中搜索“用户最终想要什么”。而注意力分布是平滑的相关信息的权重并不一定能压倒性地占据主导于是模型容易从最近几轮出发把局部话题当成全局目标。这里需要特别注意的是不同模型的上下文窗口长度不同但窗口大不代表意图保持能力就强。即使模型能“看到”所有历史它仍然需要在这些历史中做有效的归纳推理。2.2 历史消息被无条件视为等价信息很多应用在构造 prompt 时只是简单地把历史对话按时间顺序拼接system: 你是一个智能助手 user: 我想了解你们的数据采集服务 assistant: 好的我们的服务支持多种数据源接入 user: 采集到的数据能直接做可视化吗 assistant: 可以内置了图表分析功能 user: 那部署到内网需要满足什么条件这种拼接方式有一个隐含假设每一轮历史消息对当前回答的重要性是一样的。但事实并非如此。用户早期提到的“数据采集服务”可能只是引子真正目标已经变成“内网部署条件”而旧信息不仅没有帮助反而会干扰模型。更麻烦的是如果中途出现了完全无关的话题比如用户突然问“你们公司在哪里”这个信息会被模型当作候选上下文进一步削弱核心意图的权重。2.3 检索结果与当前意图错位在 RAG 系统中意图漂移的影响会被放大。系统通常会把当前用户 query 向量化然后去向量库中检索相关内容。如果 query 是“那部署到内网需要满足什么条件”检索结果大概率是部署相关文档这没问题。但如果用户先问了一堆功能问题然后说“那到底适不适合我们”这个 query 的语义非常模糊。仅靠当前文本进行向量检索很难命中真正相关的文档。系统必须结合整段对话推断用户当前意图再重新构造检索 query而不是直接拿原始文本去检索。很多 RAG 实现在这一步偷懒了导致检索结果与用户真实需求错位生成质量自然下降。2.4 Agent 多轮工具调用中的目标丢失Agent 场景中模型需要在多个工具之间切换比如调用搜索、数据库查询、计算器等。在工具调用过程中用户意图同样可能演变。例如用户先让 Agent 查询订单状态然后又问物流政策接着又问“那如果延迟可以索赔多少”。Agent 如果严格按顺序执行前两步可能都顺利但第三步需要的是一个综合判断既要知道当前订单状态又要知道物流政策还要计算赔偿金额。如果 Agent 在每一步都只参考当前一轮的 user 输入不维护一个“用户核心目标”信息它就可能在中途忘记最初查询的订单号导致后续工具调用参数缺失或者生成了逻辑上正确的通用回答但并没有针对用户的具体订单。3. 一次可复现的“迷失”实验为了更直观地理解问题可以构造一个简单的模拟实验。这里不依赖特定的大模型接口而是用代码演示“加了意图追踪”和“不加意图追踪”在 prompt 构造上的差异。实际接入模型后回答质量的差别会非常明显。3.1 实验场景设计我们模拟一段多轮客服对话用户先询问产品价格。用户中途问了一堆技术参数。用户最后真正关心的是这套产品能否部署到内网。对比两种 prompt 构造方式方式 A直接把所有历史消息拼接进 system 和 user 消息。方式 B先对历史消息做意图摘要再把摘要注入 system prompt最后拼接最近几轮消息。3.2 核心示例代码下面的代码不调用具体模型而是演示如何构造消息列表。# intent_tracker_demo.py from typing import List, Dict def summarize_intent(history: List[Dict]) - str: 对历史对话做一个简单意图摘要。 生产环境中应该调用大模型或意图分类模型 这里用关键词规则演示思路。 user_text .join( [msg[content] for msg in history if msg[role] user] ) if 内网 in user_text or 部署 in user_text: return 用户希望了解产品能否部署到内网以及部署需要满足哪些条件。 if 价格 in user_text or 报价 in user_text: return 用户想了解产品的价格信息。 if 参数 in user_text or 性能 in user_text: return 用户想了解产品的技术参数和性能数据。 return user_text[-50:] def build_prompt_without_intent(history: List[Dict]) - List[Dict]: 不加意图追踪直接拼接历史。 return [{role: system, content: 你是一个智能客服助手。}] history def build_prompt_with_intent(history: List[Dict]) - List[Dict]: 加意图追踪把意图摘要注入 system。 intent summarize_intent(history) system_content ( 你是一个智能客服助手。 f用户当前的核心诉求是{intent} ) # 只保留最近 4 轮避免历史过长 recent_history history[-4:] return [{role: system, content: system_content}] recent_history if __name__ __main__: history [ {role: user, content: 我想了解一下你们的数据采集服务}, {role: assistant, content: 好的我们的服务支持多种数据源接入。}, {role: user, content: 采集到的数据能直接做可视化吗}, {role: assistant, content: 可以内置了图表分析功能。}, {role: user, content: 这套系统能部署到内网吗}, ] prompt_a build_prompt_without_intent(history) prompt_b build_prompt_with_intent(history) print( 方式 A无意图追踪 ) for msg in prompt_a: print(msg) print(\n 方式 B有意图追踪 ) for msg in prompt_b: print(msg)3.3 运行结果与观察运行上面的代码后方式 A 的 prompt 会比较长包含全部历史消息。方式 B 在 system 中明确写入了“用户希望了解产品能否部署到内网以及部署需要满足哪些条件”并且只保留最近几轮。两者的差异在于方式 A 把“数据采集服务”“可视化”这些历史信息全部暴露给模型模型需要自己判断重点方式 B 提前帮模型做了信息筛选把当前阶段最重要的用户目标放在了最显眼的位置。在实际模型中方式 B 的回答会更有针对性。因为模型不再需要从一堆历史中猜测“用户最终要什么”而是直接基于意图摘要展开回答。当然这段代码里的summarize_intent只是演示用的规则函数。真实项目中建议使用意图分类模型或额外一次 LLM 调用生成摘要同时要注意摘要本身的准确性避免摘要错误导致意图追踪反而失效。4. 工程化解法把意图追踪显式化解决 LLM 在 evolving user intent 下迷失的问题核心思路是“把隐式的意图理解变成显式的上下文管理”。下面介绍几种在实践中有效的方法。4.1 方案一意图摘要注入 System Prompt这是最直接的一种方法。每次用户发送新消息后先对当前对话历史做一次意图归纳把归纳结果写进 system prompt让模型始终明确“用户当前最重要的事情是什么”。def build_system_message(intent: str, extra_context: str ) - str: 构造 system 消息。 intent: 当前用户核心意图 extra_context: 额外的背景信息比如用户画像、业务规则 parts [你是一个智能客服助手。] if intent: parts.append(f用户当前的核心诉求是{intent}) if extra_context: parts.append(f补充信息{extra_context}) return \n.join(parts)核心原则是把意图摘要放在 system 的开头位置提高它在注意力机制中的可见度。同时要注意摘要不能太长最好控制在 50 到 100 字以内否则它本身又会变成新的噪声。这种方式适合绝大多数 ChatBot 场景实现成本低效果好。缺点是需要额外一次模型调用生成摘要会增加延迟和成本。对于延迟敏感的系统可以使用轻量意图模型替代完整 LLM。4.2 方案二对话历史结构化压缩当对话轮次非常多时即使做了意图摘要历史消息依然会占用大量上下文窗口。此时可以对历史做结构化压缩把早期的不关键对话压成精炼信息而不是原样保留。def compress_history(history, max_kept_turns6): 将历史对话分为两部分 1. 早期内容压缩成几条关键信息 2. 近期内容原样保留最近 N 轮 if len(history) max_kept_turns: return history early history[:-max_kept_turns] recent history[-max_kept_turns:] compressed [] for msg in early: role 用户 if msg[role] user else 助手 content msg[content] compressed.append(f{role}{content}) summary .join(compressed) return [ {role: system, content: f以下是早期对话摘要{summary}} ] recent这段代码的思路是早期历史只保留一句话摘要近期历史保留完整原始内容。这样既减少了 token 占用又避免核心信息彻底丢失。实际项目中还可以结合时间维度比如超过一定时间间隔的对话自动划入“早期内容”。压缩摘要既可以在每轮动态生成也可以定时离线生成需要根据业务实时性要求取舍。4.3 方案三RAG 场景下的意图约束在 RAG 系统中不能直接用原始用户 query 去检索而应该先做 query 改写把“当前阶段的核心意图”转化为一个适合向量检索的问句。比如用户说第一轮你们有哪些数据采集方式第二轮日志文件能接入吗第三轮那只能采集日志吗第三轮的原始 query 是“那只能采集日志吗”如果直接拿去检索返回的可能是一些关于日志格式的通用文档。但用户真正想问的是“产品是否支持除日志外的其他数据采集方式”。正确的做法是结合历史把检索 query 改写为“数据采集服务支持哪些数据源类型是否只支持日志”。工程实现上可以在检索前加一个 query 改写模块def rewrite_query_for_retrieval(history, current_query, intent): 基于历史与当前意图改写检索 query。 生产环境建议使用 LLM 生成也可以使用规则。 if intent 数据源类型确认: return 数据采集服务支持哪些数据源是否只支持日志文件 if 部署 in intent: return 产品内网部署条件与网络环境要求 return current_queryRAG 系统质量好坏很大程度取决于 query 改写和检索策略。只在生成阶段下功夫很难弥补检索阶段的意图错位。4.4 方案四Agent 反思机制对于 AI Agent推荐在关键步骤之间加入“反思”环节。Agent 在执行完一次工具调用后不要立刻进行下一次调用而是先把当前结果与最初目标做一个对齐检查。def reflection_check(goal, current_result, model): 检查当前结果是否仍然服务于用户核心目标。 goal: 用户核心目标描述 current_result: 当前工具调用返回结果 prompt f 用户的核心目标是{goal} 当前工具返回的结果是{current_result} 请判断 1. 当前结果是否仍然服务于用户目标 2. 用户意图是否已经发生变化 3. 如果存在偏离下一步应该修正什么 回答格式 - 是否偏离是/否 - 原因说明 - 修正建议 return model.chat(prompt)反思机制会引入额外的模型调用但能显著提升 Agent 在复杂任务中的稳定性。需要注意的是反思指令本身也要写得清晰否则模型会为了“反思”而反思消耗时间却没有实际效果。对于高频 Agent 场景可以只在关键工具调用之间加入反思比如查询类工具与计算类工具之间或者外部 API 调用之前。低频调用则不必每次都反思。5. 常见问题与排查思路实际开发中不同团队遇到的表现不太一样但根因往往有共性。下面整理一份排查表。问题现象常见原因解决思路用户问了多个问题后模型只回答最后一个历史上下文太长早期意图被稀释加入意图摘要把核心目标放在 system 开头回答内容正确但不是用户想要的检索 query 停留在当前文本没有考虑历史意图演变增加 query 改写模块结合历史重构检索条件模型在对话中不断变换立场系统没有维护稳定的用户目标记忆引入结构化意图栈保持目标一致性Agent 调用工具时参数缺失Agent 丢失了早期用户提供的关键信息维护全局上下文变量在每轮工具调用前注入摘要信息不准确反而误导模型意图摘要模型能力不足或规则太粗糙使用更强的模型或训练专用意图分类器并加入人工校验加入摘要后回答变慢每轮额外调用一次模型生成摘要缓存相似意图摘要或使用轻量模型排查时建议按以下步骤进行先复现问题记录用户完整对话和模型输出。检查 prompt 构造逻辑确认历史消息是否被原样拼接。把历史消息逐段移除观察模型回答是否发生变化定位干扰信息。检查检索日志确认实际用于检索的 query 是什么。在 prompt 中加入显式意图描述再次对比输出。评估成本和延迟决定是否需要更轻量的意图识别方案。这套排查思路适用于大部分 LLM 对话和 RAG 项目。重点在于“把问题拆开看”不要一上来就换模型、调温度参数那样往往解决不了根因。6. 最佳实践与工程建议6.1 Prompt 设计层面在做意图追踪时system prompt 的信息结构要清晰第一段写明用户当前核心目标。第二段写模型需要遵守的规则和输出格式。第三段放业务相关的补充上下文。历史消息放在最后作为参考素材而不是作为主要依据。不要让模型自己从历史里“考古”。用户在真实对话中不会主动总结自己的意图系统必须承担这项工作。6.2 数据与评测层面意图跟踪能力的提升离不开评测。建议构建一个面向“意图漂移”的评测集里面包含多轮对话样本每轮标注“当前用户意图”和“期望回答范围”。评测指标可以使用两类意图一致性模型回答是否围绕当前标注的意图展开。关键信息完整度用户提供过的关键约束如订单号、时间、地点是否被正确使用。这类评测集一开始不需要很大50 到 100 个典型场景就能发现问题。随着线上 badcase 累积持续补充样本比堆模型参数更有效。6.3 线上可观测性与安全往线上加入意图摘要和反思机制后可观测性会变得更重要。建议在关键节点打日志至少记录以下信息用户原始消息。意图摘要结果。用于检索的最终 query。Agent 每次工具调用的输入输出。模型最终回答。这些日志不仅能排查问题还能用来持续优化 prompt 和意图分类模型。安全方面要注意意图摘要时不要把用户敏感信息带入日志或摘要中。如果对话中包含手机号、身份证号、地址等敏感字段要提前脱敏。涉及企业内部知识库检索时要遵守最小权限原则确保模型在判断意图后只能访问当前用户权限范围内的数据。6.4 其他工程建议缓存意图摘要。同一会话内如果用户新消息没有明显改变意图可以复用上一次摘要降低成本。设置“意图确认”环节。当模型判断意图发生重大变化时主动向用户确认比如“您是想从价格咨询切换到部署方案咨询吗”。这能大幅减少错误追踪。控制摘要长度。摘要过长反而会成为新的噪声建议 100 字以内。不要过度清理历史。直接删掉早期上下文风险很高优先选择“压缩 保留关键信息”的方式。7. 总结与下一步LLMs Get Lost in Evolving User Intent本质上是模型在长上下文推理中的目标追踪问题。模型自身不具备天然的“用户目标状态管理”能力真正的工程价值在于把用户目标的识别、更新、摘要和注入变成一个显式的系统模块而不是把责任全部交给模型。这篇文章从原理、演示实验、工程方案、排查清单和最佳实践几个方面梳理了一套相对完整的落地思路。核心可以归结为三点意图要显式化通过意图摘要、query 改写等方式把用户当前目标从历史中抽离出来。上下文要可管理不要无条件拼接历史要对历史压缩、分层让重要信息始终占据高优先级。执行要可观测通过反思机制和日志记录及时发现意图追踪的偏差形成持续优化闭环。如果你正在开发多轮对话、RAG 问答或 AI Agent 应用建议先从“意图摘要注入 system prompt”和“日志埋点”入手。这两项改动成本最低收益往往最明显。跑通之后再逐步加入 RAG query 改写和 Agent 反思机制形成一套完整的意图管理链路。下一步可以继续深入的方向包括基于会话维度的长期记忆管理、多目标意图的并行追踪、以及针对特定业务场景的意图分类模型微调。实际项目中优先关注线上 badcase 的回收和标注真实用户带来的意图漂移样例比任何公开数据集都更有价值。