
核心判断: 调用一次 Chat Completions API 不等于“会做大模型应用”。真正拉开差距的,是 RAG 检索质量、Agent 工具调用的稳定性、微调数据的构建能力,以及结构化输出与评估监控的闭环。本文沿“检索 → 行动 → 适配 → 部署 → 质量”五条工程主线,给出可直接运行的代码骨架。
通用模型的两条硬约束——知识有截止日期、私有文档进不了训练集——决定了 RAG 是企业落地的最短路径:先检索 Top-K 片段,再让模型基于片段生成。
最小可运行链路如下,覆盖加载、切分、向量化、检索、生成:
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_chroma import Chroma
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
docs = PyPDFLoader("handbook.pdf").load()
chunks = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=80
).split_documents(docs)
vectorstore = Chroma.from_documents(
chunks, OpenAIEmbeddings(model="text-embedding-3-small")
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
prompt = ChatPromptTemplate.from_messages([
("system", "只基于以下上下文作答。若上下文不含答案,直接说“未找到”。\n\n{context}"),
("human", "{question}"),
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = prompt | llm生产化至少再加两层:查询改写(低召回时用 LLM 重写 query)和兜底降级(无命中时走通用回答而非强行编造)。如果检索质量本身不过关,后面接再强的模型都是浪费 token。
Function Calling 的本质常被误解。模型不执行函数,只输出一段结构化调用请求;真正调函数、回填结果的是你的代码。循环是:发消息 → 模型返回 tool_calls → 你执行 → 结果塞回对话 → 模型总结
tools = [{
"type": "function",
"function": {
"name": "query_order",
"description": "当用户询问订单状态、物流进度时使用",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "订单号"}},
"required": ["order_id"],
},
},
}]
messages = [{"role": "user", "content": "帮我查订单 SN20260611001 到哪了"}]
resp = client.chat.completions.create(
model="qwen-max", messages=messages, tools=tools
)
msg = resp.choices[0].message
if msg.tool_calls:
tc = msg.tool_calls[0]
result = query_order(**json.loads(tc.function.arguments))
messages.append(msg) # 必须回传 assistant 的 tool_calls
messages.append({
"role": "tool",
"tool_call_id": tc.id, # id 必须原样带回
"content": json.dumps(result, ensure_ascii=False),
})
final = client.chat.completions.create(
model="qwen-max", messages=messages, tools=tools
)高频错误是忘记回传 tool_calls 消息,导致下一轮 tool_call_id 对不上。另一个值得多花时间的细节是工具描述:它定义的是“什么时候该用”,不是“这个函数做什么”,写得模糊,模型就会在不该调用的场景瞎调。
当输出风格、术语体系或格式约束无法靠 Prompt 稳定控制时,才轮到微调。LoRA 冻结基座权重,只在注意力模块旁路插入低秩矩阵,7B 模型的显存需求可从 60GB+ 降到 16GB 以内。
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
"your-base-model", device_map="auto", torch_dtype="auto"
)
config = LoraConfig(
r=8, lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05, task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
# 可训练参数通常在原模型的 0.1%–1% 量级数据格式用 JSONL 的 instruction / output 对即可,但质量比量级更重要:格式统一、长度适中、风格一致。训练完成后,LoRA 权重可单独保存为适配器,切换任务时无需重新加载基座。
推理层,vLLM 配合 FP8/NVFP4 量化能把显存占用和吞吐拉到可接受区间。服务层用 LangServe 的 add_routes 可以把任意 Runnable 挂到 FastAPI,自动生成 OpenAPI 文档。但比“能跑起来”更重要的是两个工程习惯:
结构化输出用 Pydantic 约束,不用裸 JSON。 裸 Prompt 输出的 JSON 可能在字段名、类型上四处漂移;response_format={"type":"json_object"} 只能保证合法 JSON,不保证符合 Schema。生产代码应经过 Pydantic 验证,并对拒答、截断、限流分别处理,而非静默替换成默认值。
上线前先量 RAG 质量。 RAGAS 的 faithfulness、answer_relevancy、context_recall 三个指标分别度量“有没有编”“答得切不切题”“该召回的有没有召回”,跑一轮基准测试的成本远低于线上翻车后再排查。
大模型应用的工程门槛不在“调通 API”,而在检索链路的召回质量、工具调用链的健壮性、微调数据的构建纪律,以及从第一天就把评估和结构化验证放进流水线。代码骨架搭起来只是起点,这四层做扎实了,系统才经得起真实流量。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。