ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型不能跳跃:从RAG到MCP的工程台阶

大模型不能跳跃:从RAG到MCP的工程台阶 如果只看模型生成的应答很多大模型项目看起来已经“会聊天”了。但真正接入业务时就会发现模型经常在缺少关键信息的地方原地打转或者用一段流畅但错误的文本把空缺填上。这种状态很适合用一个标题来概括Position: LLMs Cant Jump。其中的 Position 可以理解为立场声明也可以联想到 Transformer 里的序列位置——模型只能基于当前可用的 token 序列继续生成它不具备在信息缺失时凭空跳转的能力。对于做 LLM 应用的人来说核心任务不是让模型“更聪明”而是把每一步台阶铺好让模型不必跳跃就能走完流程。后面的内容会围绕模型边界、RAG、工具调用、MCP、编排框架、推理精度和评估体系展开最后给出可以直接落地的排查与设计方法。1. 先理解“LLMs Can’t Jump”在说什么1.1 模型不是“跳着思考”而是“按概率续写”大语言模型的底层机制本质上是在反复执行同一件事根据已有 token 序列预测下一个 token 的概率分布再按采样策略选出一个 token追加到序列末尾继续下一次生成。无论是多轮对话、代码生成还是 Agent 任务推理时的主循环都是这个模式。它决定了模型是顺序推进的不会主动跳出当前序列去“回忆”一个从未出现过的细节也不会自行回溯前面计算中的错误。举个例子。你给模型一段完全没有天气数据的对话然后问“北京现在多少度”。如果训练数据让模型对北京天气有很强的先验它可能会编出一个听起来合理的温度如果训练数据不足它也可能明确表示不知道。这两种反应都说明同一件事模型只能在概率空间里给出一个“最像答案”的续写而不是先完成信息核查再回答。因此当业务需要精确数据、实时状态或外部动作时不能指望模型自己去跳必须由系统把相关信息送到上下文里。1.2 “Position”的双关既指立场也指序列位置“Position”在论文标题里通常表示作者的立场但放在大语言模型语境下它还有一个更工程化的含义序列位置。模型对 token 的顺序和位置非常敏感训练时见过的序列长度决定了位置编码的适用范围。把远超训练长度的文本直接塞进模型很多模型并不会完全失效但注意力会被稀释位置编码也可能外推失效于是生成结果开始漂移。这个现象同样是“跳不过去”模型不能跳过上下文窗口的限制去理解一个中间段落的逻辑。对应用开发者来说这带来的启示很直接不要把所有资料一股脑拼进 prompt 就期望模型抓到重点。超过窗口长度时应该先做摘要、过滤和检索让信息以更紧凑的形式进入上下文。这正是 RAG 这类方案存在的理由也是很多“长文本总结”项目越做越差的原因——不是模型不强而是输入组织方式有问题。1.3 工程核心给模型搭台阶既然模型不能跳工程的目标就变成“搭台阶”。从低到高通常有四个层级层级解决的问题典型技术上下文增强信息缺失导致幻觉RAG、摘要、知识库检索工具调用模型无法完成外部动作Function Calling、MCP编排框架多步骤任务的状态与流程控制LangChain、LlamaIndex、Spring AI评估与观测判断模型是否真的完成任务指标、日志、回放、评测集这篇文章后续的章节就按这四个层级展开。每一层都是在为模型补充它跳不过去的那一段路。理解这件事以后就不会再遇到问题就盲目改提示词而是先问一句模型这一步是不是本来就不该跳2. 模型不能跳跃时的三类典型故障2.1 幻觉信息真空中的流畅编造幻觉是“不能跳跃”最直接的体现。当问题需要的答案不在上下文里而模型必须生成一个 token 时它就会用训练先验填充内容。这种内容往往语法流畅、结构完整甚至包含具体数字因此比明显报错更难发现。实际项目里最常见的是模型在回答运营报表问题时把指标口径说错或者给出一串看起来精确但无法对账的数据。要减少幻觉不能只靠提示词写“请如实回答”。更可靠的做法是给模型提供可校验的信息源比如检索回来的原文并明确要求答案必须引用这些原文同时配合“不知道就回答不知道”的拒识策略把编造机会关掉。在代码层面可以要求模型输出 JSON其中answer字段只允许来自给定上下文confidence字段用于后续人工抽查。2.2 拒识与 FC 输出不合法模型“跳”不出格式约束除了答案内容格式问题同样常见。使用 Function Calling 时模型需要在回复中输出符合工具描述的结构化参数通常是 JSON。如果模型在 JSON 前加了一句“好的我来查询天气”或者把参数名写错下游解析器就会报错。业务上这等同于模型在离完成任务只差一步时跳不过去。另一个容易混淆的概念是“拒识”。拒识指模型拒绝执行任务可能返回“我无法回答”也可能返回一段安全提示。产品上需要区分三种情况是模型能力不足导致的拒识是安全策略触发的主动拒识还是因为上下文没有给足信息而引发的自我防御。不同原因处理方式完全不同。能力不足就补 RAG安全拒识要保留审计日志信息不足则调整 prompt 结构或补充检索结果。针对 Function Calling 格式问题一种做法是使用约束解码让模型在生成时只能输出合法 JSON另一种做法是解析层做容错比如去掉首尾解释文本再调用 JSON 解析。推荐两种同时做生成端减少错误解析端兜底。2.3 精度漂移FP16、FP32、BF16 对结果的影响模型推理阶段的数值精度也会影响“跳跃”的成功率。FP32 是绝大多数模型训练和评估的基线精度直接部署效果最稳但显存占用高。FP16 显存减半但可表示的范围和精度有限容易出现溢出。BF16 保留和 FP32 相同的指数范围牺牲尾数精度在深度学习场景通常比 FP16 更稳已经成为很多大模型推理的默认选择。精度位宽指数位尾数位典型问题推荐场景FP3232823显存占用高基准验证、调试FP1616510可能溢出微调不佳显存紧张但需高速度BF161687尾数精度有限推理和训练的主流选择当模型被压到 INT8 或更低时输出分布可能进一步偏移导致原本置信度最高的正确答案被其他 token 反超。开发者最容易遇到的坑是同一套业务在 FP32 环境跑得好切到 FP16 后工具调用突然失败。排查时不要只怀疑 prompt还要把精度因素纳入检查清单。加载本地模型时建议明确指定torch_dtypefrom transformers import AutoModelForCausalLM, AutoTokenizer import torch model_id your-model-path tokenizer AutoTokenizer.from_pretrained(model_id) model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.bfloat16, device_mapauto )这里的关键是不要完全相信默认精度先确认模型仓库建议的精度再在评估集上验证精度切换是否影响了业务指标尤其是工具调用场景。3. 搭台阶第一层给模型补上“能检索的记忆”3.1 为什么 RAG 比提示词拼接更可靠为什么不直接把所有文档塞进 prompt因为上下文窗口有限、成本高、信息密度低。RAG 的思路是先把文档切块、向量化、存入向量库提问时先检索出与问题最相关的几段文本再把这几段拼进 prompt。模型从这些段落里找答案而不是靠记忆“猜”。这种方式让知识更新变得简单替换向量库中的文档即可不需要重新训练或微调模型。对事实型问题RAG 是比修改 Prompt 更接近根因的解法。它的前提假设是模型不需要知道全部知识只需要在需要时拿到正确的片段。这也正好对应“LLMs Can’t Jump”的观点——模型不能跳过检索这一步系统替它完成信息跨越。3.2 最小 RAG 流程切块、向量化、召回、回答下面用一个最小示例展示完整流程。先安装依赖# requirements.txt transformers4.40.0 torch2.2.0 sentence-transformers3.0.0 faiss-cpu1.8.0 openai1.30.0 fastapi0.111.0然后是向量化与检索。这里使用 sentence-transformers 生成向量使用 FAISS 做最近邻查询from sentence_transformers import SentenceTransformer import faiss model SentenceTransformer(BAAI/bge-small-zh-v1.5) docs [ Redis 默认端口是 6379。, Redis 在 Linux 下默认监听本机回环地址。, Spring AI 提供了与 MCP 客户端集成的 starter。 ] vectors model.encode(docs) index faiss.IndexFlatL2(vectors.shape[1]) index.add(vectors) query Redis 默认端口是什么 query_vec model.encode([query]) distances, indices index.search(query_vec, top_k1) print(distances) print(indices)检索得到indices后把对应文档拼进 promptdef build_prompt(query: str, contexts: list[str]) - str: ctx \n\n.join(f[{i1}] {c} for i, c in enumerate(contexts)) return ( f请只根据下面提供资料回答问题。\n\n f{ctx}\n\n f问题{query}\n f回答 )这里有两个关键参数需要调chunk_size决定检索粒度太小会丢失上下文太大则会把不相关内容带进来top_k决定送入 prompt 的片段数量过大反而会分散模型注意力。它们需要根据测试集来调而不是拍脑袋定。3.3 文本向量 API 未配置时的现象与修复很多 RAG 项目在本地模型还没下好时会选择调用文本向量 API。此时最容易遇到的问题就是“向量 API 未配置”。现象通常表现为调用时报 401、403、超时或者返回结果向量维度不一致。检查顺序建议如下确认环境变量里是否真的存在EMBEDDING_API_KEY名称是否与代码读取时一致。确认所选择的 embedding 模型名是否存在是否支持当前语言。确认向量库的维度是否和 API 返回维度一致。如果文档向量是 768 维库里建成了 1024 维检索时必然报错。确认网络策略允许访问对应 API 域名。注意不要把检索结果不加区分地拼进 prompt。检索质量差时上下文越强错误越顽固。调试 RAG 时先把检索结果打印出来看是否合理再检查生成答案。4. 搭台阶第二层让模型能调用工具4.1 从“回答问题”到“完成任务”RAG 解决了信息缺失但模型仍然“只会说不会做”。查询天气、创建工单、执行 SQL 都需要真实动作。Function Calling 的作用是让模型输出一个结构化的“调用意图”由外部程序执行动作并把结果返回给模型。这样模型本身不需要会执行只需要判断“此时该调用哪个工具、传哪些参数”。但工具调用也引入新的风险。模型如果拥有过大的工具权限可能反复调用高成本接口甚至执行危险操作。安全领域把这类问题称为 excessive agency模型拥有的行动权超过了完成单次任务所需的最小范围。设计时不要把“能调用”和“可以无条件调用”画等号。4.2 一个可控的 Function Calling 最小示例下面示例演示如何让模型选择一个天气查询函数。模型本身不执行函数只输出tool_calls真正执行的是 Python 代码。from openai import OpenAI client OpenAI(api_keyyour-api-key, base_urlyour-base-url) TOOLS [ { type: function, function: { name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: {type: string} }, required: [city] } } } ] resp client.chat.completions.create( modelyour-model, messages[{role: user, content: 北京现在热吗}], toolsTOOLS, tool_choiceauto ) print(resp.choices[0].message.tool_calls)实际项目里base_url可能指向本地推理引擎API Key 可以是一个占位值。拿到tool_calls后程序里需要维护一个“工具名到函数”的映射def dispatch(tool_name: str, args: dict): if tool_name get_weather: return get_weather(cityargs[city]) raise ValueError(funknown tool: {tool_name})执行结果要作为一条新的tool消息传回模型模型才会依据结果继续生成最终回答。很多 Agent 流程出问题就是因为没有把工具结果正确地追加回对话历史。4.3 MCP将工具接入协议化靠 Function Calling 一个个写工具映射工具多了会很难维护。MCP 用一套统一协议描述工具、资源和提示词让 LLM 客户端可以动态发现并调用外部工具。它解决的核心问题是“连接标准化”不用为每个新数据源单独写一套解析逻辑。一个最小 Python 客户端连接示例from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client server_params StdioServerParameters( commandpython, args[weather_server.py] ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() for tool in tools.tools: print(tool.name, tool.description) if __name__ __main__: import asyncio asyncio.run(main())初次跑通 MCP 时不要急着把所有工具都接进来。先接一个只读工具验证客户端与服务器的连接、工具列表、参数结构和返回格式都符合预期再逐步扩展。4.4 权限与长循环的坑工具调用最常见的坑有两个。循环重试模型调用失败后如果没有最大迭代次数限制它可能反复调用同一工具白白消耗 token。权限过大给模型开放了删除、写入、高成本操作接口。一旦 prompt 注入触发损失不可控。推荐做法所有工具默认只读高风险操作单独标注每次对话最多执行 N 次工具调用工具返回结果要带状态码和错误信息方便模型判断下一步。5. 搭台阶第三层编排、框架与本地推理配置5.1 为什么需要编排框架当任务只有一步时手写循环还能撑住。但真实业务往往是“查资料 - 调用工具 - 写总结 - 再查询”中途还要处理失败、重试和条件分支。没有编排框架模型调用代码会散落在业务逻辑里难以复用、难以观测。框架的价值在于把流程、状态和工具调用抽象成可维护的组件同时提供日志、重试和扩展点。常见的编排方式有三种LangChain 式的链与代理、LlamaIndex 式数据索引、Spring AI 式的 Java 生态集成。选型时不要追求功能最多先看团队的编程语言、模型接入方式、以及是否需要与现有 Spring Boot 应用融合。5.2 Spring AI MCP RAG Agent 的集成思路在 Java 技术栈里Spring AI 可以把模型客户端、向量库、工具调用统一封装。如果项目需要接入 MCP通常的做法是引入spring-ai-starter-mcp-client然后在配置中声明 MCP 服务器地址。下面是一个 Maven 依赖示意dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency然后在application.yml里配置模型和 MCP 服务spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL} mcp:
返回列表