做LLM应用开发这一两年,踩坑的数量比写业务代码那几年加起来都多。最典型的现象就是:拿到一个大模型API,第一反应是“帮我写个代码”,跑出来的结果跟预期差十万八千里,项目直接被判了死刑。实际上,LLM只要用对了方法,能力释放的速度非常快。问题的关键,是得在选型、部署、调用、调试、评测、安全这几个环节都有自己的决策依据,而不是跟着热搜词走。结合目前大家集中关注的本地跑GGUF、智能体容错控制、LLM as Judge、工具调用报错排查这些热点方向,我把一套完整的使用方法整理在下面,覆盖从拿到一个模型到上生产线的全过程。
这篇东西适合谁?正在做LLM应用落地、被效果飘忽和API报错折腾得不轻的开发者;想在端侧或者本地跑模型、研究GGUF格式和安卓端方案的产品和技术朋友;以及负责模型微调、评测、安全加固的算法工程师。我会按一条真实项目链路来讲,每一条经验都来自实际跑过的环境,而不是文档里的漂亮话。同样的坑你已经踩过一次,就没必要再让别人踩第二次。
1. 先定调:选模型不是选最大,而是选最合适
1.1 参数量、上下文和场景怎么对齐
很多人选模型时只盯着一个数字:参数量。7B太小,70B太大,中间选个13B就万事大吉。这是最偷懒的选法。模型选型本质上是对任务类型、延迟预算、成本预算三个约束做权衡,参数量只是其中一个变量。
根据我自己的经验,可以把常见任务简单粗暴地分成三类:
- 轻量任务:意图识别、关键词抽取、文本分类、简单的格式化输出。这类任务用7B到9B的开源模型就够,商用API也可以选最便宜的档位。任务本身不复杂,模型参数大了反而容易过度发挥,把一句“好的”扩写成一段热情洋溢的演讲。
- 中等任务:客服对话、内容总结、信息抽取、SQL生成。这类任务需要一定的推理能力,但又没那么烧脑,推荐13B到32B区间的模型,或者商用API的中端档位。32B模型在实际表现上和70B差距没有想象中那么大,但显存和响应速度友好太多。
- 重推理任务:复杂代码生成、数学推理、多跳问答、长文档分析。这类任务需要模型有足够的“思考容量”,小模型经常会在推理中途断掉逻辑链。建议直接上70B以上的开源模型,或者直接调用顶级商用API。注意,这里说的“顶级”不是指参数大,而是指经过大规模RLHF后指令遵循能力更强的商业模型。
还有一个经常被忽略的参数是上下文长度。很多模型宣称支持128K甚至200K上下文,但真实表现跟宣传是有距离的。长文本支持分两种情况:一种是“能塞进去”,第二种是“塞进去还能用”。我自己实测,超过32K之后,不少模型在长文档中的回答质量会明显下降,尤其是需要跨段落找信息时。所以,不要为了炫技把4万字的资料一次性全塞进提示词,先做检索,再把最相关的片段拼进去,效果往往比硬塞全文好得多。
另外需要提一句,现在出现了一些垂直方向的模型,比如Spatial LLM,专门处理空间感知和位置推理任务,比如机器人导航、3D场景理解、GPS轨迹分析。如果你做的刚好是这种非典型任务,通用模型不一定占优,反而应该去调研有没有专门的领域模型。选型这件事,本质上就是在“通用能力”和“领域适配度”之间找平衡。
1.2 商用API与开源模型,别被“免费”带偏
商用API和开源模型之间的选择,是每个团队都会纠结的问题。先说结论:不存在绝对优劣,只有适不适合。
商用API的核心优势是低运维成本。你不需要自己准备GPU,不需要处理显存溢出,模型迭代是服务商的事情。功能上,顶级商用API的指令遵循能力通常比开源模型好一截,特别是在工具调用、多轮对话一致性、避免幻觉这类问题上。我做过对比,同一个Prompt在商用API上的表现往往比本地7B模型稳定得多,这在面向客户的生产环境里很关键。
但商用API有它麻烦的地方:数据隐私。如果业务涉及用户隐私信息,你把对话内容传到云端API,就等于把合规风险一起打包出去了。金融、医疗、企业内部知识库这些场景,数据不出内网是硬性要求,这时候开源模型加本地部署几乎是唯一解。
开源模型也不是完全“免费”。你省了API费用,但GPU采购、机房托管、模型调优的人力成本都是隐性的。一台能跑70B量化模型的机器,配置稍好一点就要几万块,而且还需要有人维护。如果你的团队连一个能调显存的人都没有,我还是建议先用API把业务跑通,等用户量上来、成本压力变大了,再考虑换开源模型。
我自己习惯用四个维度做决策:数据敏感性、成本预算、团队运维能力、效果要求。凡是数据敏感且对效果要求不是极端的,优先开源模型;凡是数据不敏感、又是核心业务必须保证效果的,优先商用API。还有一种混合姿势:敏感数据用本地开源模型处理,非敏感且高难度的任务走API。这个方案虽然稍微麻烦一点,但可以兼顾隐私和效果。
2. 本地跑LLM:GGUF、量化与安卓端的实战记录
2.1 GGUF为什么能成为本地部署的通用格式
如果你在本地玩过LLM,一定见过“.gguf”结尾的模型文件。GGUF是llama.cpp项目推出的一种模型格式,设计目标就是一个文件解决所有事:把模型权重、分词器、超参数、特殊token、应用程序元数据全部打包在一起,分发极其方便。
早些年本地推理用的还是GGML格式,升级到GGUF的主要原因是GGML不支持对分词器等额外信息的打包,每次加载都要额外处理一堆配置文件。而且GGML在架构扩展上非常僵硬,换个量化方式就要重新设计格式。GGUF相比之下灵活得多,它内部有元数据字段,可以存自定义信息,不同的算子和量化策略也能共存。现在不管你是用Ollama、LM Studio还是llama.cpp原生命令行,底层跑的大多数都是GGUF格式。
为什么这个格式能成为事实标准,我觉得有个很重要的原因:llama.cpp社区足够活跃。它最早打通了“在消费级硬件上跑LLM”这条路,把原本需要数据中心才能玩的模型压缩到了家里一台电脑就能跑起来。GGUF跟着这个生态一起长大,几乎所有新开源模型发布后,社区都会在几小时内放出对应的GGUF转换版。这种生态效应,不是后来者随便出个新格式就能撼动的。
如果你只是普通用户,不需要关心GGUF内部结构,但你至少要知道一件事:本地部署LLM之前,先去hf-mirror或者Hugging Face上搜“模型名+GGUF”,优先下载对应作者或知名社区成员转好的版本,而不是自己用脚本转格式。自己转不是不行,是没必要。转换过程中的量化参数选择、特殊token处理都是坑,能踩的坑为什么不绕开?
2.2 量化级别选型,Q4还是Q8,别只看显存
量化是本地跑模型的核心操作。它的原理很简单:模型权重原本用16位浮点数存储,量化后只用4位或8位来近似表示,可以让模型文件体积缩小到原来的四分之一甚至更小,同时尽量保留效果。
GGUF常见的量化级别从低到高大致是Q2_K、Q3_K_S、Q4_0、Q4_K_M、Q5_K_M、Q6_K、Q8_0。级别越高,体积越大,理论上效果越好。我实测下来,Q4_K_M是最具性价比的选择,文件体积大约是原FP16模型的四分之一,效果损失在大部分任务上可以接受。如果你的任务对输出质量极度敏感,比如代码生成、结构化数据抽取,建议上Q8_0。Q8_0的体积是FP16的一半,但效果几乎无损,跑起来也更稳。
这里要纠正一个误区:很多人以为量化级别的选择就是看显存够不够,显存大就上高精度,显存小就降低精度。实际上,除了看显存,还要看你任务的复杂度和关键字的敏感程度。Q2级别的模型经常会出现语义漂移,比如把“用户地址”抽成“收货地点”,字段名都变了,下游程序直接崩。所以我有一条铁律:低于Q4_K_M的模型不用于任何生产任务,只拿来玩一玩。
具体选多大量化,还得算一下显存。以7B模型为例:
- Q4_K_M大约4.1GB,Q5_K_M大约4.8GB,Q8_0大约7.2GB,FP16大约14GB。
- 如果你只有8GB显存,上7B Q8勉强可以,但推理时还要留出KV Cache的空间,上下文一长就会爆。
- 更稳的做法是选7B Q4_K_M,留出缓冲区给长上下文。
工具方面,LM Studio是我用过最省心的本地推理工具之一,图形界面、拖拽即用,自带模型下载和配置管理。Ollama则更偏向命令行和脚本化,适合要写自动化部署的人。最简单的玩法是装好Open WebUI,界面和主流闭源产品长得几乎一样,背后接Ollama的本地模型,团队内部需要私有知识库的时候,这个组合相当能打。
2.3 安卓上跑GGUF:支持安卓8的经验与限制
移动端跑LLM是最近的热门话题。很多人想在地铁上不联网也能用大模型,这个需求在技术和产品上都成立。Android端现在已经有不少方案能直接加载GGUF文件,甚至支持到安卓8这个老版本。
我实测过的主流方案有两类。一类是MLC LLM编译出来的Android APK,它对模型的支持比较全,界面也友好,但安装包体积大,而且对手机SoC有一定要求。另一类是Termux里装llama.cpp,然后在命令行里加载GGUF模型,这种方式灵活度最高,但需要你有一定的Linux基础。此外还有不少专门的APP,比如在GitHub上可以找到的“安卓本地运行GGUF格式LLM软件”类项目,操作起来比Termux简单,但更新频率和维护质量参差不齐。
支持安卓8意味着什么?安卓8是2017年的系统了,意味着处理器大概率是老款的骁龙660或者麒麟970级别。在这个硬件上跑LLM,现实一点:你只能跑1.5B到3B的小型量化模型,而且速度不会太快。我试过在骁龙665上跑Qwen2.5-1.5B Q4_K_M,生成速度大概是每秒6-8个token,用来做简单的问答和翻译勉强能接受,指望它梳理一篇长文就别想了。
在安卓端跑模型有几个绕不开的调试项:第一,内存和存储。GGUF文件动辄一两GB,老手机存储容易告急。第二,NPU加速。安卓上很多NPU驱动不开源,支持极差,你用不上加速就老老实实走CPU,CPU频率一降,速度就断崖式下跌。第三,上下文长度。手机内存本来就紧张,上下文设到4096就差不多了,再长容易直接被系统杀掉进程。
第三方早期测试显示,在安卓8的老设备上,最稳的组合是“1.5B模型 + Q4量化 + 短上下文”,先保证能出声,再考虑效果好不好的问题。如果你确实需要在老手机上做演示,我建议先跑通系统级的LLM推理,再去优化响应速度和生成质量,这两步顺序别反。
3. 接入API与工具调用:框架救不了所有错误
3.1 Function Calling的正确姿势,以及“provider rejected the request schema”怎么排查
LLM真正在生产环境里发挥作用,必然要接触工具调用,也就是Function Calling(工具调用)。核心逻辑是:你告诉模型有哪些函数可以调用、每个函数参数长什么样,模型在回答的过程中判断当前需要调用哪个函数,并生成结构化的调用请求,然后你的程序负责执行并返回结果。这套机制让LLM从“只会聊天”进化成“能干活”。
但我收到的求助里,工具调用相关的报错占了相当大比例。最近很多人被同一个报错卡住:“LLM request failed: provider rejected the request schema or tool payload.”这句话的意思是:你发给模型的工具描述(schema)或者某个工具调用请求(tool payload)不符合服务端的校验规则。
排查这个报错,按顺序检查四件事:
- 检查工具函数的JSON Schema格式。很多模型服务端对schema有严格限制,要求必须是合法的JSON Schema对象,并且类型定义要完整。你少写一个“required”字段、把“type”写成“string”以外的乱值,都可能被拒。
- 检查函数名是否重复或冲突。同一个请求里有两个同名工具,部分provider直接整个请求拒绝。
- 检查工具参数是否过大。有些平台对工具描述的总长度有限制,如果你的工具描述写了上千字,被拒的概率很高。
- 检查工具调用返回值的格式。某些平台的tool payload要求返回值是JSON字符串,你传一个dict进去,妥妥地报错。
我自己踩过的一个坑是:自定义工具里定义了一个参数类型是“array”,但忘了给数组元素指定“items”类型。模型服务端无法推断数组里是什么,直接拒绝整个请求。这个错误看代码非常隐蔽,模型端也不会给出友好提示,只能靠逐个字段的schema审查找出来。从那次之后,我给所有工具写了一个自动化schema检查脚本,在发起LLM请求之前先本地校验一遍,问题前置,省掉不少调试时间。
3.2 LangChain、LlamaIndex这类框架到底该不该用
聊LLM使用,绕不开框架。LangChain、LlamaIndex这些框架名气最大,生态也全,链式调用、记忆管理、文档加载、向量检索一应俱全。很多人一上来就学框架,结果学了两周还在跟API文档搏斗,真正要处理的业务逻辑一个字没写。
我的经验是:框架可以学,但不要迷信。框架最大的价值是提供了“记忆管理”“多步工具调用”“文档切分与检索”等通用模块,省去大量重复造轮子的工作。但框架同时也是抽象灾害:它把模型请求、结构化输出、错误处理都封装了多层,一旦出问题,你得从最上层的业务代码一路追踪到底层HTTP请求,排查成本高得惊人。
如果任务简单,比如只是调用API生成一个摘要,我强烈推荐直接裸调API。写一个十几行的函数,把system prompt和user message拼好,发出去,解析返回的结果,结束。如果任务是中大型Agent式应用,比如需要多轮对话、多次工具调用、状态保持,可以考虑用框架的编排能力,但一定要理解框架在你的Prompt里塞了什么额外指令,这些额外指令经常改变模型行为。
这里也顺便提一下LLM Wiki这个社区项目。它不是框架,而是收集了各类LLM技术笔记、论文解析和工程经验的wiki,非常适合初学者快速建立一个知识地图。当你被某个框架的抽象绕晕、想返回底层原理看看到底发生什么时,这类参考资源会比官方文档更快给你答案。
如果你已经决定用框架,我的建议是:先用裸API把最小可行版本跑通,确认模型本身能满足需求,再引入框架去处理复杂编排。也就是先证伪“模型不适合这个任务”这个可能,再去优化工程复杂度。顺序反了,你会同时面对模型效果和框架Bug两座大山。
4. 提示工程不是玄学:把模型训成可用状态的细节
4.1 系统设定与角色,别把模型逼成“戏精”
提示工程是LLM使用里最容易被两极分化的领域。一边是小学生都能写Prompt,另一边是写了几百条模板效果依然不稳定。核心原因是,很多人把系统提示词(System Prompt)当成魔法咒语,以为写得越长越厉害,结果模型被长长的角色设定绑住了手脚,回复字字都像在背课文,就是不好好说话。
最典型的案例是客服场景。有人在系统提示里写“你是一位温暖、专业、有同理心的客服代表,需要理解用户的情绪,表达关怀”,然后让模型回答“快递到哪了”。结果模型真的开始“先表达对你心情的理解”,洋洋洒洒写了四行安抚情绪的话,用户想知道的单号和物流轨迹一个字没提。这就是角色设定的过度干预。
我实际测试下来,系统提示词最重要的功能是“约束行为边界”,而不是“塑造人格”。一个高效的系统提示词应该包括:
- 你要完成什么任务,输出格式是什么。
- 哪些情况不能做什么,例如不能编造事实、不能回答与任务无关的问题。
- 输出语言的风格,但一句话即可,不需要长篇大论。
为了更稳,可以在用户消息的开头追加一条简短的任务指令,而不是全部丢进系统提示。比如系统提示里只写“你是客服”,用户消息里写“今天物流跟踪:只返回最新一条物流状态及预计到达时间,不要解释过程”。模型对靠近末尾的指令往往更敏感,这招在实践中很管用。
另外,模型过度“戏精化”还有一个原因:很多人把Few-shot(少样本示例)写得太像剧本。示例中模型的回应带了太多情绪点缀,模型就会模仿这种风格。如果你希望输出简洁,少样本示例就用简洁的文本来写,这是风格迁移最直接的控制手段。
4.2 结构化输出,JSON模式、约束解码与校验环
生产环境里,LLM的输出不能是一团自然语言,必须是结构化的数据,最典型的就是JSON。控制模型输出JSON的方式有三层,每一层的控制力度不同:
- 第一层,在Prompt里要求“输出JSON”。这是最弱的方式,只能作为兜底。不用我说你也知道,模型不一定听话,偶尔混进一段Markdown代码块包含JSON,解析器直接崩。
- 第二层,使用API自带的response_format或JSON Mode参数。OpenAI和大多数兼容OpenAI接口的服务商都支持这个功能,模型输出会被强制规范到JSON结构。这个方式我强烈推荐,几乎所有生产任务都该开启。
第三层,约束解码。这在本地推理中更常用,例如llama.cpp的JSON Schema约束,或者Outlines这类库,通过语法级别的约束来限制生成的每一个token。这种方式最稳,但需要额外的工程接入成本。
但我今天要强调的不是这三层,而是第三环:校验。模型输出即使被约束成JSON,也保不齐缺少必填字段、某个字段为空字符串、或者字段类型跟你预期不一致。我在实际项目里见到的崩溃,大部分不是模型没有输出JSON,而是下游代码做了解析但没做空值校验,拿到一个None就继续跑,最后运行时异常。正确做法是:模型输出之后,先做一次schema校验,不合法就重试一次,重试时把上一次的错误信息一并交给模型,让它自行修正。
结构化输出这块,还有一个容易被忽略的细节:数字和枚举类型的处理。让模型直接输出“2.5”比让它输出“2.5元”更可靠,因为在生成层面,纯数字是极高频token,模型掌握得好;但是“2.5元”这种带单位的字符串,模型可能在某些风格下输错。所以,宁可让模型输出裸数值,再在你的代码里拼单位,也别让模型连单位一起生成。
5. 微调自己的模型:聊天记录精调与评测闭环
5.1 用真实聊天记录做LoRA微调,数据清洗是第一优先级
通用模型永远不可能完全懂你的业务。比如你的公司内部有一套“工单优先级的判定规则”,你在Prompt里写十遍“A类工单优先”,都不如用几十条真实标注好的聊天记录微调一个小模型来得直接。这里说的微调,最常用的是LoRA,低秩适配,可以在不改变原有模型权重的前提下,训练一小部分额外参数来适配领域。
聊天记录是微调最珍贵的数据来源,因为它真实反映了用户怎么问、你的优秀客服怎么答。但真实数据有个问题:噪声太大。用户消息里充满了错别字、无意义重复、口语碎片;客服回复里也有大量复制粘贴的模板。直接用原始聊天记录微调,模型会学到很多坏习惯。
所以数据清洗是第一优先级。我处理聊天记录微调数据的建议步骤:
- 去重。用户重复问同样问题,或者客服用同一模板回复,保留能代表正常分布的那一部分。
- 去除敏感信息。手机号、身份证、地址等PII必须脱敏,不然后患无穷。
- 挑“钻石”。不是所有对话都有训练价值。挑那些客服回应准确、语气专业、能覆盖常见难点的对话。通常30%的优质对话就能带来80%的领域能力提升。
- 构造多轮结构。如果原始对话缺失上下文,需要把它补成完整的多轮对话片段,而不是一个单独的问答。
LoRA微调的成本可控。即使你只有一块消费级显卡,也可以训练7B模型的LoRA,训练时间取决于数据量和步数。但我要给个提醒:如果数据量小于几百条,微调效果可能反而不如写好few-shot提示词。微调是锦上添花,不是无中生有。数据量太少,模型学不到你的业务规则,只会把原有的通用能力稀释掉。
5.2 LLM as Judge:让模型评模型的注意点
做微调和Prompt优化,最头疼的是评价“效果变好了没有”。人看太慢,机器看又不知道怎么算对。近几年“LLM as Judge”成了主流方案,用更强的模型来评弱模型的输出。省时省力,还便宜。
但LLM as Judge不是无脑把两个答案丢给裁判模型,它浑身都是坑。
首先是位置偏置。两个答案放在A和B两个位置,裁判模型的高频幻觉是更倾向于选第一个。缓解办法是跑两轮,交换两个答案的顺序,如果两次结论不一致,打平或做裁决。这个操作成本很小,却能显著提升评判稳定度。
其次是自恋偏置。很多模型自带“自我崇拜”倾向,会给自己家族的模型打高分。如果你的弱模型和强模型是同一家的,评分虚高几乎不可避免。交叉模型评测会好一些,但也不是绝对公平。在产线上,我尽量避免用单一裁判模型的结果做线上决策,只把它当作筛选信号。
第三是冗长偏置。裁判模型经常喜欢字数多的答案,哪怕后一半在重复前一半。解决办法是给裁判模型明确的标准:“先检查是否答非所问,再来评价内容完整度,最后才是表达方式”。评分维度拆开,每一步给分,能有效稀释冗长带来的虚假好感。
做评测的时候,给裁判模型一个评分模板会稳很多。比如把正确性、完整性、格式符合性分别打分,最后求加权平均。我试过直接让模型“打个总分”,它的分数经常飘忽不定,但拆分维度后,不同模型的得分差异就有辨识度了。评测这块,“稳定可复现”比“绝对准确”重要得多。
5.3 基于LLM的单元测试,把模型行为固化成用例
很多做LLM应用的人,每次改完Prompt就跟拆盲盒一样,不知道哪里变好了,也不知道哪里变坏了。这是没有把模型行为“测试化”导致的。传统软件开发有单元测试,LLM应用一样可以有。
做法不复杂:把一组固定的输入样例作为测试用例,每次修改Prompt或模型后,都会重新跑一遍这组用例,记录输出变化。但跟传统的断言不同,LLM的输出是开放性的,不能简单判断“等于某个值”,要么用规则判断,要么用LLM as Judge来打分。
我自己的LLM单元测试里会固定包含几类场景:
- 基础正确性:例如“判断用户是否在表达退货意愿”,必须输出“是/否”并给出原因。
- 格式合规性:输出JSON必须能被解析,必填字段必须存在。
- 边界与异常输入:空字符串、超长文本、混合语言输入,模型不能崩,最好能给出明确“无法处理”的响应。
- 工具调用正确性:给定一个工具调用场景,模型选对了工具、填的参数类型正确。
- 拒绝回答合规性:涉及风险话题,模型应该拒绝或按预案回应。
为了一次跑完这些用例不烧太多钱,可以选小一点的模型当“主测对象”,在用例上做精确对比。产物可以是一个简单的pytest套件,把每次请求的返回内容、评分、耗时一起记录到日志里,改动Prompt之后对比历史日志。你很快就能建立一套自己的回归保护区——以后不管谁手痒改了Prompt,测试跑不过就不给上线。
6. 上生产前的安全课:智能体容错与红队思维
6.1 智能体自主容错控制的工程实践:超时、重试、校验器
单轮调用LLM已经够让人头疼了,智能体(Agent)则是把LLM放进一个多步循环里,每步都可能调工具、读记忆、做决策。这意味着LLM的不确定性会被多次放大。一个环节回复延迟,整个任务就卡住;一次工具调用参数错误,后续流程基本作废。所以智能体系统的核心,不是让模型更聪明,而是用工程手段兜住模型的失误,这也正是“智能体自主容错控制”这个方向想解决的问题。
我实践下来的关键点是三件套:超时、重试、校验器。
超时必须有。LLM接口偶发延迟很常见,给每个子任务设置明确的超时时间,超过就切换备用模型或返回部分结果,比无限等待强一百倍。智能体的子任务多,要区分“整个Agent的全局超时”和“单次LLM调用的局部超时”,两种都要设,不然一次长任务能把整个服务拖垮。
重试要有策略,不能无脑重试。对瞬时性错误,比如网络超时、服务端5xx,可以快速重试,间隔指数退避;对模型端的4xx,说明你的请求本身有问题,重试一万次也没用,直接记录错误并降级。很多人在重试里忘了“隔一段时间再试”这个选项,遇到服务端过载,连续的快速重试只会让情况更糟。
校验器是智能体最容易被忽略的部分。模型每输出一步决策,你要先验证这个决策是否合法,再决定要不要执行。例如模型决定调用“查询订单”工具,你要先校验它传进来的订单号格式是否合法,而不是傻乎乎把任何字符串拿到数据库里查。该校验器虽小,但能从源头拦截掉大量幻觉导致的流程污染。
搭建可靠AI系统的核心思路,是“把模型当成不可靠组件”。这听起来刺耳,但如果你想让它进产线,就得抱着这种心态:模型可能会说错、调错、想错,工程结构必须能把错误限制在可控范围内。我会留一个兜底方案:当Agent连续N步校验失败,主动退出循环并转人工。这个“人工回流”机制虽然老套,但比让模型硬撑着继续跑可靠得多。
6.2 Red-Teaming与记忆投毒,AgentPoison这类攻击能带来什么警示
安全是LLM工程里最默默无闻的环节。大家都在秀效果,没人愿意讲自己的系统被攻破过。但如果你真把LLM放上产线,迟早会遇到对抗性攻击,这不是“以后再说”的问题。
Red-Teaming(红队测试)是主动找攻击者的过程:你把自己当成黑客,专门去测模型的边界情况。比如构造恶意提示词绕过安全限制、往对话里插入无关指令、制造上下文混乱,看模型会不会被带跑。这个测试不能等上线后再做,因为线上修复的成本极高。在开发阶段就建立Red-Teaming用例集,每轮迭代Prompt后跑一遍,能烧掉很多隐患。
近期的AgentPoison研究揭露出一个新的攻击面:通过污染智能体的记忆或知识库来攻击LLM Agent。原理是,很多Agent会维护一个长期记忆库,或者从外部知识库里检索信息来做决策。攻击者如果在这些记忆/知识库里植入精心构造的毒化片段,Agent检索到之后,输出的决策就会被带偏。这在很多场景下非常危险,比如自动审核系统被植入一条“所有含X的请求都自动通过”的隐藏指令。
针对这种攻击的防护,我有几条实际建议:
- 知识库内容必须做来源审计。外部导入的知识都要有可信出处,并且只读取受控目录里的内容。
- 对检索到的内容执行“只读使用”,不要让检索结果拼接成新Prompt后覆盖掉你的系统提示。
- 给Agent的行为做最小权限设计。工具调用能不开的权限就不开,能只读的绝不写。不必要的API密钥不放在Agent环境变量里。
- 对Agent的高成本操作,比如发送消息、转账、删除数据,设置二次确认或人工审核,把这当作最后防线。
红队测试不是一次性的工作。每次模型升级、每次增加工具调用能力,攻击面都在变化。把它纳入常规发布流程,跟单元测试、评测放在同一个流水线里,你手上这套LLM系统才算真正能上得了台面。
我自己后来养成了一个习惯:每次给Agent新增一个工具,都会先问“如果这个工具被恶意调用,最坏会发生什么?”,把答案写进设计文档。这不需要高深的安全能力,只需要一点“被害妄想”,而这一点恰恰是很多AI工程师最缺的。上了生产之后你会发现,保住系统的底线比冲高准召重要得多。