大模型应用开发这件事,我从2023年下半年开始密集投入,到现在经手过企业内部知识库、智能客服、代码辅助工具、文档审阅助手这几类项目,踩过的坑比写过的代码还多。很多人以为"LLM应用开发"就是调个API、拼个提示词,真上手才发现,从模型选型、上下文管理、成本控制到效果评估,每一步都有大量工程决策要做。这篇文章不讲空泛的概念,只讲我在实际项目里验证过的路径、参数和教训,适合已经了解大模型基础、准备或正在做应用落地的开发者,也适合想搞清楚"LLM应用到底怎么开发"的产品和技术负责人。
1. 先想清楚:LLM应用开发和传统软件开发到底差在哪
1.1 确定性逻辑与非确定性输出的根本冲突
传统软件开发的底层假设是:同样的输入,经过确定的代码逻辑,得到同样的输出。我们写单元测试、做回归验证,都建立在这个假设上。但LLM应用打破了这个假设——同一个问题,模型两次回答可能措辞不同、结构不同,甚至结论有细微差异。
这个差异带来的连锁反应比想象中大得多。传统开发里,一个函数返回错误码,你加个if判断就能处理;LLM应用里,模型返回一段自然语言,你得先判断它"意图对不对",再判断"内容对不对",最后还要判断"格式对不对"。三层判断,每层都可能出问题。
我在做第一个企业知识库项目时就吃了这个亏。当时设计流程是:用户提问→检索相关文档→拼接提示词→模型生成回答。测试阶段一切正常,上线后用户问了一个检索结果为空的问题,模型直接开始"编造"答案,因为提示词里没有明确告诉它"检索不到就说不知道"。这就是典型的用确定性思维做非确定性系统。
应对这个问题的核心思路是:把LLM当作一个能力很强但不可靠的组件,在它前后加足够多的确定性护栏。具体来说:
- 输入侧:做意图分类和参数校验,不合法请求直接拦截,不进模型
- 检索侧:设置相似度阈值,低于阈值的结果不传给模型
- 提示词侧:明确约束输出格式和边界条件
- 输出侧:做格式校验和内容过滤,不合格的走兜底逻辑
1.2 上下文窗口不是越大越好
很多人选模型第一眼看上下文长度,觉得128K肯定比32K好。实际项目里,上下文长度和效果、成本、延迟是三角制约关系。
我做过一组对比测试,同一个文档问答任务,分别用4K、16K、64K上下文窗口跑。结果是:16K的效果最好,4K因为截断丢失关键信息导致准确率下降,64K反而因为"中间遗忘"现象(模型对长上下文中间部分的信息利用率下降)导致准确率比16K低约7个百分点,同时单次调用成本是16K的3倍多,首token延迟增加近2秒。
所以选上下文长度的正确姿势是:先估算你的典型任务需要多少token,然后留30%余量,不要盲目追大。估算方法很简单,中文大致1个汉字≈1.5个token,英文1个单词≈1.3个token。一个典型的RAG问答场景,系统提示词500token+检索文档3段各800token+用户问题200token+历史对话1000token,总共约4100token,选8K窗口绰绰有余。
1.3 成本结构决定了架构设计
LLM应用的成本和传统Web应用完全不同。传统应用主要是服务器固定成本,流量涨了加机器就行,边际成本可控。LLM应用的成本几乎全是变动成本,按token计费,用户每多问一句就多花一份钱。
我算过一笔账:一个中等规模的企业内部问答助手,日活200人,人均提问15次,每次平均消耗输入2000token、输出500token。按当时主流模型的价格,输入约0.008元/千token,输出约0.02元/千token,一天的成本是:
- 输入:200×15×2000÷1000×0.008 = 48元
- 输出:200×15×500÷1000×0.02 = 30元
- 合计:78元/天,约2340元/月
看起来不多,但如果用户量翻10倍,或者有人恶意刷接口,成本会迅速失控。所以架构设计阶段就必须考虑:缓存策略(相同问题命中缓存直接返回)、限流策略(单用户每分钟请求上限)、模型分级(简单问题走小模型,复杂问题走大模型)。
2. 模型选型:别只看榜单,要看你的任务类型
2.1 闭源API和开源本地部署的决策树
这是每个LLM项目第一个要做的决策。我的判断逻辑是这样的:
选闭源API的情况:项目周期紧、团队没有GPU运维能力、任务复杂度高(需要强推理能力)、数据敏感度可接受(不涉及核心机密)。优点是开箱即用、效果有保障、无需运维;缺点是数据出域、成本随量线性增长、模型版本不可控(厂商升级你被动跟随)。
选开源本地部署的情况:数据绝对不能出域、有长期大量调用需求(成本摊薄后本地更划算)、需要深度定制(微调、特殊tokenizer)、有GPU资源。缺点是前期投入大(一张A100或同级别卡就是几万到十几万)、运维复杂、效果通常比顶级闭源模型差一截。
我自己的做法是混合架构:核心敏感数据走本地开源模型,通用问答走闭源API。这样既保证了数据安全,又控制了成本。具体分流逻辑在网关层实现,根据请求携带的数据标签路由到不同后端。
2.2 开源模型选型的几个硬指标
如果决定本地部署,选哪个开源模型?我的评估维度按优先级排序:
| 评估维度 | 为什么重要 | 怎么测 |
|---|---|---|
| 中文能力 | 大部分国内场景是中文任务 | 用你的真实业务问题做测试集,人工评分 |
| 显存占用 | 决定你需要什么卡 | 看模型参数量和量化方式,7B模型FP16约14G显存 |
| 推理速度 | 影响用户体验 | 测首token延迟和每秒输出token数 |
| 微调生态 | 后续可能需要定制 | 看社区LoRA、微调脚本是否完善 |
| 许可证 | 商用合规 | 仔细读license,有些限制商用 |
实测下来,7B级别的模型在消费级显卡(如RTX 4090 24G)上跑FP16勉强够用,但要做并发就得量化。4bit量化后7B模型约4G显存,13B约8G,可以在单卡上跑多个实例做并发。量化会损失一些效果,通常3-5个百分点,但对大多数应用场景可以接受。
2.3 别忽略Embedding模型的选择
RAG应用里,Embedding模型(把文本转成向量做检索)的重要性不亚于生成模型。很多人随便选一个开源Embedding就用,结果检索召回率上不去,生成质量自然差。
我的经验是:中文场景优先选在中文语料上训练过的Embedding模型,不要直接用英文为主的通用模型。测试方法很直接:准备50-100个"问题-正确文档"对,用候选Embedding模型做检索,看正确文档排在前3的比例(Recall@3)。这个指标比任何榜单都靠谱。
另外,Embedding模型的维度也影响存储和检索速度。768维和1024维在效果上可能只差1-2个百分点,但存储和计算量差不少。如果向量库规模在百万级以下,维度的影响可以忽略;如果上千万级,就要认真权衡。
3. 提示词工程:从"能跑"到"稳定"的关键跨越
3.1 结构化提示词模板的设计方法
提示词不是写得越长越好,而是要结构清晰、约束明确。我经过多个项目迭代,总结出一个比较通用的模板结构:
# 角色 你是一个[具体角色],负责[具体职责]。 # 任务 [清晰描述要做什么] # 约束 1. [约束条件1] 2. [约束条件2] ... # 输出格式 [明确指定输出格式,最好给示例] # 输入 [用户输入或检索到的上下文]这个结构的关键在于把"角色""任务""约束""格式"分开,而不是混在一段话里。实测下来,分开写比混着写的指令遵循率高15%以上。原因是模型对结构化输入的解析更准确,不容易漏掉某条约束。
还有一个细节:约束条件要写成"必须做什么"而不是"不要做什么"。比如"不要编造答案"不如"如果上下文中没有相关信息,回答'根据现有资料无法回答'"。前者是负向约束,模型容易忽略;后者是正向指令,模型更容易执行。
3.2 Few-shot示例的数量和选择
Few-shot(给几个示例让模型模仿)是提升效果最直接的手段,但示例不是越多越好。我做过对比:0个示例、3个示例、5个示例、10个示例,在同一个分类任务上的准确率分别是72%、89%、91%、90%。3到5个示例是性价比最高的区间,再多收益递减,还增加token成本。
示例的选择比数量更重要。核心原则是覆盖边界情况。比如做一个情感分类,不要只给"正面""负面"的典型例子,还要给"中性""混合情感"这种容易混淆的例子。我通常会从bad case里挑几个典型错误,做成示例加进去,效果立竿见影。
3.3 输出格式控制的实战技巧
让模型稳定输出JSON是很多应用的基础需求,但模型经常会在JSON前后加解释文字,或者漏字段、多字段。我的解决方案是三层保障:
第一层,提示词里明确要求"只输出JSON,不要任何其他文字",并给出完整的JSON schema示例。
第二层,如果用的是支持function calling或JSON mode的API,直接开启这个模式,模型层面保证输出合法JSON。
第三层,代码里做解析兜底。用正则提取第一个{到最后一个}之间的内容,再尝试解析。解析失败就走重试逻辑,重试时在提示词里加上"上次输出格式错误,请严格按JSON格式输出"。
这三层下来,JSON解析成功率能从裸调的70%左右提升到99.5%以上。剩下0.5%走人工兜底或返回默认值。
4. RAG系统的工程细节:检索质量决定生成质量
4.1 文档切分策略比你想的重要
RAG的第一步是把文档切成小块(chunk),这一步做不好,后面全白搭。我见过太多项目直接用固定长度切分(比如每500字一刀),结果把完整的语义单元切碎了,检索出来的片段缺头少尾,模型根本没法用。
正确的切分策略是按语义边界切,同时控制长度。具体做法:
- 优先按文档结构切(标题、段落、列表项)
- 如果单个语义单元超过阈值(比如800token),再按句子边界二次切分
- 相邻chunk之间保留10-20%的重叠,避免边界信息丢失
对于Markdown、HTML这类有结构的文档,直接用解析库按标题层级切,效果最好。对于PDF,先做版面分析提取段落,再切分。纯文本就按段落和句子切。
还有一个容易被忽略的点:给每个chunk加上元数据,比如来源文档名、章节标题、页码。检索时把这些元数据一起返回,生成时可以让模型引用来源,用户也能追溯。这在企业场景里是刚需。
4.2 检索策略:向量检索不是唯一解
很多人做RAG只知道向量检索,其实混合检索(向量+关键词)效果通常更好。向量检索擅长语义匹配,但对精确匹配(比如产品型号、人名、专有名词)不敏感;关键词检索(如BM25)正好相反。
我的标准配置是:向量检索召回Top 20,BM25召回Top 20,然后用RRF(Reciprocal Rank Fusion)算法融合,取Top 5传给模型。这套组合在多个项目里比纯向量检索的召回率高10-15个百分点。
如果对延迟敏感,可以先用关键词检索做粗筛,再对粗筛结果做向量重排,这样比全量向量检索快很多。百万级文档下,全量向量检索可能要几百毫秒,两阶段检索能压到几十毫秒。
4.3 重排模型:小投入大回报
检索召回Top 20之后,直接传给生成模型有个问题:20个片段里可能只有3-4个真正相关,其余的是噪声,会干扰模型判断。这时候加一个重排(Rerank)模型,对这20个片段做精排,取Top 3-5,效果提升非常明显。
重排模型通常比Embedding模型大,推理慢一些,但因为只处理20个候选,总延迟增加有限(通常50-100毫秒)。实测下来,加Rerank后答案准确率能提升8-12个百分点,是RAG系统里性价比最高的优化点之一。
选重排模型同样要看中文能力,方法和选Embedding模型一样,用你的业务测试集测Recall@3。
5. 从Demo到生产:那些上线后才暴露的问题
5.1 并发下的性能瓶颈定位
Demo阶段单用户测试一切流畅,上线后并发一上来就各种超时。我遇到过的瓶颈按出现频率排序:
第一是模型API的速率限制。闭源API通常有RPM(每分钟请求数)和TPM(每分钟token数)限制,并发高了直接429。解决方案是加请求队列和退避重试,同时监控队列长度,超过阈值就降级或限流。
第二是向量库的检索性能。单条检索很快,但并发100时可能因为连接池不够或索引结构问题变慢。解决方案是给向量库配连接池,检查索引类型(HNSW通常比IVF快),必要时做分片。
第三是应用层的同步阻塞。很多实现是"检索→调模型→返回"全同步,一个请求占一个线程。并发高了线程池耗尽。解决方案是改成异步,用asyncio或类似机制,让IO等待时间不占用线程。
5.2 效果评估:没有评估就没有优化
LLM应用最难的环节之一是评估。传统软件看通过率,LLM应用看什么?我的做法是建立分层评估体系:
- 检索层:Recall@K、MRR,衡量检索质量
- 生成层:用另一个强模型做裁判(LLM-as-Judge),对答案的准确性、相关性、完整性打分
- 端到端:人工抽检+用户反馈(点赞点踩)
评估集要持续积累。我通常从真实用户问题里每周抽50-100条,人工标注标准答案,加入评估集。这样评估集越来越贴近真实分布,优化方向才不会跑偏。
LLM-as-Judge有个坑要注意:裁判模型会有位置偏见(倾向于选第一个或最后一个)和长度偏见(倾向于选长的)。解决方案是交换选项位置做两次判断,取一致的结果;同时控制对比答案的长度差异不要太大。
5.3 缓存策略:省钱又提速
LLM应用的缓存分两层:
精确缓存:用户问题完全相同时直接返回缓存结果。实现简单,用问题文本的hash做key。命中率取决于用户问题的重复度,企业内部场景通常有20-30%的重复率。
语义缓存:问题语义相似时返回缓存结果。用Embedding算相似度,超过阈值就命中。命中率能到40-50%,但有误判风险(相似问题答案可能不同)。我的做法是设一个较高的阈值(如0.95),宁可漏判不可误判。
缓存还要考虑失效策略。知识库更新后,相关缓存要失效。简单做法是给缓存加时间戳,超过一定时间(如24小时)自动过期。复杂做法是建立文档到缓存的映射,文档更新时精准失效相关缓存。
6. 多模型协作与Agent:什么时候该上,什么时候不该上
6.1 多模型协作的适用场景
"多AI协作"是热词,但不是所有场景都需要。我的判断标准是:单模型单次调用能否可靠完成?能,就别上多模型;不能,再考虑拆解。
适合多模型协作的场景有两类:
一类是任务异构。比如一个合同审阅系统,需要提取关键条款(适合小模型快速抽取)、判断条款风险(适合强推理模型)、生成审阅意见(适合长文本生成模型)。三个子任务用不同模型,各司其职。
另一类是质量校验。生成模型出初稿,校验模型做事实核查和格式检查,不合格打回重生成。这种"生成-校验"循环能显著降低错误率,代价是延迟和成本翻倍。
不适合的场景:简单的问答、分类、摘要。这些任务单模型一次调用就能做好,上多模型纯属增加复杂度。
6.2 Agent的边界:自主性和可控性的平衡
Agent(智能体)的核心是让模型自主决定调用什么工具、按什么顺序执行。听起来很美,实际落地时最大的问题是不可控。模型可能陷入循环、调用错误的工具、或者做出危险操作。
我的实践原则是:在关键节点加人工确认或规则拦截。比如Agent要执行删除操作、发送邮件、调用付费接口时,必须经过确认。Agent的自主性限制在"信息收集和初步处理"范围内,涉及副作用的操作都要有护栏。
另一个原则是限制最大步数。Agent执行超过N步(比如10步)还没结束,强制终止并返回当前结果。这能防止无限循环消耗资源。
6.3 工具调用的稳定性优化
Agent依赖工具调用(function calling),但模型调用工具的准确率不是100%。优化手段包括:
- 工具描述要极其清晰,包括参数类型、取值范围、示例
- 工具数量控制在10个以内,太多模型容易选错
- 给每个工具加"使用场景"说明,帮助模型判断何时该用
- 调用失败时返回明确的错误信息,让模型能自我纠正
实测下来,工具描述优化能把调用准确率从80%左右提升到95%以上。剩下5%靠重试和兜底。
7. 部署与运维:让LLM应用稳定跑起来
7.1 私有化部署的资源规划
企业私有化部署LLM应用,资源规划是第一步。我通常按这个公式估算:
- GPU显存:模型参数量( B) × 2( FP16) × 1.2( 余量) GB。7B模型约需17G,13B约需31G
- 并发能力:单张A100跑7B模型,FP16下大约支持5-10并发(取决于输入输出长度)
- 内存:向量库大小+应用内存+模型加载内存,通常64G起步
- 存储:模型文件+向量库+日志,按实际数据量估算
如果并发需求高,有两个方向:加卡做模型并行,或者用量化+多实例。后者成本更低,但效果有损失。我的建议是先用量化多实例扛住,效果不达标再考虑加卡。
7.2 监控指标:盯住这几个就够了
LLM应用的监控和传统应用不同,除了CPU、内存、QPS这些常规指标,还要盯:
| 指标 | 含义 | 告警阈值建议 |
|---|---|---|
| 首token延迟 | 用户感知的响应速度 | P95超过3秒告警 |
| 端到端延迟 | 完整响应时间 | P95超过15秒告警 |
| token消耗速率 | 成本控制 | 超过预算80%告警 |
| 错误率 | 调用失败比例 | 超过5%告警 |
| 缓存命中率 | 成本优化效果 | 低于20%需排查 |
| 检索召回率 | RAG质量 | 低于基线告警 |
这些指标要打到监控面板上,最好能按用户、按接口维度下钻,方便定位问题。
7.3 版本管理与灰度发布
模型和提示词的变更都会影响效果,必须做版本管理。我的做法是:
- 提示词存在配置中心,每次修改记录版本号和修改人
- 模型版本固定,升级前先在评估集上跑对比
- 新版本先灰度5%流量,观察核心指标无异常再逐步放量
- 保留回滚能力,出问题5分钟内切回旧版本
这套流程看起来重,但真出问题时能救命。我经历过一次提示词微调导致答案准确率下降15个百分点的事故,因为有灰度机制,只影响了5%用户,及时发现并回滚了。
8. 一些踩坑后的个人体会
做LLM应用开发这两年,最大的感受是:技术选型的重要性远低于工程细节的打磨。选哪个模型、用哪个框架,这些决策的影响可能只有20%;而提示词怎么写、检索怎么优化、缓存怎么做、监控怎么配,这些细节决定了80%的效果和稳定性。
另一个体会是不要追求一步到位。我见过太多项目想一开始就做完美的Agent、复杂的多模型协作,结果连基础的RAG都没跑通。正确的路径是:先用最简单的方案跑通端到端,验证核心价值,再逐步优化。一个能用的简单系统,比一个不能用的复杂系统有价值得多。
最后说一个具体的技巧:建立你的bad case库。每次发现效果不好的案例,记录下来,分析原因,归类。积累到50-100个bad case后,你会发现大部分问题集中在少数几类原因上(检索不准、提示词歧义、上下文丢失等),针对性解决这几类问题,效果提升最快。这个习惯我从第一个项目保持到现在,是投入产出比最高的优化手段。