ARTICLE DETAIL

资讯详情

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

Don‘t credit the LLM:构建可靠大模型应用的系统工程实践

Don‘t credit the LLM:构建可靠大模型应用的系统工程实践 如果你的团队也正在用大模型做应用很可能遇到过这样的场景模型选型时千挑万选评测榜单上的开源模型和商用模型都试了一遍提示词也调了一轮又一轮可产品一上线效果依然达不到预期。项目复盘时大家习惯性把所有问题都归类为“模型不行”。但真正把请求日志、工具调用结果和中间输出翻出来后你会发现大部分问题并不是模型推理能力导致的而是应用系统里缺少校验、缺少兜底、缺少对模型能力的边界认知。“Dont credit the LLM”这句在开发者社群中流传的话并不是在否定大模型的价值而是在提醒我们LLM 只是整个应用系统里的一个组件它既不应该独占所有功劳也不应该承担所有失败。一个可用的 AI 应用最终效果是由模型、提示词、外部数据、工具调用、输出校验、交互设计和日志评估共同决定的。谁负责哪一段谁就要被验证、被约束、被兜底。本文会从这句话出发拆解 LLM 应用开发的正确姿势并通过一个完整的客服助手案例演示如何把“模型只负责生成”这一原则落到实处。文章适合正在做 LLM 应用开发、Agent 搭建或 RAG 项目的开发者也适合刚接触大模型 API、对工程化实践还比较模糊的新手。读完你可以理解为什么不能盲目信任模型输出以及如何通过结构化输出、function calling、兜底逻辑和日志评估来构建一个更可靠的应用。1. 理解“Dont credit the LLM”从模型崇拜到系统工程1.1 这句话到底在说什么“Dont credit the LLM”字面意思是“不要把功劳都记在 LLM 头上”。在工程语境里它更实际的含义是不要把最终业务效果的好坏简单归因于模型本身。LLM 本质上是一个基于海量文本训练的概率生成模型它的任务是“给定上文生成一个看起来合理的下文”。这个“看起来合理”并不等于“业务正确”。业务正确需要知识库、工具、规则和兜底机制来保证。比如你问客服机器人“我的订单什么时候到”模型可以生成一句非常流畅的“您的订单预计明天送达”但如果它根本没有查询过你的订单系统这句话就只是编造而不是事实。很多 LLM 应用项目失败不是因为模型不够聪明而是因为开发者把模型当成了数据库、逻辑引擎和业务规则引擎的合体。过度信任模型输出会让应用在遇到边界情况时变得非常脆弱。把模型放回系统里看待理解它的能力边界才是工程化的起点。1.2 三个常见误区第一个误区是“模型越强应用越强”。确实更强的模型在理解复杂指令、处理长文本、生成高质量回答方面有明显优势但应用的最终体验还取决于知识数据是否准确、工具调用是否稳定、输出是否符合下游格式。一个平庸模型配上精确的检索和严格的校验往往比一个顶尖模型裸奔上线更可靠。第二个误区是“提示词写好了就万事大吉”。提示词是 LLM 应用极其重要的一环但它非常脆弱。稍微换一个问法、输入一个没见过的数据格式、工具返回了跟预期不一致的字段模型就可能偏离轨道。提示词只能约束模型的生成倾向不能保证业务正确性。第三个误区是“模型输出可以直接当结果用”。模型返回的是一段文本而下游系统通常需要结构化数据比如 JSON、SQL、状态码。如果直接把它透传给用户或业务系统一旦出现格式错误、字段缺失、内容包含幻觉就会引发线上事故。模型输出必须经过解析、校验和兜底这应该是默认动作而不是可选项。1.3 LLM 应用到底有哪些组成部分一个完整的 LLM 应用通常包含以下环节输入清洗、意图识别与路由、外部知识检索RAG、工具调用function calling / MCP、提示词组装、模型生成、输出解析、业务规则校验、兜底响应、日志与评估。每一个环节都会影响最终效果。输入清洗决定了模型看到的内容质量意图识别决定请求是否被正确分配RAG 决定模型有没有足够的事实依据工具调用决定模型能否获取实时数据输出解析决定下游能否消费结果兜底逻辑决定异常情况下用户会不会被“晾在”那里日志评估决定你能否持续发现问题。这就是“Dont credit the LLM”的核心逻辑模型只负责其中一个环节其他环节需要你投入同样的精力。现在的 LLM Agent、RAG、MCP 等热门方案本质上都是在给模型做约束和补给而不是让模型单打独斗。2. 核心原则模型只是组件不是大脑2.1 一个最小 LLM 应用的链路我们可以把一次完整的 LLM 应用调用拆成下面几条链路用户输入原始问题。系统对输入做清洗和基础校验比如敏感词、长度限制、是否是无效内容。意图识别模块判断问题属于问答、订单查询还是转人工。需要外部数据时触发 RAG 检索或 function calling 工具调用。把检索结果、工具返回结果、历史对话组装进消息上下文。调用模型生成回答。对模型输出做格式和内容校验。校验通过则返回用户否则走兜底逻辑。全程记录日志用于评估和后续优化。这个链路里第 6 步才是模型推理其余步骤都是工程代码。很多团队只把注意力放在第 6 步选择模型、调提示词、调温度参数却忽视了前后端环节。结果模型看起来很强应用整体却很脆弱。2.2 各环节如何影响最终效果数据质量对模型输出的影响往往被低估。如果 RAG 检索回来的文档本身是过期的、不相关的模型再强也会基于错误材料作答。工具调用的结果同样需要校验假设订单查询接口返回了一个status字段取值是1、2、3你的代码需要先把数字翻译成“已发货”“运输中”“已签收”再交给模型组织语言否则模型可能把1理解成“已收到”之类的错误含义。输出校验也很关键。如果下游系统需要 JSON模型却返回了带 Markdown 代码块的 JSON解析端不做处理就会直接报错。再比如模型生成了“我可以帮你删除所有数据”这种危险回复业务规则必须拦截掉。把这些问题归到“模型幻觉”头上固然没错但更准确的说法是系统没有给模型足够的约束和校验才让幻觉有机会产生实际影响。2.3 两种典型的“过度信任”失败场景场景一把模型当数据库。用户问“A 商品还有库存吗”模型从未接入库存系统但基于训练数据里的商品信息编造了一个答案。这是最典型的错误。正确做法是接入库存查询工具或数据库拿到真实数据后再让模型组织回答。模型不知道就是不知道强行生成只会产生幻觉。场景二Agent 中让模型自由调用工具却不校验工具参数。现在很多 Agent 框架允许模型自己决定调用哪个函数、传什么参数这确实很灵活但也很危险。模型可能从一个用户输入里提取出错误的订单号调用查询接口后把错误信息当正确答案返回给用户。解决思路是工具参数必须在代码里做一次格式校验工具返回结果也必须做业务校验而不是无条件相信。这两个场景的共同点是模型被赋予了超出它能力范围的职责。记住一句话模型负责生成系统负责正确。3. 环境准备与最小示例3.1 运行环境在开始写代码之前先确认你的环境。本文示例使用 Python建议使用 Python 3.9 或更高版本。大模型 API 的 SDK 不同厂商有所差异但绝大多数都兼容或参考了 OpenAI 的接口风格包括messages、tools、response_format等概念。如果你使用的是国内云厂商的模型服务、企业私有化部署的模型或者开源模型框架只需要把base_url、api_key、model替换成你自己的信息即可。安装依赖时只需要安装官方 SDK 或通用的 HTTP 客户端。例如pip install openai如果你的网络环境无法使用该 SDK也可以用requests直接调用 HTTP 接口本质是一样的。要注意的是不同提供商的接口版本存在差异例如新版 SDK 和旧版 SDK 在客户端初始化方式上不同。本文以较新的客户端风格为例如果你的版本不同以你使用的 SDK 官方文档为准。3.2 最小调用示例先写一个最小调用示例感受一下模型返回的原始内容长什么样。这个例子可以用于快速验证 API 连通性。# 文件路径llm_app/minimal_call.py import os from openai import OpenAI client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL), ) response client.chat.completions.create( modelos.getenv(LLM_MODEL_NAME), messages[ {role: system, content: 你是一个简洁的助手。}, {role: user, content: 请介绍一下你自己}, ], temperature0.7, ) content response.choices[0].message.content print(content)运行前先设置环境变量export LLM_API_KEY你的密钥 export LLM_BASE_URL你的模型服务地址 export LLM_MODEL_NAME你的模型名称这里需要重点注意的是response.choices[0].message.content是一个字符串。也就是说模型返回的本质是一段文本而不是结构化数据。无论它看起来像 JSON、像代码、像表格在程序里都只是字符串。这一步认知很重要因为后面所有校验逻辑都是围绕“把字符串变成可信赖的业务数据”展开的。3.3 为什么必须做输出校验假设你让模型输出一个 JSON内容是用户问题的意图分类。模型可能会返回以下几种情况标准 JSON{intent: query_order}带 Markdown 代码块的 JSON\json\n{intent: query_order}\n解释性文字加上 JSON比如“根据您的输入意图如下{intent: query_order}”完全无法解析的文本甚至因为幻觉产生缺少引号的 JSON。如果不对输出做处理直接执行json.loads(content)第二种和第三种情况会直接抛异常第四种情况会静默返回错误结果。下面是一个健壮的解析函数import json import re def parse_json_content(content: str) - dict: 从模型输出中提取 JSON 对象返回字典。 if not content: raise ValueError(模型输出为空) text content.strip() # 去掉代码块标记 text re.sub(r^(?:json)?\s*, , text) text re.sub(r\s*$, , text) # 查找第一个 { 到最后一个 } 之间的内容 start text.find({) end text.rfind(}) if start -1 or end -1 or end start: raise ValueError(f模型输出中没有找到 JSON 对象: {content}) json_str text[start:end 1] return json.loads(json_str)这段代码的作用是容忍模型输出常见的“杂质”比如代码块标记和解释性文字再把最核心的 JSON 部分提取出来。如果仍然解析失败就应该触发重试机制或走兜底逻辑而不是把异常直接抛给用户。这就是“不盲目信任模型输出”的第一个落地点。4. 实战构建一个不“迷信”LLM 的客服助手4.1 需求拆解与流程设计下面我们做一个更完整的示例一个客服问答助手。它的业务范围包括回答常见问题比如退货政策、发货时间。查询订单状态需要接入订单系统。无法回答时明确答复不知道而不是编造。遇到超出范围的问题时引导转人工。这个需求在 LLM 应用开发中很常见也很适合用来展示“模型只是组件”的思想。我们不会把所有逻辑都塞进提示词里而是通过代码来拆分职责。整体流程设计如下接收用户问题。调用模型判断问题是否需要查询订单这一步用 function calling 实现。如果需要查询订单代码执行真实的订单查询函数拿到结果。把订单查询结果放回消息记录让模型基于真实数据生成回答。对模型最终输出做格式和内容校验。校验失败或判断为无法回答时返回预设的兜底话术。下面逐步实现。4.2 结构化输出与 JSON 校验先来实现意图识别模块。这里我们仍然让模型来理解用户输入但要求它输出结构化 JSON并且我们的代码会做严格校验。# 文件路径llm_app/intent.py import json import os from openai import OpenAI from .utils import parse_json_content client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL), ) SYSTEM_PROMPT 你是客服助手的意图识别模块。 请判断用户问题的意图只输出 JSON 对象不需要任何解释。 JSON 格式{intent: query_order | faq | unsupported, need_order_id: true | false} 规则 1. 如果用户询问订单、物流、配送、签收intent 为 query_orderneed_order_id 为 true。 2. 如果用户询问退换货、发货时间、发票、售后政策intent 为 faq。 3. 如果问题与业务无关或不确定intent 为 unsupported。 def analyze_intent(user_query: str) - dict: 调用模型分析用户意图返回结构化字典。 response client.chat.completions.create( modelos.getenv(LLM_MODEL_NAME), response_format{type: json_object}, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_query}, ], temperature0.1, ) content response.choices[0].message.content data parse_json_content(content) # 校验字段完整性 if intent not in data: raise ValueError(f模型输出缺少 intent 字段: {content}) if data[intent] not in (query_order, faq, unsupported): raise ValueError(f模型输出了未知的 intent: {content}) return data这段代码的特点是模型负责理解语义代码负责决定这个理解是否可用。response_format参数是很多模型服务商支持的 JSON 模式如果当前模型不支持可以去掉该参数依靠提示词约束。字段校验是必须的因为即使使用 JSON 模式模型仍有可能输出缺少字段或意义不明的结果。4.3 通过 function calling 接入订单查询能力当意图是query_order时我们需要从用户输入中提取订单号并调用真实的订单查询服务。这里的关键点是function calling 只是让模型输出“我想调用某个函数参数是什么”真正的函数执行发生在我们的代码里。# 文件路径llm_app/order_agent.py import json import os from openai import OpenAI from .intent import analyze_intent client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL), ) TOOLS [ { type: function, function: { name: query_order, description: 根据订单号查询订单的当前状态、物流信息和预计送达时间, parameters: { type: object, properties: { order_id: { type: string, description: 订单号通常是字母和数字的组合 } }, required: [order_id] } } } ] def query_order(order_id: str): 模拟订单查询函数。实际项目中这里应调用订单系统接口。 if not order_id or len(order_id) 5: return {error: 订单号格式不正确, order_id: order_id} # 这里只是模拟结果生产环境请替换为真实查询 return { order_id: order_id, status: shipped, status_text: 已发货, latest_logistics: 包裹已到达杭州转运中心, estimated_arrival: 3天内 } def run_order_agent(user_query: str): 执行带工具调用的完整对话。 messages [ {role: system, content: 你是客服助手。请基于工具返回的真实信息回答用户。如果工具返回错误请告知用户暂时无法查询。}, {role: user, content: user_query} ] first_response client.chat.completions.create( modelos.getenv(LLM_MODEL_NAME), messagesmessages, toolsTOOLS, temperature0.3, ) first_message first_response.choices[0].message if not first_message.tool_calls: return first_message.content for tool_call in first_message.tool_calls: if tool_call.function.name query_order: args json.loads(tool_call.function.arguments) order_id args.get(order_id, ) # 参数校验模型可能漏传订单号 if not order_id: return 没有识别到订单号请提供您的订单号。 result query_order(order_id) messages.append(first_message) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) second_response client.chat.completions.create( modelos.getenv(LLM_MODEL_NAME), messagesmessages, toolsTOOLS, temperature0.3, ) return second_response.choices[0].message.content这段代码里有两个容易踩坑的地方。第一模型提取出的order_id不一定可靠可能多出前后空格可能包含标点符号需要在代码里做清洗和校验。第二工具返回结果后必须把工具结果以role: tool的消息放回上下文模型才能基于真实信息生成回答。这个过程完整展示了“模型只负责生成代码负责执行”。4.4 兜底与拒识逻辑客服助手最怕的是模型在不知道答案时编造一个看起来合理的回答。所以我们要在业务层面加一道兜底逻辑包括三类情况模型意图识别为unsupported直接返回预设话术。模型生成内容为空或包含“不确定”“没有相关信息”等拒识表达时返回兜底话术。调用过程中出现异常比如超时、JSON 解析失败、工具调用异常不能让用户看到报错信息。# 文件路径llm_app/safety.py from .order_agent import run_order_agent from .intent import analyze_intent UNSUPPORTED_REPLY 这个问题超出了我的服务范围已为你转接人工客服。 FAILED_REPLY 我暂时无法获取准确信息请稍后再试或直接联系人工客服。 def safe_reply(user_query: str) - str: 客服助手入口带完整兜底逻辑。 try: intent_data analyze_intent(user_query) intent intent_data.get(intent) if intent unsupported: return UNSUPPORTED_REPLY if intent query_order: answer run_order_agent(user_query) else: answer run_order_agent(user_query) if not answer or len(answer.strip()) 0: return FAILED_REPLY # 拒识关键词检查防止模型硬编造 refuse_keywords [我不知道, 我不确定, 无法确认, 没有相关信息] if any(kw in answer for kw in refuse_keywords): return 该问题需要专业人工确认已为你转接人工客服。 return answer except Exception as e: # 生产环境这里应记录完整异常堆栈 print(fsafe_reply error: {e}) return FAILED_REPLY这里的拒识关键词判断只是最简单的一层。更可靠的做法是使用分类模型或规则引擎来判断模型是否真的回答了用户问题。但无论如何兜底逻辑的核心原则是宁可少答不可错答。4.5 日志记录与简单评估最后一个工程环节是日志。没有日志你就无法知道是哪一环出了问题。每次调用都应该记录下用户输入、模型输出、工具结果、错误信息、耗时等信息。# 文件路径llm_app/logging_utils.py import json import time from pathlib import Path LOG_DIR Path(logs) LOG_DIR.mkdir(exist_okTrue) LOG_FILE LOG_DIR / chat_log.jsonl def log_chat(session_id: str, user_query: str, reply: str, tool_resultNone, error: str None, latency_ms: int 0): 记录一次会话日志便于后续排查和评估。 record { ts: int(time.time() * 1000), session_id: session_id, user_query: user_query, reply: reply, tool_result: tool_result if tool_result is not None else {}, error: error, latency_ms: latency_ms } with open(LOG_FILE, a, encodingutf-8) as f: f.write(json.dumps(record, ensure_asciiFalse) \n)日志是评估的基础。建议人工定期抽检日志对模型回复打标“正确”“错误”“可接受”。积累几百条标注数据后就可以做回归测试。每次修改提示词或更换模型都可以拿这批数据跑一遍观察正确率变化。这才是“不迷信模型”的最终形态用数据说话而不是凭感觉说“感觉效果变好了”。5. 常见问题与排查思路5.1 高频问题速查表下面整理 LLM 应用开发中常见的问题、原因和解决思路。问题现象常见原因解决思路模型编造订单号、库存等数据模型未接入真实数据源强制接入查询工具或数据库检索不到时明确拒答输出 JSON 解析失败模型输出带解释文字或格式不规范使用 JSON 模式、去除代码块标记、解析失败后重试function calling 不触发工具描述不够清晰触发条件不明确优化工具描述补充调用示例明确参数格式工具返回结果后模型回答错误工具结果未回填或字段语义未解释把工具结果放入消息上下文并在提示词中解释字段含义响应延迟高上下文过长、模型过大、多次串行调用压缩历史消息、使用流式输出、拆分复杂流程上下文长度超限对话轮次太多或检索文档过长截断历史消息、对检索结果做摘要、只保留关键片段模型输出越来越不稳定温度参数偏高或提示词冲突降低 temperature收敛提示词把约束写清楚5.2 排查顺序建议遇到一个 LLM 应用问题时建议从日志开始排查而不是先去质疑模型。推荐按下面的顺序逐步定位第一确认输入输出。先看用户实际传入的文本是什么日志里记录的模型输入是什么。很多时候用户输入和开发时的测试用例差异很大比如包含表情符号、错别字、口语化表达这些都会影响模型理解。第二隔离问题环节。是意图识别错了还是工具调用参数错了还是模型基于正确数据却生成错误回答可以通过分别打印每个环节的中间结果来确定。本文示例中analyze_intent的输出、query_order的结果、run_order_agent的最终回复都可以用日志打印出来。第三复现并修复。确定问题环节后写一个最小复现用例修改对应的提示词或代码再用同一用例验证。第四补充回归。修复后把这个用例加入回归测试集防止后续改动导致问题复发。这套流程和传统后端开发的排查方式几乎一样。区别只是多了一个“模型输出不确定”的变量所以更需要用日志和评估集来对抗这种不确定性。6. 工程最佳实践6.1 提示词也是代码必须版本管理提示词不要只写在聊天记录里。建议把系统提示词、用户提示词模板、few-shot 示例都存成单独的文件纳入 Git 管理。改动提示词要像改代码一样提 PR、做 review、跑评估。目前很多团队已经在用提示词管理平台即使没有至少也要做到提示词可追溯。每当一个功能上线后记录当时的提示词版本和模型版本。这样如果线上效果波动可以快速确认是模型服务商升级了模型行为还是我们改了提示词。这个习惯会让排查效率高很多。6.2 可观测性与成本控制LLM 应用的可观测性比传统应用更重要因为它多了一个不确定性来源。建议至少记录以下指标每次请求的模型名称和模型版本。输入的 token 数量、输出的 token 数量。首字延迟和总耗时。是否触发工具调用工具调用是否成功。输出校验是否通过是否走了兜底逻辑。错误类型和错误堆栈。成本方面可以采用模型分级策略简单意图识别用小模型复杂客服回答用大模型日常问题走批量处理紧急问题走在线调用。这些策略需要结合实际的调用量和成本预算来设计不必一开始就做得非常复杂。6.3 安全与合规注意事项LLM 应用的安全问题不能等到上线后再处理。首先是提示注入风险。用户输入本质上是不可信数据如果系统提示词里包含了敏感指令恶意用户可能通过输入“忽略以上所有规则”来绕过限制。应对思路包括对用户输入做敏感词过滤、将不可信输入与控制指令分开、在代码层面对工具调用权限做最小化限制。其次是生产数据安全。涉及订单、用户信息等敏感数据时日志中不要记录完整手机号、地址等敏感字段必要时要脱敏。工具调用的权限要按最小化原则设计比如一个查询订单状态的函数不应该同时具备修改订单的权限。最后是变更安全。涉及删除、修改生产数据的操作绝对不能只靠模型决定。所有写操作必须经过人工确认或强校验并且要有审批和审计记录。这不仅是工程问题也是合规底线。7. 总结与下一步学习路线7.1 本文核心收获“Dont credit the LLM”不是一句口号而是一条工程原则。它的具体含义可以拆成四点第一模型只负责生成正确性由系统保证。你需要通过工具调用、RAG、规则约束等方式给模型提供事实依据。第二模型输出必须校验。JSON 解析、字段校验、内容审核都要在代码里完成不能把模型的文本输出直接当业务结果。第三兜底逻辑必不可少。模型不会的时候要会说“不知道”系统异常的时候要能给用户一个体面的回复而不是把报错堆到用户脸上。第四一切以日志和评估为准。没有数据支撑的“我觉得效果不错”是不可靠的。建立标注集、回归测试和观测体系才能让应用持续演进。7.2 后续可以继续深入的方向如果这篇文章的实践内容你已经掌握了下一步可以朝这几个方向继续深入一是 RAG 的进阶优化包括文档切分策略、向量检索的召回率评估、重排序模型的使用。RAG 能解决模型知识不足的问题但它本身也需要大量的工程调优。二是 Agent 编排框架的深入。很多框架已经把 function calling、MCP 工具调用、多轮对话管理封装好了但框架带来的抽象也可能隐藏问题。建议先用本文这种裸调用方式理解原理再决定要不要引入框架。三是评估体系建设。这是 LLM 应用从 demo 走向生产的关键一步。可以学习如何构建高质量的评测集如何使用开源评估工具如何在模型升级时做效果对比。我在做这个客服助手项目的过程中最大的体会是真正花时间的地方不是让模型“更聪明”而是让应用“更可靠”。下一次当你准备把效果不好归因于模型时先问一句日志里能看到是哪一环节出了问题吗如果看不到先补日志如果看到了就先去修那个环节。这才是对 LLM 应用最务实的态度。
返回列表