尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

大模型选型与Prompt工程实战:从流式输出到稳定性兜底

大模型选型与Prompt工程实战:从流式输出到稳定性兜底 这篇稿子我是真刀真枪在“超体”项目里熬出来的。前两篇系列文章聊了整体架构和数据流这一篇专门讲模型选型和 Prompt 工程这两块硬骨头。很多朋友问我为什么同一个大模型有的人用起来稳定靠谱有的人用起来像开盲盒答案很直接选型选错了Prompt 写得稀烂。这篇就把我在“超体”里的完整思路和踩坑经验拆开讲从怎么选模型到怎么写提示词再到流式输出和兜底策略全部实操验证过。1. 选型之前先想清楚三件比模型更重要的事很多人的第一反应是拿榜单排名选模型哪个分高用哪个。这种思路放在“超体”里会死得很难看。因为榜单测的是通用能力而你的业务要的是特定场景下的稳定输出。我在项目早期用过一个综合评分很高的模型结果在结构化抽取任务上频繁格式漂移后来一查才发现它对中文指令的跟随性并不好只是英文基准测试分数漂亮。选型之前必须回答三个问题这三个问题直接影响后续所有技术决策第一个问题是任务类型到底偏生成还是偏抽取。任务类型决定了模型的能力侧重点也决定了 Prompt 的写法。如果“超体”的核心场景是给定一堆非结构化文本输出 JSON 格式的实体和关系那么模型对格式指令的遵从度比语义理解能力更重要。相反如果场景是营销文案生成那模型的文采和风格迁移能力就更关键。我实际用下来发现通用对话模型在纯抽取任务上表现普遍不如专门的指令微调模型因为前者被训练得过于“话痨”总想解释几句而不是老老实实给结果。第二个问题是对延迟和成本的容忍度。“超体”里如果只是离线批量分析可以用大参数模型慢慢跑但如果要做在线实时交互每一轮对话都卡 3 秒体验就直接崩了。我在早期试过 70B 级别的模型生成质量确实好但单次推理要 2 到 4 秒根本扛不住真实请求。后来换成了 7B 到 14B 区间的模型配合量化延迟降到了 600 到 800 毫秒质量损失完全在可接受范围内。这里有个经验延迟和参数量不是线性关系同一个模型在不同量化级别下比如 FP16、INT8、INT4的延迟差异可能是 3 倍以上但质量差异往往只有几个点。第三个问题是数据能不能出域。有些业务场景对数据安全极度敏感所有内容必须留在本地那闭源 API 直接被排除。“超体”的设计里有一部分涉及内部资料分析这部分数据我无论如何不敢用公有云 API 处理。所以从一开始架构上就预留了本地部署的接口用 Ollama 跑量化后的开源模型效果虽然不如云端大模型但胜在完全可控。所以选型从来不是单纯的技术比较而是业务约束下的综合决策数据能否出域是最硬的一条红线。把这三点想清楚之后再去看模型参数、上下文长度、量化格式才有意义。否则就是拿着望远镜找钥匙方向都错了。2. 四套选型参考按场景而不是按名气挑模型“超体”最终不是只接一个模型而是搭了一套可以切换的模型路由层。因为没有任何单一模型能在所有维度上都胜出全都要反而全都不精。我在项目中整理了四套选型参考每一套对应一类具体场景这套框架现在还在用每次接入新模型都按这个套路过一遍。2.1 通用对话与创作类别只看参数大小要看对齐风格通用对话和文案创作这类场景模型的“性格”比“智力”更重要。我实测对比过 Qwen、DeepSeek 和 GLM 系列发现它们在中文创作上的风格差异非常明显。Qwen 系列在指令跟随和结构化输出上表现稳定非常适合需要严谨格式的创作任务DeepSeek 系列的推理能力在中文场景下很能打写分析报告和逻辑推导类内容时明显更有条理GLM 系列在快速对话中响应质量稳定多轮交互不容易跑偏。我在“超体”的对外服务里主用 Qwen 系开源模型原因很简单社区生态成熟从量化到部署再到微调的链路全部打通遇到问题能找到大量现成方案。2.2 结构化抽取与代码生成类格式遵从度是硬指标这一类场景是“超体”最核心的使用场景也最考验模型对格式指令的遵从能力。我用的方法是把一段包含时间、地点、人物的事件文本丢给不同模型要求必须返回 JSON且字段名完全按照我给的模板。结果高下立判。一些通用模型会自作聪明加注释甚至用 Markdown 代码块把 JSON 包起来而针对指令调优过的模型尤其是用大量代码和结构化数据训练过的版本基本能做到零错误输出。代码生成同理CodeGeeX 这类代码专用模型在函数补全上的准确率确实比通用模型高一截但如果是自然语言转 SQL 这类任务通用模型配合写好的 Prompt 反而更稳。2.3 长文档与多模态场景上下文窗口不等于真正理解热词里出现了多模态大模型、知识抽取框架 oneke 这些方向我也都实际试过。长文档分析这类任务模型宣称的上下文长度只是“能接收”的上限不等于“能有效利用”的范围。我实测过 128K 上下文的模型文档超过 40K 之后答案的准确率就开始明显下降模型会漏掉前文的关键细节。所以我在“超体”里对超长文档的处理方式是先用抽取逻辑把文档切片和压缩只把关键段落拼进 Prompt而不是把全文一股脑丢给模型。多模态场景也一样虽然 Qwen-VL 这类模型能看图说话但如果你要分析的是复杂的报表截图把表格结构化之后再让模型处理准确率会高得多。模型的能力边界往往不在参数上而在使用方式上。2.4 本地私有化部署路线Ollama 到底选哪个模型第四个问题对应热搜词里的高频搜索项。我在本地部署上试过 Llama 3、Qwen2.5 7B、Qwen2.5 32B 量化版等也跑过 vLLM 和 llama.cpp 两套推理框架。结论是Ollama 作为本地部署工具最大的优势是开箱即用配置简单到一条命令就能拉模型跑起来适合快速验证和轻量生产环境。但它对并发请求的支持不如 vLLMvLLM 的连续批处理机制能让吞吐量翻几倍适合真正的高并发场景llama.cpp 则更适合资源受限的 CPU 环境尤其配合 GGUF 量化格式可以在没有独立显卡的机器上跑得动。单论“Ollama 本地部署选哪个模型最好”我的答案是如果机器只有 16G 内存就选 Qwen2.5 7B 的 Q4_K_M 量化版本如果有 32G 内存或一张 12G 显存的显卡上 Qwen2.5 14B 或 32B 的量化版效果会有质变。Llama 3 8B 在英文场景下表现不错但中文场景实测不如同量级的 Qwen 系模型。DeepSeek 系列尤其是 R1 蒸馏版在推理任务上有惊喜适合做本地 Agent 的推理核心。切记一件事本地模型的参数量要严格按显存来匹配INT4 量化下 7B 模型大约需要 5 到 6G 显存14B 需要 10 到 12G32B 则至少要 20G 以上显存。硬塞进显存不够的环境系统会退化成 CPU 推理速度慢到怀疑人生。3. Prompt 工程的核心逻辑把提示词当接口来设计而不是当聊天来写选好模型之后真正的考验才开始。Prompt 不是用自然语言“求”模型干活而是用工程化的方式“命令”模型输出。这套思路不转变换再好的模型也白搭。我在“超体”里把 Prompt 当成一个严格的接口规范来设计每个字段都有明确约束不允许模型自由发挥。3.1 系统提示词先把边界和“不能做的事”写清楚很多人写的系统提示词全是“你应该扮演一个专家”“请帮我完成以下任务”这类空洞表述这对模型的约束力几乎为零。我写系统提示词遵循一个原则先立规矩再讲任务。规矩包含三块模型只负责什么、不负责什么、输出必须遵守什么格式。举个例子我在“超体”的事件抽取模块里系统提示词第一段会明确写出“你是一个信息抽取引擎只输出 JSON不输出任何解释性文字、不提问、不道歉、不使用 Markdown 代码块”。这些负面约束非常关键因为模型默认的对话惯性是“礼貌且啰嗦”你不明确禁止它就会不自觉加一句“根据您提供的信息我为您整理如下”。3.2 输出格式约束给出模板就是给出“稳定性”格式约束是 Prompt 工程最重要的一个环节。我始终认为与其让模型自己决定输出结构不如直接给它一个填空模板。具体的做法是在用户消息的最后一段用 XML 标签或者 JSON Schema 示例把期望的输出格式钉死然后加一句“严格按以下结构输出不要修改任何字段名”。比如“超体”里的合同要素抽取我会把输出模板直接贴在提示词末尾字段名、层级、类型全部预先定义好。这样做的好处是只要模型遵循了格式后续程序的解析逻辑就非常稳定不需要写一堆容错正则去匹配模型的各种花式输出。3.3 Few-shot 示例的排列逻辑比数量更重要说完了格式约束另一个高价值手段是 few-shot。给模型的示例不是越多越好而是要“成对”给出坏例和好例。我在早期给“超体”写情感分类 Prompt 时只给了三个正面示例结果模型对边界模糊的输入判断就飘今天判正明天判负。后来改成每组示例都包含一个“负面情况”——比如“用户说‘你们客服真是太好了等了我整整一小时’模型要输出positivefalsereason反讽语气识别”。这样模型就学会了分辨隐含情感和讽刺准确率明显提升。示例的另一个关键点是排列逻辑正例在前、反例在后并且标注原因模型才能从示例中捕获分类规则而不只是模仿示例的字面模式。3.4 采样参数temperature 和 top_p 是稳定输出的最后一道闸提示词写得再好如果采样参数设置不对输出照样飘。这里有个经验值直接抄作业凡是做抽取、分类、格式化输出这类确定性任务temperature0是首选必要时可以设到 0.1 以下目的是让模型每次都选概率最高的 token输出基本稳定凡是做文案创作、头脑风暴这类发散性任务temperature设在 0.7 到 0.9 之间给模型更多发挥空间。top_p对应核采样一般保持 0.9 左右即可不需要动太多。如果你发现同样的输入模型两次输出差异很大先检查是不是把 temperature 设高了而不是怀疑 Prompt 写得不够好。我在“超体”中就踩过这个坑一度以为是模型抽风后来发现是开发环境默认配置把温度拉到了 0.95整整一周的测试数据全部不可用。4. 稳定性翻车现场三类最常见问题的排查与修复即使前面全做对了真实上线后还是会遇到模型不稳定的情况。我梳理了三类“超体”里出现频率最高的翻车场景每一个都附上完整的排查链路和最终的解决方案。4.1 格式漂移模型某一天突然不按格式输出了现象是最让人头疼的同一个模型、同一个 Prompt前几天输出一直是干净的 JSON今天突然开始在前面加一段“好的这是您需要的答案”。排查的顺序是这样的先查前端传参有没有变化再查模型服务端的参数配置尤其是 temperature 是不是被某个依赖库偷偷改掉了最后查 Prompt 模板在代码迭代中有没有被改动格式比如空格缩进变了、字段名大小写变了。我实际遇到的根因是 Prompt 模板里一个字段名的引号被翻译工具转换成了全角字符导致模型解析错乱。解决办法是给模板写自动化测试每次改动后用一组固定样本跑一遍输出格式校验你有任何模板变更都先过这组回归测试再上线。4.2 内容幻觉模型一本正经地编造不存在的知识“超体”的文档问答场景最开始幻觉很严重问一个文档里没提的问题模型会基于训练数据脑补一个合理的答案。处理思路不是单纯靠提示词压制而是给模型一个“不知道”的出口。我在 Prompt 里明确写了一条“如果问题内容在提供文档中找不到出处请直接回答‘未找到相关信息’不要尝试推测或编造。”同时在程序的检索层做兜底先把用户的提问和文档片段做召回匹配如果召回结果的相关度分数低于阈值就不把这些内容放进上下文。模型没有依据可编自然就不会胡说了。这个方法上线后幻觉率至少下降了一半。4.3 长度失控回答突然变得冗长或者戛然而止长回答的问题容易出现在max_tokens设置和stop条件上。我在“超体”里遇到过生成的回答超过设定的最大 token 数被硬生生截断最后半个句子都没有了。这就是把max_tokens设得太小造成的。解决办法不是单纯把上限调大而是把 Prompt 里的输出结构改成“先给结论再给理由”的顺序让最重要的信息排在前面。同时在 Prompt 里明确字符上限写“回答控制在 200 字以内超过 200 字的部分会被截断请确保结论在前 100 字内完整表达”。这也是一种提示词约束。如果你用 vLLM 部署还可以用--stop参数指定停止词比如遇到\n\n就停止生成配合结构化 Prompt 能有效防止模型无休止地“发挥”。5. 接得住才算完SSE 流式输出、Abort 中断与前端渲染的工程细节模型输出稳定之后还有一个经常被忽略的问题怎么把内容实时、可中断地送到用户界面上。现在的大模型应用不是等全部生成完一次性展示而是打字机效果一个 token 一个 token 往外蹦。这就涉及流式输出的工程实现也是热搜词里反复出现的“SSE 流式输出”和“AbortController”这两个词背后的真正含义。5.1 为什么选 SSE 而不是 WebSocket很多大模型 API 都支持流式返回技术选型上主要有 SSE 和 WebSocket 两条路。SSE 是单向的客户端发一次请求服务端持续推送文本数据直到结束WebSocket 是双向的适合需要实时交互的场景。在“超体”的业务里用户问一句、AI 回一段本质是“一问一答”的请求响应模式用 WebSocket 等于杀鸡用牛刀还得处理连接状态和消息协议。SSE 用普通的 HTTP 请求就能跑天然支持浏览器原生 EventSource API服务端逻辑也简单很多。我用下来最大的感受是SSE 的调试成本极低浏览器 DevTools 里直接能看到文本流出问题一眼就定位了。5.2 前端解析 SSE 的轻量实现浏览器原生的 EventSource 有个限制不支持自定义 Header比如 Authorization 请求头。如果你的 API 走的是鉴权模式EventSource 就没法用了。这时候只能用 fetch ReadableStream 自己解析。我在这里分享一段“超体”中实际在用的简化代码const response await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${token} }, body: JSON.stringify({ prompt: userInput, stream: true }) }); const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop(); // 最后一段可能不完整留到下一轮 for (const line of lines) { const trimmed line.trim(); if (!trimmed.startsWith(data:)) continue; const data trimmed.slice(5).trim(); if (data [DONE]) { /* 流结束 */ break; } const parsed JSON.parse(data); renderToken(parsed.choices[0].delta.content ?? ); } }这段代码的核心是解决“数据包被 TCP 分包”的问题。SSE 的消息是按\n\n分隔的但网络传输不会按你的消息边界整齐切开所以必须用一个 buffer 累积字符串每次只取完整的行不完整的留到下一次读取。这是流式解析最容易踩的坑很多新手直接把数据按行 split然后发现 JSON.parse 一直报错。5.3 AbortController用户点“停止”时到底发生了什么流式生成的另一个痛点是如何支持“停止生成”。大模型生成过程很耗时用户如果发现答案不对不可能等它全部跑完。AbortController 就是干这个的。用法是把 controller 传给 fetch用户点击停止时调用controller.abort()浏览器就会立即中断这次请求。但这里面有个细节服务端收到中断信号后如果生成逻辑没做取消处理模型的推理还会在后台继续算白白占用 GPU 资源。我在“超体”里做了两层处理前端abort()中断网络请求后端通过监听请求的取消事件调用 vLLM 或 Ollama 的stop()方法终止生成。前端伪代码const controller new AbortController(); /** 点击“停止”按钮时调用 */ function stopGeneration() { controller.abort(); }如果用的是 OpenAI 官方的 Node SDKAbortSignal.timeout(5000)还能直接传 abortsignal 参数原理完全一样。有一点需要留意Abort 之后reader.read()会抛一个 AbortError必须用 try/catch 包住否则控制台会刷红色报错用户也会看到异常的弹窗。5.4 心跳与断线重连的兜底策略流式请求还有一个现实问题如果生成过程中网络断了或者服务端超时用户界面会卡在一个死循环里既没有输出结束也没报错。我加了一个 90 秒无数据心跳检测如果从最后一次拿到数据开始计时超过 90 秒没有新内容就认为连接异常主动执行abort()并提示用户“生成超时请重试”。另外在 UI 层做好状态机设计把「生成中」「已结束」「已中断」「错误」四个状态分开任何异常状态都让用户能通过“重试”按钮恢复而不是卡死页面。这块虽然没那么“AI”却在真实体验里决定了整个应用的可靠程度不能省。6. 兜底策略稳定性不是靠某个模型赢来的是靠架构设计保出来的最后一个关键认知是模型的输出天然带有随机性再好的 Prompt 也不能保证 100% 稳定。真正让系统稳定的是围绕模型设计的一整套兜底策略。我在“超体”里做了三层防护每一层都是在实际运行后被逼出来的。第一层是多模型路由与降级。主模型挂了或者输出质量异常系统自动切换到备用模型用户几乎无感知。我按“成本从低到高、质量从低到高”排了一个模型链小模型先跑如果输出格式校验不通过自动升级到大模型重试一次。实测中这个降级逻辑把整体可用率从 92% 提到了 99.5% 以上。第二层是结果质量评估。拿到模型的输出后先用程序做基础校验比如 JSON 能不能解析、必填字段是否存在、枚举值是否合法。校验不通过就触发重试或降级校验通过的数据才进入业务逻辑。这一层是纯规则判断零成本却挡住了大量脏数据。有些团队把这个做成了更复杂的“模型裁判”用一个模型去评估另一个模型的输出效果更好但成本也成倍增加我建议先上规则校验再按需叠加模型评估。第三层是版本管理与回归测试。每次更换模型、升级 Prompt 或修改参数前都跑一遍固定的回归测试集。我维护了一个大约 80 个 case 的测试集覆盖“超体”里的所有核心场景包括正常输入、边界输入、格式干扰、幻觉诱发等。模型选型不再凭感觉而是用测试集通过率说话。这相当于给模型能力上了一把刻度尺每次改动都能量化它到底是变好了还是变坏了。第四层是从 Prompt 到微调的演进。当你说“让大模型稳定输出高质量结果”时要考虑的问题不是用 Prompt 逼模型做到什么而是模型本身是否具备这个能力。我在“超体”里遇到过一个场景需要模型按照特定的行业术语和报告格式输出用 Prompt 怎么强调都不够稳定。后来用 Qwen2.5-7B 在几百条真实语料上做了一个轻量微调问题迎刃而解。微调不是为了替代 Prompt而是让模型天生就适应你的任务。你如果手里有足够的高质量标注数据微调是跨越稳定性瓶颈的最有效方式。微调之后的模型再用 Prompt 控制细节稳定性的上限会高很多。再补充一个从上海交大“动手学大模型”项目里体验到的核心观点学习大模型应用开发最重要的不是背 API而是理解“输入输出”这两端的工程化能力。模型是发动机Prompt 是方向盘工程的兜底策略是安全气囊。只有三者配合你的应用才算真正达到了可上线的稳定标准。我在“超体”里反复改过模型、调过提示词也在生产环境里处理过各种掉链子的突发事故。每次复盘都会发现问题的根源从来不在某一个环节而是整个链条上缺了一个约束。现在你手里的这套思路和方法是我用无数个加班夜晚换来的。最后提醒一句模型榜单上的分数可以给你方向参考但真正决定成败的是你选的模型、写的 Prompt 和兜底架构这三者的匹配度。别迷信任何一个“最强模型”也别指望一段神奇提示词解决所有问题老老实实把每个环节都做成可验证的工程输出的稳定性自然就有了保障。
返回列表