1. 项目概述:什么才算“简单至上”的大模型应用
1.1 这个指南是什么,写给谁看
最近半年我帮几个团队做过大模型应用的技术评审,发现一个共性:很多人不是被技术难点卡住的,是被过度设计拖垮的。一拿到大模型的能力,第一反应就是上Agent框架、上向量库、上消息队列、上微服务,结果调用链长到自己都看不清,出了问题排查半天,成本还高得吓人。
这篇文章我不想讲那些花哨的东西,核心就一个原则:让事情简单,让系统稳定,让成本可控。写给谁看?两类人。第一类是想把大模型接进自己业务、但还没完全跑通的开发者,你可以把它当成一份能直接照着做的落地手册。第二类是已经跑通、但总觉得系统太脆、一上线就出幺蛾子的开发者,你可以重点看第4章的工程化加固和第5章的排查技巧。
我用的技术栈是Python加常规Web框架,市面上主流的大模型厂商SDK基本都支持这种方式。文中的代码不会依赖某个特定平台,你只需要把模型名和密钥换成自己实际使用的,就能原样跑起来。
1.2 为什么要“简单至上”:过度设计的代价
我见过一个让我印象深刻的案例。某团队要做一个文档问答机器人,最初的架构是:文档切块 → 向量化 → 存入向量库 → 用检索增强生成(RAG)框架 → 接Agent调度 → 再挂一层缓存。听起来很完整对吧?结果呢,开发周期从预期的一周拖到了一个月,中间光是向量库的选型和运维就反复折腾了三次。最后上线一看,用户问的都是“合同里违约金比例是多少”这种高频问题,很多答案检索模块压根没召回,反而直接问大模型,回答得又快又准。
这个案例不是否定RAG或Agent的价值,而是想说明一个道理:**你的业务复杂度决定了方案的复杂度,而不是方案本身决定了业务的高度。**大多数场景下,用户问题就在提示词能覆盖的范围内,模型原生能力已经够用了。你优先要做的,是把最简单的方案跑通,让业务真实运转起来,再根据数据反馈决定要不要加复杂度。
而且过度设计有个隐藏成本:每一层中间件都意味着一个故障点。检索召回错了、缓存过期策略没写好、Agent工具调用格式变了,任何一个环节出问题,最终用户看到的都是“机器人变笨了”。排查链路越长,定位问题的时间就越长。稳定高效的第一性原理,就是尽可能减少链条上的环节。
简单设计还意味着更快的迭代速度。大模型技术迭代非常快,模型能力不断升级,提示词也需要频繁调整。你架构越复杂,改一次提示词要动的代码就越多。反过来,如果核心逻辑就一个函数加一个提示词配置,改起来十分钟搞定,灰度上线也是分钟级的事。
1.3 稳定高效的三个核心原则
基于踩过的大量坑,我总结出大模型应用“稳定高效”的三个核心原则,可以用三个词概括:可观测、可降级、可回滚。
可观测指在所有关键节点埋好日志。大模型应用的输入输出是不可控的,你必须能看到每一次请求的完整链路:用户问了什么、系统传了什么上下文、模型返回了什么、最终返回给用户什么。我见过太多团队上线后只打了一行“调用成功”的日志,模型返回异常内容时根本不知道是提示词的问题还是上游参数的问题。
可降级指在模型服务不稳定或成本超预期时,系统有明确的降级路径。比如短时间内的重复请求直接走缓存,大模型服务超时后回复预设兜底文案。这套机制不复杂,但必须在第一天就设计进去,而不是等线上事故了再补。
可回滚指提示词和配置的改动可以被快速回退。很多团队用Git管理代码,但提示词却随手改在线上配置中心甚至硬编码里,这属于定时炸弹。提示词和代码同等重要,它的版本管理一点也不能含糊。
这三个原则贯彻到位,你的应用即使体量不大,稳定性也能超过很多用复杂架构堆出来的系统。接下来我从底层机制开始拆解,再给出一套可以直接落地的实操方案。
2. 核心细节解析:先把底层机制吃透
2.1 提示词是应用的第一行代码
很多开发者把大模型应用开发理解成“写代码调用API”,这是很大的误区。大模型应用里,提示词就是产品逻辑本身。你的业务规则、输出格式、语气风格、边界条件,全部通过提示词来约束。代码只是把提示词送到模型那里,再把结果拿回来。
我打个比方。传统开发里,你写一个函数,参数校验、异常处理、返回值结构都是代码控制的。但大模型应用里,这些控制逻辑全部要靠提示词来表达。你写“请提取合同中的甲方、乙方、金额”,模型就可能返回各种格式的结果;你写“请提取合同中的甲方、乙方、金额,以JSON格式返回,格式为{...}”,模型大概率会老实配合。
这意味着写提示词不能像写说明书一样随意。我见过不少团队用自然语言写了一大段描述,结果模型理解不到位,又不敢动代码,只能在提示词里反复做加法,越加越长,最后连模型自己都分不清重点。正确的做法是:规则前置、示例跟上、格式明确。
这里有个很多文章没讲透的点:提示词里同时包含“历史消息”和“系统指令”时要格外注意。模型对越靠后的信息权重越大,你把核心业务规则放在用户问题之后,它的注意力会被分散。实操中我习惯把系统提示词放在最前面,业务规则写清楚,示例放中间,用户输入放最后,这样模型能够同时兼顾指令和输入。
提示词写好之后,一定要固化版本。后面章节我会专门讲版本管理的做法,这里先说一句:不要在产品早期频繁大幅调整提示词,每次改动都要记录前后差异并做对比测试,否则模型表现变了,你根本不知道是哪个改动引起的。
2.2 上下文窗口是稀缺资源
大模型的上下文窗口是有限资源,这是所有大模型应用都必须面对的核心约束。很多人刚接触时以为上下文窗口等于“我能塞多少历史对话进去”,实际上它同时受三个因素控制:输入Token数、输出Token数、模型内部处理的Token数。
我举个实际例子。某模型的上下文窗口是32K Token,换算成汉字大约是两万多字,听起来不少。但你想想:一份合同原文可能就有五千字,加上系统提示词、历史对话摘要、用户新问题、模型要输出的完整结果,一轮问答下来,32K窗口至少要用掉一半。如果你做的是多轮对话,几十轮之后,光是历史消息就可能把窗口撑爆。
处理这个问题,最简单的方案有两个。第一个是只保留最近N轮对话,超过的部分直接截断。这个方案代码量极少,适合多数客服、问答类场景。第二个是做一轮摘要,把之前的对话浓缩成几句话塞进系统提示词,再附带最近的几轮原始对话。我自己做项目时,通常先上方案一,等用户反馈确实需要跨轮次记忆时再上方案二。还是那句话,先简单,再演进。
很多人一提到上下文管理,第一反应是“用向量库做长期记忆”,这是第二个典型过度设计。向量库适合处理“从海量文本里检索相关信息”的场景,比如文档问答、知识库检索。但如果你只是想要“记住用户上次说了什么”,用一个字典或数据库表就能解决,根本不需要相似度检索。
2.3 输出格式的稳定性
大模型应用开发中,最常踩的坑就是输出格式不稳定。你让模型返回一段JSON,它偶尔会在JSON前后加一句解释,偶尔会用单引号,偶尔把值改成字符串。规则简单时问题不大,规则复杂时,下游代码直接因为你没做格式校验而崩溃。
我推荐的实操方案是三层保障。
第一层,在提示词里明确指定输出格式,并给出完整的示例。模型对示例的模仿能力很强,给出带类型说明的JSON示例比空泛描述管用得多。比如你要求返回一个包含姓名和年龄的对象,提示词里就要写“返回格式:{“name”: “张三”, “age”: 30}”,而不是“请返回JSON格式的数据”。
第二层,在代码中做严格的格式校验。解析JSON时检查关键字段是否存在、类型是否正确,不合格就触发重试。这里有一个关键细节:**重试时不要原样重发提示词。**模型在同样输入下很容易给出同样的坏结果,你要在提示词里加一句“上次返回的JSON格式不正确,请严格按照示例格式重新输出”。
第三层,对于必须百分百结构化的场景,使用模型厂商提供的结构化输出或JSON模式能力。现在主流厂商基本都支持限定输出JSON格式的参数,开启之后模型会尽力保证输出是合法JSON。不过依然建议保留代码层面的校验,因为格式落地到具体业务规则时,模型仍可能给出语义不合法的值。
2.4 温度与Token限制的关键参数
大模型API调用会涉及几个参数,很多人直接沿用默认值,不做调整。实际上这些参数对应用表现影响非常直接。
最常用的是温度(temperature)。它控制输出的随机性,取值范围通常是0到2,数值越高回答越发散,越低越保守。我的经验是:做信息抽取、格式化输出、客服回答这类追求确定性的任务,温度直接调到0.1到0.3;做头脑风暴、文案创意这类需要多样性的任务,才调高到0.8以上。还有多个厂商提供top_p参数(核采样),作用和温度有点重叠,二选一调整即可,两个都调容易互相干扰。
另一个重要参数是max_tokens,也叫max_output_tokens,控制生成的最大长度。这个参数一定要按场景显式设置。不设置的后果是输出可能会被截断,你拿到的内容戛然而止,下游却以为是完整结果。设置时要注意,它只限制输出Token,不影响输入,同时输入加输出不能超过模型的上下文上限。
再把超时设置单独说一点。大模型推理不像普通API那么快,复杂任务跑几十秒都很正常。但应用层的超时时间不能因此放开,必须设定一个合理值。我一般设60秒,如果模型服务商有流式接口,优先用流式,这样用户可以边等边看,体验好很多,也方便做更精细的超时和中断控制。
3. 实操过程:从零搭建一个稳定的大模型应用
3.1 环境准备与依赖管理
先交代一下我自己项目里的环境配置。语言使用Python 3.10以上版本,包管理使用uv或pip配合虚拟环境。这一步别偷懒,直接在系统Python里装依赖后面会后悔,不同项目之间依赖版本冲突的时候很让人头疼。
依赖安装原则是能少则少。只安装官方SDK、Web框架、环境变量管理库这几样,其他工具边界需要用到时再装,不要提前装一堆有可能用到的库。
密钥管理方面,建议把API密钥写入环境变量或专门的配置文件,并用.gitignore排除。我看到不少新手直接把密钥硬编码在源码里,然后上传到公开仓库,这是安全事故高发区。正确的做法是启动时从环境变量读取,没有密钥就快速失败,而不是让它带病运行。
3.2 最小的完整调用:几行代码跑通
我们先把最小可用的调用流程跑起来。假设用的是某家主流的大模型对话API,拿到密钥后,用官方Python SDK写一个最简单的调用:
import os from llm_sdk import Client # 从环境变量读取密钥 client = Client(api_key=os.getenv("LLM_API_KEY")) response = client.chat.completions.create( model="your-model-name", messages=[ {"role": "system", "content": "你是一个严谨的助手,回答尽量简洁。"}, {"role": "user", "content": "用三句话解释为什么数据库需要索引。"} ], temperature=0.3, max_tokens=500, ) print(response.choices[0].message.content)这段代码本身没什么可说的,但有几个工程细节值得展开。
第一,模型名要写配置文件里,不要散落在代码里。因为模型会持续更新,你的应用要在不发布版本的情况下快速切换模型。用一个简单的配置字典或环境变量,线上应急要换模型时,改一行重启即可。
第二,messages列表里必须有清晰的系统角色和用户角色。很多人图省事只传用户消息,结果模型经常表现得像“没有灵魂的复读机”。系统角色是设定模型行为的核心位置,一个写好的系统提示词能让后续所有用户消息的表现质量明显提升。
第三,max_tokens要显式设置。上面设了500,意味着模型最多输出500 Token。如果任务比较复杂,这个值会被用满,你会拿到一个被截断的回复。实际开发中要根据任务的输出规模评估,拿捏不准时可以先设1000跑几个样例,观察返回内容末尾是否自然结束。
跑通基础调用后,把它封装成一个函数。函数只干两件事:接收messages列表,返回模型输出文本。这层封装把“调API”和“业务逻辑”隔离开,后续不管是加缓存、加日志,还是换模型供应商,改这一个函数就够了。
3.3 多轮对话的会话管理
多轮对话是大模型应用最常见的形态之一。很多人第一次写聊天机器人,思路是把用户所有历史消息一股脑全塞进messages,结果没聊几轮就超过上下文限制。
我从实践中总结出的方案比较直接。用一个Python字典在内存中维护会话历史,每个会话有一个唯一ID,字典的值是一个消息列表。每次轮询收到新用户消息时,把用户消息追加进去,再把整个列表发给模型,最后把模型回复也追加进去。
sessions = {} def chat(session_id: str, user_message: str) -> str: # 会话不存在时初始化系统提示词 if session_id not in sessions: sessions[session_id] = [ {"role": "system", "content": SYSTEM_PROMPT} ] # 追加用户消息 sessions[session_id].append({"role": "user", "content": user_message}) # 做上下文截断:只保留最近10轮 history = sessions[session_id] if len(history) > 21: # 1条系统 + 20条对话 session[session_id] = [history[0]] + history[-20:] response = call_llm(sessions[session_id]) # 追加模型回复 sessions[session_id].append({"role": "assistant", "content": response}) return response注意上面代码里的截断策略,这在生产环境里非常重要,能防止会话无限膨胀。
这里我必须提醒一个关键误区:**模型不会记录会话状态,它只看到你传给它的消息列表。**所以你在代码里做的任何截断、摘要、归档,都会直接影响模型对“之前聊了什么”的认知。截断了用户上一轮说过的关键信息,模型就会“失忆”。所以简单截断适合信息密度低的闲聊场景;对于严谨的任务型对话,更合适的是做摘要。
摘要的做法是:当历史超过阈值时,用一个独立调用让模型把旧对话压缩为几句话,再作为一条“system”或“user”消息放进上下文。这样模型能保留核心信息,又不会让旧对话占满窗口。
内存方案适合单机、小流量场景。如果应用需要多实例部署,必须把会话存到外部存储中,常见的方案是Redis,键用session_id,值用序列化后的消息列表。做法不复杂,但架构逼着你升级时再动手,过早引入Redis,除了增加维护成本,对项目没有实质帮助。
3.4 流式输出的实现与体验优化
大模型响应通常需要数秒才能完整返回。如果让用户干等一个白屏,体验非常差。流式输出就是解决这个问题的。
所谓流式,就是模型一边生成一边把内容返回给你,你一边接收一边渲染到前端。用户看到的效果是“字一个个蹦出来”,体感快很多,也让排队等待显得不至于太漫长。
流式调用在API层面的写法很简单,主要是在create里加一个stream=True参数,然后把返回值改成遍历方式:
def call_llm_stream(messages: list): response = client.chat.completions.create( model="your-model-name", messages=messages, stream=True, temperature=0.3, max_tokens=500, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: yield delta上游拿到生成器后,用SSE(Server-Sent Events)或WebSocket推给前端。这里优先推荐SSE,协议更简单,浏览器原生支持,不用引入额外库,在Text/EventStream标准下兼容性非常好。如果用WebSocket,就要考虑心跳、断线重连、连接池管理等一堆事务,属于纯粹给自己增加工作量。
流式模式下超时策略要重新设计。因为接口已经“开始返回”,常规的完整请求超时没有意义了。你需要关注的是“两次数据块之间的间隔时间”,比如设定20秒没有新数据就算超时,主动断开连接并提示用户。
流式模式下错误处理也要注意,模型可能在输出了一部分后被截断或被服务商中止。前端要能感知到“流是否正常结束”,这可以通过特殊标识符来表示结束状态。前端收到异常结束信号时,要提示用户重试或告知网络异常,避免用户以为AI已经把话说完了。
3.5 输出校验与后处理
模型返回的内容,不能直接拿来就用。我从真实项目中总结出的经验是:先校验,后转义,再落库。
校验指的是格式校验和业务规则校验。如果功能要求返回JSON,就在调用后马上解析,解析失败就进入重试逻辑。业务规则校验更严格,比如做合同审核应用,要求模型返回“违约金比例是否合理”的判断结论,这个结论必须符合合同的实际情况。我的做法是把关键事实单独要求模型在JSON里列出(比如“合同原文中关于违约金的原文是:...”),代码再针对这些事实做二次确认。这一招虽然稍有额外耗时,但能过滤掉相当一部分“幻觉输出”。
转义是很多人会忽略的点。大模型输出可能包含Markdown符号、HTML标签、特殊Unicode字符。当你要把结果渲染到网页时,一定要确认前端框架对这类内容做了防XSS处理,否则模型输出可能成为安全漏洞。稳妥起见,我在后端对输出内容做一次HTML实体转义,再交给前端渲染,这样可以彻底杜绝脚本注入类风险。
后处理还包括格式规范化。同一个问题,模型可能在某次对话里返回“2.5万元”,下一次返回“25000元”。这在文本展示时没问题,但如果你要对金额做运算,就必须让模型在JSON里返回纯数字,展示时再加单位。这属于提示词设计层面的约束,我在第2章提到过:示例要给得足够明确,让模型知道哪种格式才算合规。
4. 工程化加固:让“能用”变成“稳定”
4.1 超时、重试与降级
先别追求花哨的架构,把基础的三件事做扎实:超时、重试、降级。
超时:每次调用都必须设置超时时间。连接超时建议5到10秒,读取超时根据任务复杂程度从30秒到120秒不等。流式模式下改为“块间隔超时”。没有超时,就意味着当模型服务异常时你的应用也会跟着挂起,所有请求线程堆积,最终拖垮整个服务。
重试:遇到网络抖动、服务端返回5xx错误或限流提示时,应该重试。但重试不是无脑重跑,我采用指数退避策略:第一次重试等2秒、第二次等4秒、第三次等8秒,最多三次。指数退避给服务端留出恢复时间,也避免你的请求因为太密集被继续限流。
import time from functools import wraps def retry_with_backoff(func, retries=3, base_delay=2): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(retries): try: return func(*args, **kwargs) except Exception as e: if attempt == retries - 1: raise delay = base_delay * (2 ** attempt) time.sleep(delay) return wrapper重试有个要注意的副作用:如果模型真的生成了一次结果,但网络断在返回途中,你重试时模型会重新生成一次,等于多花一次钱。幂等性设计在这里不追求“不重复生成”,而是接受“重复但正确”的结果。口语化说,就是钱可以多花,但业务不能错。
降级:降级策略我分为两级。一级降级是短时缓存兜底,把最近N个常见问题的答案存起来,模型调用失败时直接返回最近一次的成功答案。这个对客服类场景特别有效,因为用户问的问题高度重复。二级降级是固定文案兜底,当所有办法都失败时,返回“系统繁忙,请稍后再试”这类提示,至少用户感受到的是“服务暂时不可用”,不是“程序崩溃了”。
4.2 日志、追踪与成本记账
我为大模型应用专门设计了一套日志规范,核心是记录“四个W”:Who(谁在问)、What(问了什么)、When(什么时候)、What-returned(模型返回了什么)。每一行日志都要把这四个信息完整记录。排查问题时,只要有这套日志,我就敢说80%的问题能在五分钟内定位。
日志里还应该包含一个请求ID(request_id),它贯穿整个调用链。你的应用有日志、模型服务端有日志、如果将来引入缓存也应有日志,通过request_id把三者串起来,能还原完整链路。
成本记账是我强烈建议从第一天就做的事情。原因是大模型的计费是“用后才知”,按Token计费,没有清清楚楚的记录,月底对账单时你会很被动。我在项目里会额外记录每次请求的prompt_tokens和completion_tokens,累计到一张表里,定期统计。
| 日期 | 会话 | 输入Token | 输出Token | 费用(估算) |
|---|---|---|---|---|
| 2024-01-10 | 合同问答 | 3200 | 850 | 0.012元 |
| 2024-01-10 | 合同问答 | 4100 | 1200 | 0.016元 |
有了这张表,你能立刻发现哪个功能成本异常。比如某天某会话的输入Token突然从几万涨到几十万,说明上下文截断逻辑可能失效了,趁早修,比月底看账单再后悔要强得多。
还要养成定期检查模型跑偏的习惯。我给团队定的规矩是:每周抽样10%的日志,人工看模型输出是否符合预期。这听起来很原始,但对付“概率性错误”特别有效。模型的表现是概率性的,代码没改、提示词没动,理论上不该变,但线上数据分布一变化,模型的表现就可能漂移。定期人工抽检是发现漂移最直接的方法。
4.3 提示词与配置的版本管理
提示词改动的风险被很多团队低估了。你可能只是把“你是一个助手”改成“你是一个专业的助手”,但模型在业务场景里的输出风格就全变了,甚至会影响格式稳定性。
我的做法是:提示词和代码一起走版本管理。项目根目录建一个prompts目录,每个功能一个文件,比如chat_system.txt、extract_contract.json。这些文件纳入Git,改动时提交commit并写清楚改动原因。部署时,配置文件随代码一起发布。
如果你需要线上改提示词不经过发布流程,可以引入配置中心或数据库存储提示词。但这样做的代价是提示词脱离版本控制,改挂了不能快速回滚。我个人的建议是:项目早期别这么干,就让提示词跟着代码走。等团队规模大了、提示词迭代频率高了,再上配置管理平台。
提示词改动后一定要跑回归测试。我会为每个核心功能维护一个测试用例集,大约三五十条问题,覆盖正常、边界、刁难三种类型。改完提示词,先跑一遍用例集,对比输出差异。实测下来,这一步能拦截70%以上的回归问题。很多人觉得麻烦,认为是浪费时间,实际上它在帮你省更大的麻烦。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我把线上遇到的高频问题整理成了速查表,方便你对照排查。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 模型返回内容被截断 | max_tokens设置过小 | 检查返回结果是否有结束标识,调大max_tokens |
| 同一请求结果不稳定 | temperature过高 | 将temperature降到0.3以下 |
| 返回JSON解析失败 | 提示词格式约束不足 | 增加JSON示例,开启结构化输出 |
| 请求超时 | 模型负载高或网络问题 | 检查读取超时设置有误,启用指数退避重试 |
| 成本异常飙升 | 上下文未截断或重试机制无上限 | 查询日志确认输入Token数量,修正截断逻辑 |
| 用户说“AI忘了以前说的内容” | 会话历史被截断 | 对关键信息做摘要,或扩大保留轮数 |
| 模型答非所问 | 系统提示词太弱 | 强化系统提示词,明确任务边界和回答范围 |
这张表不是让你背下来,而是提醒你:**排查任何大模型问题,都要从“输入到底长什么样”开始。**很多人一发现问题就怀疑模型傻了,其实95%的情况是输入数据没有按预想传给模型。
5.2 一个概率性问题的排查案例
有次我负责的客服问答机器人突然出现一批“答非所问”的情况,用户问退换货政策,机器人却回答物流速度。复现时大部分请求是正常的,只有约20%会跑偏,持续了好几天才被业务方反馈。
我先查了日志,发现跑偏的请求中用户消息都没有问题。我开始怀疑提示词在上次改动中引入了歧义,翻看了最近一次提示词变更记录,发现只是把“请回答用户问题”改成了“请准确回答用户问题”。按说改动很小,不应该造成这么大影响。
于是我开始逐个逆推参数。先检查温度,线上配置的是0.7,对客服场景来说太高了,随机性大,容易选到不相关的知识片段。我把温度降到0.2,再跑同样的测试集,跑偏比例从20%降到2%。
后续又发现一个麻烦:部分跑偏集中在每天的高峰时段。进一步排查,模型服务在高峰时响应变慢,而我们的超时设置只有20秒。一旦被迫重试,模型在重试时拿到的是同一批消息,生成的文本可能更不稳定。我做了两个调整:把超时放宽到45秒,同时给会话加上“上下文截止”策略,高峰期间自动过滤掉过于久远的历史消息,减轻模型处理负担。这两个调整落地后,跑偏比例降到了可忽略的水平。
这次排查给我一个刻骨铭心的经验:**概率性问题往往是多个小问题叠加的结果,单一变量分析不完全靠谱。**温度、超时、上下文长度、模型负载都可能是诱因,必须组合排查,而不是只盯着一个参数。
5.3 踩坑心得汇总
最后整理几条我反复踩过的坑,希望你在一开始就避开。
第一个坑是过度相信模型的“自我修正”能力。模型会在对话里承认错误并道歉,但它的下一次输出不保证就修正了。你需要的是强制性的规则约束,而不是口头约定。该做格式校验就做校验,该写日志就写日志,把“让它守规矩”的责任放在代码里,而不是寄希望于模型心情好。
第二个坑是忽略输入侧的异常数据。大模型应用调试时,我花在“检查输入是否正确组织成messages”上的时间,比调提示词的时间还多。用户传来的内容可能要清洗,比如去掉超长文本、敏感词过滤、统一编码。输入脏,输出必然脏,这是铁律,也是很多人定位问题时的盲区。
第三个坑是一开始就把应用做成“什么都干”的超级助手。大模型能力很强,但应用边界必须清晰。你要决定哪些问题它负责回答,哪些问题它礼貌拒绝。边界可以通过系统提示词约束,比如写“当用户问超出本系统知识范围的问题时,回复:该问题超出我的能力范围”。没有边界,你的应用迟早会被用户问出各种奇怪的“越狱”操作,稳定性无从谈起。
把上面的坑都避开了,你的大模型应用再差也能稳定跑起来。不过话说回来,“稳定”不是一次性的目标,而是持续迭代的结果。模型服务商会更新模型版本、你的业务数据会变化、用户的使用习惯也在变,每隔一段时间回归一遍核心逻辑,检查一遍成本账单,是必须做的功课。
最后分享一个我目前还在用的习惯:每次部署大模型相关的新功能,我会先拿一套固定的测试问题在小范围内跑一遍,确认输出正常再全量放开。这个看起来笨拙的操作,帮我挡掉了好多次模型版本切换带来的线上事故。你也试试,真的管用。