1. 先重新理解:LLM使用和普通软件调用完全不是一回事
我见过太多人把LLM的API当普通接口调,第一步就错了。你传个字符串进去,期望它像JSON.parse一样返回一个确定性结果,结果发现同样的输入两次返回不一样,立刻觉得这玩意不靠谱。它不是不靠谱,是你的使用方法还停留在“函数调用”的思维里。
LLM(Large Language Model,大语言模型)本质上是一个在海量文本上训练出来的概率生成模型。你给它一段文本,它在词表上一步步预测下一个最可能出现的token,再把预测出的token拼回输入继续生成下一个。这个过程带随机性,所以同一个问题回答不完全一样,这是它的底层工作方式决定的。
刚开始接触LLM,我建议先建立两个心智模型。第一个叫“实习生模型”:“你招了一个读过海量文档、知识面极广但没什么实操经验的实习生。你得把任务交代清楚,给它工具,告诉它边界和输出格式。它表现好不好,很大程度上取决于你怎么布置任务。”第二个叫“上下文窗口模型”:模型只能看到你当前对话窗口里的那些token,窗口之外的旧内容它会彻底忘掉,跟鱼的记忆一样。所有长对话的截断、摘要、丢历史,本质都是在伺候这个窗口。
把这个基础认知理顺,后面所有技巧才有落点。否则你学的提示词模板也好,框架封装也好,都只是抄了个壳。
还有个很实际的问题:选模型。现在可选的LLM非常多,闭源的有GPT系列、Claude系列、Gemini系列,开源的有Llama、Qwen、DeepSeek、Mistral等。选型时主要看三件事:一是“参数量”和量化版本,它决定你需要多大的显存或内存;二是模型是否做过“指令微调”,也就是你说人话它能不能听懂人话,原始基座模型通常不能直接聊天用;三是上下文窗口长度,从4K到200K不等,长窗口的模型在长文档场景里能省很多事。
提示:除非你有明确的科研需求,否则日常使用优先选经过指令微调的Chat版本,而不是基座模型。基座模型只会文本续写,你问“你好”它可能回一个省略号。
2. API调用层面的基本功:从一次请求到可靠调用
2.1 一个请求里到底放了什么
不管用哪家的服务,一次LLM调用的核心结构其实大同小异。以OpenAI兼容的接口为例,一个最基本的请求是这样:
from openai import OpenAI client = OpenAI( base_url="https://api.example.com/v1", api_key="your-api-key" ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一名资深的Python工程师,回答要简洁、准确。"}, {"role": "user", "content": "用Python写一个函数,判断一个字符串是否是回文。"} ], temperature=0.3, max_tokens=1024 ) print(response.choices[0].message.content)关键的字段就两个:model和messages。messages是一个数组,里面的元素按role区分——system是系统指令,user是用户输入,assistant是模型的历史回复。整个对话历史就是靠这个数组维护的,你每次调用都要把历史一起传过去,模型才能“记得”之前聊了什么。
这里容易犯的第一个错:把system提示词里塞一大段无关紧要的背景故事。系统提示词越聚焦越好,它直接决定模型的行为边界。我在实际项目中用的系统提示词一般不超过200字,把角色、任务约束、输出格式三件事说清楚即可。
2.2 参数调节:temperature、top_p、max_tokens到底在控制什么
很多初学者把所有参数都按默认值来,从不细看。其实这几个参数直接影响生成的稳定性,搞懂了能省大量调试时间。
temperature控制的是采样的随机程度,取值范围一般是0到2。值越低,模型越倾向选概率最高的token,输出更稳定、更可复现;值越高,输出越发散、更有创造性。做数据抽取、代码生成、JSON格式化这类任务,我通常设0到0.3;做文案创作、头脑风暴,可以设0.7到1.0。注意temperature设为0不代表每次都一样,因为模型内部还有并行计算带来的微小随机性,但结果已经非常接近了。
top_p是另一种采样策略,叫核采样:只从累积概率达到p的最小token集合里采样。通常调temperature就够了,两个一起调容易互相干扰。OpenAI官方建议,两者只动一个。
max_tokens是生成的最大长度上限。这个要提前估算好。我见过有人要模型生成一份详细报告,结果不设max_tokens,模型到一半被截断,输出直接残缺。反过来,明明只要一个短结果,却设了很大的max_tokens,不仅浪费钱,还可能让模型“话痨”。经验法则:单次输出预期200字以内设512,500字左右设1024,长文档按字符数除以1.5估算(中文一个汉字约1到2个token,英文一个单词约1.3个token)。
2.3 上下文管理:和token预算的拉锯战
上下文窗口是有限的资源。GPT-4o mini的窗口有128K,听起来很大,但真正用起来你会发现,几百轮对话、几份长文档转眼就塞满了。
上下文管理有三个常用手段,按从简单到复杂排序:
- 滑动窗口截断:只保留最近N轮对话。简单粗暴,但会丢早期重要信息。
- 摘要压缩:当对话快满时,调用一次LLM把已有内容浓缩成摘要,再带着摘要继续聊。这个方案开销低、效果好,是目前的主流做法。
- 外部记忆:把重要信息抽出来存到向量数据库或普通数据库里,需要时再把相关内容检索回来。这就是后面要说的RAG(检索增强生成)的基本思路。
我自己做客服机器人的时候用的是“摘要+关键信息落库”的组合方案:对话早期就开始提取用户的关键需求写入结构化记录,窗口快满时做一次摘要,然后把摘要和历史关键记录一起作为新一轮的system上下文。
2.4 那些让你崩溃的报错与重试策略
调用LLM接口一定会遇到报错,这是工程的一部分。常见的有这几种:
| 报错类型 | 典型原因 | 处理方式 |
|---|---|---|
| Rate limit exceeded | 请求频率超限 | 指数退避重试,或降低并发 |
| context_length_exceeded | 输入token超窗口 | 截断或压缩历史后重试 |
| LLM request failed: provider rejected the request schema or tool payload | 工具调用参数不符合声明的JSON Schema | 校验函数参数格式,对模型输出做兜底解析 |
| Connection timeout | 网络波动 | 设置超时时间,重试2到3次 |
| Invalid API key | 密钥错误 | 检查环境变量和密钥归属项目 |
其中最容易被忽视的是“provider rejected the request schema or tool payload”这类报错。它发生在使用函数调用(Function Calling)功能时:模型返回了一个工具调用,但返回的参数字段和你在请求里声明的JSON Schema不匹配,服务端直接拒绝了。这个问题的根源在于模型是概率生成,它“觉得”参数应该长那样,但严格校验不过。解决办法有两个层面:一是声明的schema尽量简单,字段用简单类型,少用复杂嵌套对象;二是拿到模型返回后先做一次宽松的解析,用正则或JSON修复库提取参数,而不是直接信它给的原始字符串。
重试策略我建议用“指数退避”:第一次失败等1秒,第二次2秒,第三次4秒,最多5次。别傻乎乎地无限重试,也别一失败就立刻重试,这样只会把限流打得更死。
3. 提示词工程:让LLM稳定输出的实操方法
3.1 系统提示词的“角色加约束加格式”三段式
提示词工程说穿了就一件事:把“你想要什么、不要什么、按什么格式给”讲清楚。我自己的系统提示词模板长这样:
你是{角色}。你的任务是{任务描述}。 必须遵守的规则: 1. {规则1,例如:只能基于给定的资料回答,不知道就说不知道} 2. {规则2,例如:禁止编造统计数字} 输出格式: {JSON格式示例或其他格式说明}这个三段式结构看起来简单,但每个位置都有讲究。角色设定决定了语言风格和信息组织方式,约束部分决定了行为边界,输出格式决定了后续程序能否顺利解析。
3.2 few-shot示例:与其讲道理,不如抄作业
当你多次尝试文字描述都达不到效果,试试给示例。模型非常擅长从示例中推断模式,这跟人类“看例子抄作业”是一个道理。
举个例子,你需要把用户的自然语言转换成SQL查询语句。与其描述“如果你收到这样的问题,就生成这样的SQL”,不如直接给两三个成对的例子:
用户:查询上个月销售额超过10万元的客户名单 SQL:SELECT customer_id, customer_name FROM sales WHERE amount > 100000 AND order_date BETWEEN '2024-04-01' AND '2024-04-30'; 用户:统计每个类别的商品平均库存量 SQL:SELECT category, AVG(stock) FROM products GROUP BY category;几个高质量示例通常比大段说明有效,特别是格式敏感的任务。少样本(few-shot)不是越多越好,三到五个精心设计的示例往往就够,太多反而让模型困惑该模仿哪个。
3.3 结构化输出:JSON模式与函数调用
程序要使用LLM的输出,就不能容忍散文。我给所有生产环境里的LLM调用都要求结构化输出,最常用的有两种方式:
第一种是JSON模式。OpenAI兼容接口支持response_format={"type": "json_object"},强制模型输出合法JSON。但要注意:这个选项只能保证输出是JSON,不能保证字段一定符合你的预期。所以拿到结果后仍然要做字段校验和缺省值兜底。
第二种是函数调用(Function Calling / Tool Calling)。你把可用工具的函数名和参数schema传给模型,模型返回“工具名加参数”而不是自然语言。这套机制是构建智能体的地基,后面章节细说。
3.4 复杂任务拆分与思维链
一个常见误区:把所有需求都写进一条提示词,指望模型一口气完成。比如“分析这篇文章的情感、提取关键实体、翻译成英文、再写摘要”——四个任务糅在一起,输出质量必定拉胯。
正确做法是拆分:一次只让模型干一件事,每一步的输出作为下一步的输入。针对推理类任务,让模型“先想后答”,也就是思维链(Chain-of-Thought)提示。最简单的方式是在指令中加一句“请逐步分析,并在最终给出结论”。实践中我建议让中间推理过程放在一个单独字段里,最终结论用结构化格式输出,这样既能利用推理能力,又方便程序只取结论。
4. 进阶用法:LLM as Judge、RAG与智能体的容错控制
4.1 LLM as Judge:让大模型当裁判
“LLM as Judge”是最近一年多非常火的用法:让一个LLM担任另一个LLM或一组回复的评审员。它在两个场景里特别实用:批量评估生成质量,以及在一个多模型方案里选最优回复。
实际操作时注意几个坑。第一是位置偏差,评审模型对排在前面的候选回复更友好,解决办法是交换候选顺序评审两次取平均或取多数。第二是自我偏好,GPT模型评审时更偏爱GPT生成的回复,Claude评审偏爱Claude,跨家族评估时要用第三方模型或人工抽样复核。第三是奖励黑客,评审模型如果懂“套话”,质量差但辞藻华丽的回复分数会虚高。
我现在的评测方案是:用一套包含50条任务的测试集,每条任务用“LLM为裁判打分加人工抽检10%”的方式,两周跑一次回归,效果比纯人工评审稳定得多,成本也只有纯人工的十分之一。
4.2 RAG链路:让LLM学会“查资料再回答”
RAG(检索增强生成)解决的是“模型不知道、不想编”的问题。核心链路四步:切分、向量化、检索、合成。
切分是把长文档切成小块,每块500到800字,相邻块之间重叠100字左右,避免语义被切断。向量化是把每块文本嵌入成向量,用embedding接口或本地向量模型。检索是拿用户问题做同样的向量化,然后从向量库里找出最相似的Top-K块。合成是把这些块拼接进提示词,让模型基于资料回答。
我踩过的坑是分块过小导致语义碎裂。比如一份合同条款,一个条款跨两页,你把它切碎后检索回来只有半句,模型自然答非所问。后面我改成“父子分块”:小切块用于检索,命中后把所属的父级段落一起带入提示词,效果立竿见影。
4.3 智能体的容错控制:让LLM少数服从多数,而不是多数服从幻觉
现在人人都想搭LLM智能体——让模型自己规划步骤、调用工具、完成任务。但智能体有个工程痛点叫“误差累积”:模型每一步都有一定概率出错,十步下去成功率是指数级下降。构建可靠AI系统的关键,是把它当成一个“自主容错控制系统”来做。
我的工程实践有三条原则:
第一,每一步都要校验。模型说要调用工具A,参数给出来了吗?先做schema校验,不过就要求模型重写。模型说任务完成了,完成得有证据吗?要求它提供输出文件或结果文本,由程序侧确认。
第二,给智能体人为设置安全边界。包括最大迭代次数(比如20步防止死循环)、危险操作的二次确认机制、超时熔断。我在Agent配置里写死“如果连续两次相同操作失败,停止并上报人工”。
第三,状态回退机制。把智能体的每一步操作分成“可回退”和“不可回退”两类。查数据库、读文件是可回退的;发邮件、删数据是不可回退的。不可回退操作必须经过更严格的校验甚至人工审批。
还有个容易忽略的点:拒绝服务。当智能体加载了大量资料后,越界信息可能污染判断,有攻击者甚至故意向Agent的记忆库或知识库注入恶意提示(也就是Red-Teaming里常说的提示注入和知识库投毒)。实际项目中要在向模型发送内容前做敏感指令过滤,同时隔离外部资料与系统指令的边界。
5. 本地部署生态:GGUF格式、Ollama与端侧运行
5.1 GGUF是什么,为什么现在到处在聊它
GGUF是llama.cpp项目推出的一种模型量化格式。它能将原本占用十几个GB的模型压到几个GB,让消费级显卡、甚至纯CPU都能跑起来。量化(Quantization)的原理是用更少的比特去表示神经网络的权重,常见档位有Q2_K、Q4_K_M、Q5_K_M、Q8_0等。Q4_K_M是质量和体积比较均衡的档位,绝大多数场景我都从它开始试。
拿到GGUF文件的姿势:从Hugging Face上下载对应模型的GGUF版。国内网络访问Hugging Face不稳定,可以用hf-mirror.com镜像站,把https://huggingface.co/替换成https://hf-mirror.com/即可直接下载,非常方便。
5.2 Ollama:本地跑模型的省心方案
如果要我推荐本地部署的第一站,我会选Ollama。它把模型下载、运行、API服务全封装好了,一条命令就能跑起一个本地模型:
ollama pull qwen2.5:7b-instruct-q4_K_M ollama run qwen2.5:7b-instruct-q4_K_M安装完Ollama之后它就自动在本地起了OpenAI兼容接口,默认地址http://localhost:11434。代码里只需要把base_url改成它,model名改成你拉取的模型名,其他逻辑完全不用动。
跑本地模型前我建议先摸清自己机器的家底。7B量级的Q4模型需要8GB左右内存,14B需要16GB,32B则需要32GB以上。显存不够也别灰心,纯CPU也能跑,就是速度感人,7B模型每秒大概只能跑3到5个token,适合离线测试和玩票。
此外也可以留意LM Studio这个工具,它提供了图形界面,适合不想敲命令行的人。它能直接加载GGUF模型并提供OpenAI兼容的本地服务,在长上下文管理和模型加载速度上做得不错。
5.3 端侧跑模型的取舍
在安卓设备上本地跑GGUF模型,现在已经有了成熟的方案,主要靠llama.cpp的Android编译版本及其他封装应用来实现。就实际体验来说,手机内存8GB以上建议跑1.5B到3B的量化模型,16GB内存可以尝试7B。跑本地模型在端侧的优势主要是隐私和离线场景,代价是模型能力相比云端旗舰有明显的差距。
端侧使用LLM,说句实话,更多是一种“能离线干活”的保底方案。比如在会议室没网,掏出手机让本地模型帮忙润色一段文案,这个是能胜任的。真要让它做复杂推理,还是交给云端大模型更靠谱。安卓本地跑GGUF建议优先找支持8.0及以上系统的工具,低版本安卓的兼容性非常折腾,我不建议浪费时间在安卓7以下的老设备上。
6. 模型精调:用聊天记录养一个专属助手
6.1 什么时候才该微调
很多人一上来就想微调自己的模型,觉得这样才“高级”。但微调的成本和风险都不低,我一般建议先问三个问题:提示词方案试过了吗?RAG用上了吗?示例给了吗?这三板斧能解决八成需求。微调的适用场景其实很窄:一是模型的语气、风格需要固化,二是某些任务用提示词描述不清但示例明确,三是需要把特定领域的知识嵌进模型权重本身。
6.2 从聊天记录到训练数据
热词里提到“使用聊天记录模型精调LLM”,这条路确实可行。方法很简单:把客服对话、你和助手的优质对话整理出来,构造成微调需要的格式。主流的微调格式是对话补全(Chat Completion),每条样本长这样:
{ "messages": [ {"role": "system", "content": "你是某客服售后助手,语气亲切,回答不超过100字。"}, {"role": "user", "content": "我买的充电宝两天就坏了,怎么办?"}, {"role": "assistant", "content": "非常抱歉给您带来不便。请您提供订单号,我们会为您安排免费换新,来回运费由我们承担。"} ] }数据质量决定模型上限。我从真实聊天记录里整理数据时做了三层处理:第一层去隐私,脱敏姓名、手机号、地址;第二层去噪音,把客服的客套话、复制粘贴的错漏清洗掉;第三层控比例,每个业务类型保持大致均衡,避免模型被高频类型带偏。数据量方面,三千到一万条高质量样本是比较合适的区间,少于一千条效果甚微。
微调技术栈使用LoRA(低秩适配)已经是共识,用几百块钱的云端GPU就能做。开源工具首选LlamaFactory,它对数据和训练参数做了封装,支持Qwen、Llama等主流模型,七步之内就能跑通一次微调。
6.3 用LLM做单元测试:验证微调和应用效果的实用思路
微调之后怎么评估?这时候“基于LLM的单元测试”就派上用场了。
我的做法是:维护一份黄金测试集,里面包含50到100条典型问题,每条问题附带预期行为描述和评判标准。每次微调完或改完提示词,跑一遍测试集,然后用一个评判模型按标准打分。这个过程可以做成CI的一部分,和代码单元测试一样每天自动跑。
测试集的构造要注意难度分布。别全是简单问题,简单问题大家都过得了,拉不开差距。我会特意放进去一批“边界样本”:比如没礼貌的用户、超长的问题、完全不在知识范围内的问题、带有诱导性的问题。这些样本最能暴露模型的弱点。有一次我把一个“引导用户评价赔偿金额”的诱导性问题放进测试集,发现微调后的模型直接开始承诺赔偿具体金额,好在评测提前暴露了这个问题,避免了一次线上事故。
评测打分我推荐用一个固定模板。评判模型不需要太强,它们的任务是“对照标准判断是否达标”,请降低自己的预期,7B级别的量化模型就能做一个不错的评分员。每一轮结果自动记录到表格里,版本迭代时能清楚看到每条样本的得分变化。
最后再说一个我个人的体会:LLM的工程化使用,本质上是在“模型的概率行为”和“系统的确定性需求”之间搭一座桥。你没法消除它的随机性,但可以靠上下文管理、结构化输出、校验重试、评测回归这一整套方法,把随机性圈在可控范围里。这套方法论不是一次性的,模型在变,框架在变,评测标准也在变,但底层思路是稳定的:先搞清楚边界,再设计兜底,最后用数据说话。