大模型从“能聊天”到“能干活”,中间其实还隔着好几个关键环节。很多人把模型部署起来之后,发现回答质量不稳定、知识过期、不会调用工具、稍微复杂一点的任务就卡住,于是开始怀疑是不是模型本身不行。实际上,多数情况下不是模型能力不够,而是你把 Prompt、微调、RAG、Agent 这几层工程手段的边界搞混了。
本文围绕 Qwen3.5 这条主线,完整梳理大模型从部署、Prompt 调优、LoRA/QAT 微调,到 RAG 知识库和 ReAct Agent 的落地链路。内容偏实战,代码尽量完整可复制,也会把高频报错和排查思路单独列出来。无论你是刚开始接触大模型的新手,还是已经做过简单 API 调用的开发者,都能从里面找到可以直接用的方案。
1. 背景:为什么大模型落地还需要“四件套”
1.1 大模型应用的基本路径
先看一张简化的大模型落地链路:
业务需求 -> Prompt 工程 -> 模型调用 -> RAG 知识增强 -> Agent 工具调用 -> 微调 / 量化 -> 部署上线这层结构解释了为什么现在大模型相关的技术词汇如此密集:不是每个问题都需要微调,也不是所有场景都必须上 Agent。你需要先判断,当前业务的核心瓶颈在哪一层。
- 如果模型回答风格不对、不理解业务术语,优先考虑 Prompt 优化。
- 如果模型知识不足、需要回答私有文档中的内容,优先考虑 RAG。
- 如果模型需要调用多个外部系统、完成多步推理任务,优先考虑 Agent + ReAct。
- 如果 Prompt 无论如何调都效果有限,且你有一定量高质量业务数据,再考虑微调。
- 如果要部署到低资源环境,还要考虑量化(GGUF、GPTQ、QAT 等)。
1.2 四件套分别解决什么问题
- Prompt:最轻量、成本最低的模型控制方式。它不改变模型参数,只改变模型的输入指令,用于约束输出格式、推理步骤、回答风格。
- ReAct:推理与行动交替执行的模式。模型先“思考”需要做什么,再“行动”调用工具,最后根据工具返回结果继续推理。
- RAG:检索增强生成。先从外部知识库检索相关内容,再拼入 Prompt 让模型生成答案,解决知识实时性和私有知识问题。
- Agent:以模型为大脑,自主拆解任务、调用工具、完成复杂工作流的系统。
- QAT:Quantization-Aware Training,量化感知训练。在训练阶段就模拟量化误差,让量化后的模型损失更小,是部署优化的关键手段之一。
- Harness:通常指模型评测框架或 Agent 运行时的“控制台”。它负责加载模型、执行评测任务、记录推理过程,是工程化落地中容易被忽略但很重要的一层。
1.3 关于 Qwen3.5 的版本说明
本文以 Qwen3.5 为线索,主要是因为它是当前中文开源大模型中生态比较完整的一支,官方仓库同时提供了基础模型、对话模型、GGUF 量化版本,并且对 llama.cpp、Ollama、Transformers、vLLM 都支持得比较成熟。需要注意,大模型版本迭代很快,本文出现的模型名、命令、参数,请以你本地实际的模型仓库为准。核心方法论是通用的,换成其他 Qwen 系列模型也能跑通。
2. 环境准备与版本选择
2.1 硬件环境
微调和推理对硬件的要求差异很大。下面给出一个参考区间:
| 场景 | 最低要求 | 建议配置 |
|---|---|---|
| 纯 Prompt + API 调用 | 无特殊要求 | 普通开发机即可 |
| 本地推理 7B/9B 模型 | 16GB 内存 | 24GB 内存 + 8GB 显存 |
| 本地推理 13B/14B 模型 | 24GB 内存 | 32GB 内存 + 12GB 以上显存 |
| LoRA 微调 7B/9B | 16GB 显存 | 24GB 以上显存 |
| QAT 全参量化训练 | 32GB 显存 | 多卡环境 |
如果你的机器达不到微调要求,也可以先用 API 或云端 GPU 完成实验,本地只跑推理。
2.2 软件环境
本文示例以 Linux 环境为主,Windows 下需要使用 WSL2 或调整部分命令。核心软件版本如下:
- Python 3.10 或 3.11
- CUDA 11.8 或 12.1(根据显卡驱动选择)
- PyTorch 2.x
- Transformers 4.40+
- PEFT 0.7+
- llama.cpp(最新 release)
- Ollama(最新 release)
- FastAPI、uvicorn、faiss-cpu、sentence-transformers
版本不要求完全一致,但要注意:PyTorch 和 CUDA 版本必须匹配,否则会在导入 torch 时直接报错。
2.3 推荐工具链清单
用一张表把本文会用到的工具串起来:
| 工具 | 作用 |
|---|---|
| Ollama | 最省事的模型加载和推理入口 |
| llama.cpp | 本地 GGUF 推理,CPU 也能跑 |
| Transformers + PEFT | LoRA 微调、加载模型 |
| sentence-transformers | 文本向量化 |
| FAISS | 向量检索 |
| FastAPI | 封装 HTTP 接口 |
| LM Harness | 模型评测和 Agent 运行控制 |
3. 先学会提问:如何编写并调优 Prompt
3.1 一个 Prompt 的基本结构
不要把 Prompt 理解成“给模型写一句话”。一个结构完整的 Prompt 通常包含以下部分:
- 角色:告诉模型它是什么身份。
- 任务:明确要做什么。
- 上下文:提供背景资料或数据。
- 约束:限制输出格式、长度、禁止事项。
- 示例:给出 1 到 2 个输入输出范例。
以 Qwen3.5 为例,一个结构完整的系统提示词可以这样写:
你是一名资深客服质检员。请根据给定的用户与客服对话记录,判断客服是否存在以下问题:态度不友好、回复敷衍、未解决问题。输出格式为 JSON,字段包括 problem、level、reason。只输出 JSON,不要解释。这种写法比“帮我分析对话”要稳定得多。原因是模型在解码时根据 Token 概率生成内容,约束越明确,候选输出空间越小,越不容易跑偏。
3.2 系统性提示词框架
在真实项目中,我建议把提示词拆成可维护的模板,而不是每次硬编码。下面是一个简单示例:
system_prompt = """ 你是一个严谨的 {domain} 助手。请遵循以下规则: 1. 只基于“已知信息”回答问题,不要编造。 2. 如果信息不足,请明确回答“资料中未提及”。 3. 回答结构:先说结论,再补充依据。 4. 最终输出必须控制在 {max_length} 字以内。 """这种做法的好处是:提示词一旦调好,可以在不同接口中复用;后续想调整语气或规则时,只需要改模板,不用改业务代码。
3.3 Prompt 调优的常见思路
Prompt 调优不是玄学,而是有迹可循的迭代过程:
- 先跑一个 baseline,记录输出质量。
- 分析错误类型:是格式错误、知识错误,还是推理错误?
- 针对错误类型调整对应模块。格式错误就在约束部分加示例;知识错误就补充上下文;推理错误就把推理步骤拆开要求“一步步思考”。
- 每次只改一个变量,避免把所有修改堆在一起后无法定位原因。
这里还要区分一个概念:Skill 是不是高级版 Prompt?从表面看,Skill 确实是一组精心编写的提示词工程模板,但它往往还包含预设的工具调用规则、输出解析逻辑和错误恢复机制。可以理解为“封装了运行逻辑的 Prompt”,而本文后面讲的 Agent 则更强调“循环”和“工具执行”。
3.4 关于 invalid prompt 的说明
调用模型接口时,有时会遇到这类错误:
invalid prompt: your prompt was flagged as potentially violating our usage policy. please try again with a different prompt.这不是模型坏了,而是内容安全过滤器拦截了输入。出现这种提示,通常是因为输入文本命中了某些敏感规则。解决思路如下:
- 检查业务场景本身是否存在违规风险,调整输入内容。
- 避免在输入中堆叠大量负面关键词,即使上下文是安全的。
- 如果只是误判,可以改用更中性的表述、补充正面的限定词,比如“模拟一份合规培训内容”而不是直接罗列敏感词。
- 在本地部署环境下,如果你使用的是官方模型,该提示通常来自 API 网关而不是模型本身;如果确实被拦截,需要从网关策略上调整。
4. 基于 Ollama 和 llama.cpp 部署 Qwen3.5
4.1 Ollama 部署流程
Ollama 是目前最简单的大模型本地运行工具之一,适合快速验证。安装完成后,拉取模型并运行:
# 拉取 Qwen3.5 9B 模型 ollama pull qwen3.5:9b # 直接交互式对话 ollama run qwen3.5:9b # 查看已安装模型 ollama listOllama 还提供了本地 HTTP API。默认端口是 11434,可以直接用 curl 调用:
curl http://localhost:11434/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5:9b", "messages": [ {"role": "system", "content": "你是一个严谨的中文助手。"}, {"role": "user", "content": "用一句话解释大模型微调"} ], "stream": false }'如果拉取模型速度慢,可以配置国内镜像源。Ollama 支持通过环境变量设置镜像地址,具体地址请以你所在网络环境的可用镜像为准。
4.2 使用 llama.cpp 部署 GGUF 模型
如果你的机器内存有限,或者想实现 CPU 推理,llama.cpp 是更合适的选择。把 Qwen3.5 转换为 GGUF 格式或直接下载别人量化好的 GGUF 文件后,启动 server 模式:
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release -j # 启动服务 ./build/bin/llama-server \ -m /path/to/qwen3.5-9b.gguf \ -c 8192 \ --host 127.0.0.1 \ --port 8080启动完成后,可以访问/v1/chat/completions接口,这与 OpenAI 接口格式兼容,方便后续接 RAG 或 Agent 框架。
4.3 基于 FastAPI 封装统一模型服务
在项目开发中,业务方通常不希望直接面对 Ollama 或 llama.cpp 的接口差异。更规范的做法是增加一层统一网关。下面给出一个最小可用的 FastAPI 封装:
# 文件路径:app/main.py from fastapi import FastAPI from pydantic import BaseModel import requests app = FastAPI(title="Qwen3.5 Gateway") OLLAMA_URL = "http://localhost:11434/api/chat" MODEL_NAME = "qwen3.5:9b" class ChatRequest(BaseModel): prompt: str system: str = "你是一个乐于助人的中文助手。" temperature: float = 0.7 class ChatResponse(BaseModel): reply: str @app.post("/chat", response_model=ChatResponse) def chat(req: ChatRequest): payload = { "model": MODEL_NAME, "messages": [ {"role": "system", "content": req.system}, {"role": "user", "content": req.prompt} ], "stream": False, "options": { "temperature": req.temperature } } resp = requests.post(OLLAMA_URL, json=payload, timeout=120) resp.raise_for_status() data = resp.json() return ChatResponse(reply=data["message"]["content"])启动命令:
uvicorn app.main:app --host 0.0.0.0 --port 8000有了这层网关,后面接 RAG、Agent 时就可以只对接一个统一接口,不需要关心底层是 Ollama 还是 llama.cpp。
5. 大模型微调实战:以 LoRA / QAT 为例
5.1 什么时候需要微调
微调的核心目的是“改变模型的行为习惯”,而不是“给模型塞知识”。当你发现以下情况时,才真正需要微调:
- 模型输出风格和你的业务差异太大,Prompt 无论怎么写都拉不回来。
- 模型在特定格式任务上表现差,比如必须输出严格 JSON 或特定标签。
- 你的业务场景有大量专业术语,且 RAG 召回效果有限。
- 你需要模型具备某种固定任务闭环能力,比如把口语日志改写成标准工单。
如果你只是想让模型回答公司文档里的内容,优先做 RAG,而不是微调。微调需要数据、算力和更多维护成本,但并不会让模型凭空获得新知识。
5.2 准备微调数据
微调数据通常采用 JSONL 格式,每条数据是一个“指令 + 输出”对。这里给出一个 Qwen 对话格式的训练样本:
{"instruction": "将下面日志改写成标准故障工单:连接超时。", "output": "【故障现象】客户端连接外部服务时发生超时。\n【影响范围】依赖该服务的业务模块。\n【建议动作】检查网络连通性及对端服务状态。"}数据质量比数据数量重要。几十条高质量样本可能比几千条噪声数据效果更好。建议先把数据清洗出来,人工抽检 20% 以上,确保指令和输出是一一对应的,不应该出现“同样的指令,不同输出”的情况。
5.3 LoRA 微调代码
LoRA 是一种参数高效微调方法,它只训练一小部分低秩矩阵,显存占用明显低于全参微调。下面给出基于 Transformers + PEFT 的完整示例:
# 文件路径:train_lora.py import json from datasets import Dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer ) from peft import LoraConfig, get_peft_model # 1. 读取数据 with open("train_data.jsonl", "r", encoding="utf-8") as f: raw_data = [json.loads(line) for line in f] def build_chat_text(item): return ( "<|im_start|>system\n" "你是一个业务知识助手。\n" "<|im_end|>\n" f"<|im_start|>user\n{item['instruction']}\n<|im_end|>\n" f"<|im_start|>assistant\n{item['output']}\n<|im_end|>" ) dataset = Dataset.from_list([{"text": build_chat_text(x)} for x in raw_data]) # 2. 加载模型与分词器 model_name = "Qwen/Qwen3.5-9B" tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype="auto", device_map="auto", trust_remote_code=True ) # 3. 配置 LoRA lora_config = LoraConfig( r=16, lora_alpha=32, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj" ], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 4. Tokenize def tokenize_function(examples): return tokenizer( examples["text"], truncation=True, max_length=2048, padding=False ) tokenized_dataset = dataset.map( tokenize_function, batched=True, remove_columns=["text"] ) # 5. 训练参数 training_args = TrainingArguments( output_dir="./qwen3.5-lora-checkpoints", per_device_train_batch_size=2, gradient_accumulation_steps=8, num_train_epochs=3, learning_rate=2e-4, logging_steps=10, save_steps=500, fp16=True, report_to="none" ) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset, ) trainer.train() model.save_pretrained("./qwen3.5-lora-final")训练完成后,可以用下面的代码合并 LoRA 权重并保存完整模型:
from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer base_model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen3.5-9B", torch_dtype="auto", device_map="auto" ) model = PeftModel.from_pretrained( base_model, "./qwen3.5-lora-final" ) merged_model = model.merge_and_unload() merged_model.save_pretrained("./qwen3.5-merged") tokenizer.save_pretrained("./qwen3.5-merged")5.4 量化感知训练 QAT 简介
QAT 的原理是让模型在训练阶段就感知到量化误差。相比训练完成后直接做 PTQ(训练后量化),QAT 通常能保留更高的精度。它的实现思路大致是:
- 在训练过程中插入“伪量化节点”,前向传播时执行量化再反量化。
- 反向传播时使用直通估计器近似梯度,让参数适应量化误差。
- 训练结束后导出为 INT8 或 INT4 推理模型。
如果你使用的是 llama.cpp 或 GGUF 路线,更常见的做法是先用原始权重微调,再做 GGUF 量化,例如将模型导出为不同精度的 GGUF 文件:
# 进入 llama.cpp 目录 python3 convert_hf_to_gguf.py \ /path/to/qwen3.5-merged \ --outfile qwen3.5-merged.gguf \ --outtype q8_0 # 进一步量化到 Q4_K_M ./build/bin/llama-quantize \ qwen3.5-merged.gguf \ qwen3.5-q4_k_m.gguf \ q4_k_m需要说明的是,真正的 QAT 训练需要专门的量化框架和较大的算力,直接使用torch.fake_quantize或 NVIDIA TensorRT Model Optimizer 会更规范。本文不展开完整 QAT 训练代码,因为涉及具体框架和模型结构,建议按官方文档操作。
5.5 微调后的评估
微调之后不要急着上线。先用一份“训练时没见过的测试集”评估效果。评估维度可以包括:
- 输出格式合规率:是否严格输出了要求格式。
- 答案准确性:与标准答案的匹配程度。
- 语义相似度:使用 BERTScore 或人工评分。
- 回归测试:确认微调没有把模型的通用能力带偏。
这里我们使用 lm-evaluation-harness 快速跑一个评测:
pip install lm-eval lm_eval \ --model hf \ --model_args pretrained=./qwen3.5-merged \ --tasks cmmlu \ --num_fewshot 0 \ --batch_size autoHarness 在这里就是“评测控制器”,它负责加载你的模型、准备评测集、统计指标。工程化落地时,Harness 还可以扩展用来管理多个实验的评测记录。
6. RAG 知识库实战
6.1 RAG 核心链路
RAG 的完整链路可以拆成五个环节:
文档加载 -> 分块 -> 向量化 -> 索引存储 -> 检索 -> 注入 Prompt -> 生成这里最容易出问题的是分块和检索质量。如果分块策略不合理,再好的向量模型也救不回来。
6.2 切块策略
切块需要平衡上下文长度与语义完整性。常见策略如下:
| 切块方式 | 适用场景 | 注意点 |
|---|---|---|
| 固定长度切块 | 通用文档 | 容易切断语义,不适合长文本 |
| 段落切块 | 结构化文档 | 保留较完整语义,推荐优先 |
| 递归字符切块 | 半结构化文本 | 需要设置分隔符优先级 |
| 语义切块 | 复杂业务文档 | 实现成本高,效果取决于模型 |
切块长度建议从 256 到 1024 个 token 之间测试。切得太短,上下文信息不完整;切得太长,向量检索精度下降,且容易超出模型上下文限制。另外,要保留块之间的重叠部分,一般重叠 64 到 128 个 token,避免关键信息恰好落在分块边界上。
6.3 向量化与检索
中文场景推荐使用 BGE 系列向量模型,例如:
pip install sentence-transformers faiss-cpu索引构建与检索代码如下:
# 文件路径:rag_index.py import json import numpy as np import faiss from sentence_transformers import SentenceTransformer embedder = SentenceTransformer("BAAI/bge-large-zh-v1.5") # 1. 加载文档,这里假设 docs.json 是分块后的文本列表 with open("chunks.json", "r", encoding="utf-8") as f: chunks = json.load(f) doc_texts = [item["content"] for item in chunks] doc_vectors = embedder.encode(doc_texts, normalize_embeddings=True) # 2. 构建 FAISS 索引 dimension = doc_vectors.shape[1] index = faiss.IndexFlatIP(dimension) index.add(np.array(doc_vectors).astype("float32")) # 3. 检索 def search(query, top_k=3): q_vec = embedder.encode([query], normalize_embeddings=True) scores, ids = index.search(np.array(q_vec).astype("float32"), top_k) results = [] for i in ids[0]: results.append({"text": doc_texts[i], "score": float(scores[0][list(ids[0]).index(i)])}) return results这里的IndexFlatIP是内积索引,配合归一化向量就等价于余弦相似度。如果文档量大,可以换成faiss.IndexIVFFlat或faiss.IndexHNSWFlat来提升检索速度。
6.4 完整 RAG 问答代码
有了检索结果,接下来拼 Prompt 并调用模型生成答案:
# 文件路径:rag_qa.py import requests from rag_index import search OLLAMA_URL = "http://localhost:11434/api/chat" MODEL_NAME = "qwen3.5:9b" def build_rag_prompt(query, top_k=3): results = search(query, top_k=top_k) context = "\n\n".join([r["text"] for r in results]) prompt = f"""请基于以下给定的资料回答问题。 参考资料: {context} 问题:{query} 要求: 1. 如果资料中能找到答案,直接给出结论,并标注依据来源。 2. 如果资料中没有提到,明确回答“资料中未提及”,不要编造。 3. 回答控制在 200 字以内。 """ return prompt def rag_chat(question): prompt = build_rag_prompt(question) payload = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": prompt} ], "stream": False } resp = requests.post(OLLAMA_URL, json=payload, timeout=120) return resp.json()["message"]["content"] if __name__ == "__main__": while True: q = input("请输入问题(输入 exit 退出):") if q == "exit": break print(rag_chat(q))在 RAG 上面,现在又出现了 Ontology RAG、Agentic RAG 等概念。本质是在基础 RAG 之上增加了实体关系图谱、多轮检索改写或自动路由能力。对于大多数业务场景,先把基础 RAG 做好,把切块、向量模型、检索召回率调到位,再决定是否引入更重的框架。
7. Agent 与 ReAct 模式实战
7.1 ReAct 原理
ReAct 是 Reasoning + Acting 的组合。它让模型交替输出“思考”和“行动”,再根据行动结果更新下一步思考。一个典型的 ReAct 输出循环如下:
Thought:用户想查询北京天气,我需要调用天气查询工具。 Action:get_weather,city=北京 Observation:北京今天多云,气温 24℃ Thought:我已经得到天气信息,可以直接回答用户。 Answer:北京今天多云,气温 24℃。实现 Agent 时,你需要做两件事:一是解析模型输出的 Action 内容和参数;二是根据 Action 找到本地注册的工具并执行。
7.2 Agent 的循环控制
下面给出一个最小可用的 Agent 实现,仍然是基于 Ollama HTTP API:
# 文件路径:simple_agent.py import requests OLLAMA_URL = "http://localhost:11434/api/chat" MODEL_NAME = "qwen3.5:9b" # 1. 注册工具 TOOLS = { "get_weather": { "description": "查询指定城市天气,参数格式:city=城市名", "execute": lambda city: f"{city}今天多云,气温 24℃" }, "calc": { "description": "计算数学表达式,参数格式:expr=表达式", "execute": lambda expr: str(eval(expr)) } } def call_llm(messages): payload = { "model": MODEL_NAME, "messages": messages, "stream": False, "options": {"temperature": 0.2} } resp = requests.post(OLLAMA_URL, json=payload, timeout=120) return resp.json()["message"]["content"] def parse_action(reply): # 简化版解析:查找 Action: 后面的内容 if "Action:" not in reply: return None, None line = reply.split("Action:")[1].strip().split("\n")[0] if "," in line: tool_name, _, param = line.partition(",") else: tool_name, _, param = line.partition(",") return tool_name.strip(), param.strip() def run_agent(prompt, max_steps=5): messages = [ { "role": "system", "content": ( "你是 Agent 控制器。请使用 ReAct 模式工作。\n" "可调用工具:get_weather,calc。\n" "每次输出格式:\n" "Thought:你的思考\n" "Action:工具名,参数\n" "或者直接:Answer:最终答案" ) }, {"role": "user", "content": prompt} ] for step in range(max_steps): reply = call_llm(messages) print(f"=== Step {step + 1} ===") print(reply) if "Answer:" in reply: return reply tool_name, param = parse_action(reply) if tool_name is None: # 模型没有输出 Action 或 Answer,追加提示让它继续 messages.append({"role": "assistant", "content": reply}) messages.append({ "role": "user", "content": "请继续,输出 Action 或 Answer。" }) continue tool = TOOLS.get(tool_name.strip()) if tool is None: observation = f"错误:未找到工具 {tool_name}" else: # 解析参数,简化处理 key=value param_dict = {} for pair in param.split(",") if "," in param else param.split(","): if "=" in pair: k, v = pair.split("=", 1) param_dict[k.strip()] = v.strip() try: observation = tool["execute"](*param_dict.values()) except Exception as e: observation = f"工具执行异常:{str(e)}" messages.append({"role": "assistant", "content": reply}) messages.append({"role": "user", "content": f"Observation:{observation}"}) return "已达到最大步数,任务终止。" if __name__ == "__main__": result = run_agent("北京天气怎么样?顺便计算 12 * 8") print("\n最终结果:") print(result)7.3 Harness 是什么:与 Agent 的关系
Harness 在很多语境下被翻译成“脚手架”或“控制框架”。在模型评测里,Harness 负责统一加载数据集、执行评测循环;在 Agent 开发里,Harness 类似 Agent 运行时的“总控台”,负责调度模型、工具、记忆和错误恢复。
很多开源框架会把 Agent 和执行 Harness 分开设计。Agent 负责决策逻辑,Harness 负责基础设施,比如日志记录、API 重试、超时处理、并发控制。你在设计工程架构时,也应该把这两层分开,避免后续想换模型或换工具时牵一发动全身。
7.4 Agent 开发注意事项
Agent 最常见的线上事故是“循环执行不终止”和“工具参数解析失败”:
- 要为 Agent 设置最大步数上限,避免死循环消耗资源。
- 要给工具调用加超时和异常捕获,不能让工具崩溃拖垮整个 Agent。
- 工具返回结果要精简,过长的 Observation 会挤占模型上下文。
- 关键决策路径要打印日志,便于回溯。
如果模型在 Agent 场景经常出现工具名拼写错误或格式错误,可以考虑升级 Prompt,或者做少量 Agent 轨迹数据的微调,让模型更稳定地输出 Action 格式。
8. 常见问题与排查清单
8.1 高频报错列表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 拉取模型时连接中断 | 网络问题 | 配置国内镜像源,或手动下载 GGUF 文件 |
| 模型运行后输出乱码 | 分词器不匹配 | 下载模型时同时下载 tokenizer,确认与模型配套 |
| 调用 API 返回 invalid prompt | 输入触发内容安全策略 | 调整措辞,避免敏感词,检查业务合规性 |
| 提示 prompt is too long | 输入超出上下文窗口 | 缩短 Prompt,或增大-c上下文参数 |
| 微调时 CUDA out of memory | 显存不足 | 降低 batch size、开启梯度累积、使用 4bit 量化加载 |
| Agent 执行中报 agent terminated due to error | 模型输出格式非法或工具异常 | 增加格式约束、捕获工具异常、设置重试机制 |
| RAG 检索结果不相关 | 切块策略不合理或向量模型不匹配 | 调整切块长度,换用更适合中文的向量模型 |
8.2 Prompt 过长如何解决
有开发者遇到过这样的报错:
prompt is too long ... automatic compaction failed: api error: 400 unsupported这说明输入 Prompt 超过了模型上下文限制,同时自动压缩又失败了。解决办法有几种:
- 手动压缩 Prompt,去掉冗余内容。
- 使用 RAG 只保留检索到的关键片段。
- 增加模型的上下文窗口长度,例如在 llama.cpp 中调大
-c参数。 - 将多轮历史对话做摘要,只保留最近几轮。
8.3 排查 checklist
如果整体链路跑不通,按下面的顺序排查:
- 先单独测试模型 API,排除模型服务问题。
- 再测试向量检索,直接打印检索结果,确认召回内容是否合理。
- 然后测试 Prompt 拼接,检查是否有格式错误。
- 最后测试 Agent 循环,确认工具解析和调用是否正常。
- 每一步都保留日志,不要跳过中间环节直接看最终结果。
9. 最佳实践与工程建议
9.1 根据场景选择技术方案
不要为了“用技术而用技术”。推荐按以下分级:
- 标准化问答场景:Prompt + 少量示例即可。
- 私有文档问答:Prompt + RAG。
- 多工具多步骤任务:Prompt + ReAct + Agent。
- 输出格式要求极高、风格固定:在 RAG/Agent 基础上做 LoRA 微调。
- 低资源部署:GGUF 量化 + llama.cpp,必要时做 QAT。
9.2 数据与配置管理
这里特别强调三条工程纪律:
- 模型文件、向量库、配置文件要通过版本管理工具管理,记录每一次变更。
- 微调实验要保留 base model、LoRA 权重、训练数据、评测结果四件套,方便回溯。
- 提示词不要散落在业务代码中,集中放在配置中心或单独的模板文件里。
9.3 安全与合规
大模型落地时,安全边界一定要提前规划:
- 对用户输入和模型输出做内容安全过滤。
- 不要直接使用来源不明的“去限制”或“uncensored”模型,这类模型往往存在严重合规风险,也可能被植入恶意指令。
- 涉及生产环境变更时,先备份模型和向量库,先在测试环境验证。
- 对 Agent 的工具权限做最小化授权,不要让模型拥有任意执行命令的权限。
9.4 性能优化
性能优化可以从几个层面入手:
- 推理层:使用 vLLM 替代原始 Transformers 推理,提升吞吐量。
- 检索层:为 FAISS 索配置 GPU 版本,或者换用 Milvus 等分布式向量库。
- 缓存层:对高频问题做语义缓存,相同或相似问题直接返回缓存结果。
- 并发层:把模型服务拆成独立集群,避免与业务服务互相影响。
10. 总结与下一步学习路线
一轮走下来,你已经能搭建一个“本地模型 + Prompt 调优 + RAG 知识库 + 简单 Agent”的完整链路。这套链路覆盖了目前大模型应用开发的绝大多数高频场景。
下一步建议按这个顺序继续深入:
- 先动手跑通 Ollama 或 llama.cpp 部署,把模型调用接口调熟。
- 找一个真实的业务文档,做 RAG,反复调整切块策略,观察检索效果。
- 尝试用 LoRA 微调一个特定任务,比如日志改写、工单分类,体验从数据准备到权重合并的完整流程。
- 再尝试把 Agent 接到你的工具系统,比如数据库查询、HTTP API 调用,注意加上步数上限和异常处理。
- 最后可以考虑深入学习 vLLM 推理优化、分布式微调、QAT 等工程方向。
大模型应用开发的核心不是“背 API”,而是搞清楚每一层方案解决什么问题、成本是多少、边界在哪里。建议先跑通最小闭环,再逐步叠加模块,这样遇到问题时才不会一团乱麻。如果本文对你有帮助,可以收藏备用,后续我也会继续补充更多关于 Qwen 系列微调和 RAG 实战的细节。