《从零搭建RAG系统第6天:查询LLM生成问题答案》
大家好,我是老赵,一名程序员老兵,平时主要从事企业级应用开发,最近打算从零学习、并落地一套完整的RAG检索增强生成系统。不搞虚头理论,全程边学边做,把遇到的问题、踩过的坑、能直接跑通的代码,全都真实记录下来。
前五天我们完成了环境搭建、Milvus部署、文档向量化以及检索匹配、并安装了Ollama在本地运行LLM模型。今天第6天的目标是将优化后的检索片段交给大模型(LLM),让模型基于检索到的上下文生成准确、简练的回答,完成RAG的最后一段闭环。
一、今日目标
- 将第4天优化后的检索片段与用户问题拼装成高质量prompt;
- 调用LLM:本地轻量模型qwen3:4b;
- 提供后处理与答案可信度控制技巧(来源标注、简短回答);
- 给出可直接运行的
qa_pipeline .py完整示例。
二、前置准备(衔接 Day4)
- 环境:已激活
rag-env、Milvus 已运行且第3天的向量已存入; - 第4天脚本已能输出
optimized_results(格式为列表,包含键匹配度和文档片段); - 可选:如果使用云 API,请准备好相应的 API Key(本文优先推荐本地方式以降低门槛)。
三、关键思路(简要)
- 从
optimized_results中选取若干(通常 1~3 个)最高相关片段作为上下文; - 使用明确的Prompt模板,引导LLM基于上下文回答,并要求给出引用来源;
- 设定模型生成参数(如
temperature、max_tokens)以控制答案的创造性与长度; - 对生成结果做简单后处理:裁剪、去重、对匹配度低的结果触发回退策略(如提示“未找到足够证据”)。
四、Prompt 模板(参考,可调整)
你是一个专业的技术文档助手。下面给出一些文档片段和用户问题,请基于这些片段给出准确、简短的回答(最多3段),并在末尾用“来源:”标注使用到的片段序号。
上下文片段:
1. 【片段1内容】
2. 【片段2内容】
...
用户问题:{question}
要求:
- 以简洁的中文回答问题;
- 若证据不足,请说明“未检索到足够相关内容”;
- 在回答末尾列出“来源:片段1, 片段2”形式的引用。
五、实现(代码示例)
下面的示例假设你已在 question_retrieval.py 中得到了 optimized_results,接下来我们把生成逻辑独立成 question_answering.py。
import sys
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama.llms import OllamaLLM
class CustomStreamingHandler(BaseCallbackHandler):
def on_llm_new_token(self, token, **kwargs):
sys.stdout.write(token)
sys.stdout.flush()
template = """你是一个专业的技术文档助手。参考下面的上下文片段回答问题,回答要简明扼要。\n\n
上下文片段:\n{ctx_text}\n\n
用户问题:{question}\n\n
要求:用中文回答,若证据不足请说明。最后标注来源片段编号。"""
prompt = ChatPromptTemplate.from_template(template)
model = OllamaLLM(base_url="http://localhost:11434",
model="llama3.2:latest",
callbacks=[CustomStreamingHandler()])
chain = prompt | model
optimized_results = [
{"匹配度": 0.12, "文档片段": "RAG 是检索增强生成框架,将检索到的相关文档作为上下文提供给 LLM。"},
{"匹配度": 0.33, "文档片段": "RAG 可以降低模型幻觉,提高事实性回答。"}
]
contexts = [r['文档片段'] for r in optimized_results[:3]]
chain.invoke({"question": "什么是RAG?", "ctx_text": "\n\n".join(contexts)})
六、后处理与可信度控制
- 源证据标注:在Prompt中强制要求模型列出使用到的片段编号,便于溯源与人工审核;
- 回退策略:当
optimized_results为空或匹配度较差(如平均距离>阈值),直接返回“未检索到足够相关信息”,避免模型凭空生成错误信息; - 分段生成:对于复杂问题,可先要求模型给出“摘要+参考片段”,再要求补充细节,降低hallucination;
- 多模型对比(可选):并行调用两个不同模型,若输出一致可信度更高;若不一致,触发人工审阅。
七、常见问题与解决方案
- 若模型回答无证据或回答错误:检查上下文片段是否覆盖问题关键词,尝试扩大
limit或降低chunk_size后重新向Milvus载入; - 回答啰嗦或生成多余信息:把
temperature设为0.0并在prompt中明确“最多3段”; - 本地模型显存不够:使用量化模型或把
device改为-1(CPU),并减小max_length。
八、完整示例(整合Day4的检索与Day6的生成,qa_pipeline.py)
示例给出一个从检索到生成的完整小脚本,便于本地跑通:
# qa_pipeline.py - 端到端示例(检索 + 生成)
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import OllamaLLM
from pymilvus import connections, Collection
from sentence_transformers import SentenceTransformer
from mfj_langchain.custom_streaming_handler import CustomStreamingHandler
def retrieve(question, top_k=3):
connections.connect(alias='default', host='localhost', port='19530')
collection_name = 'rag_test_collection'
collection = Collection(collection_name)
collection.load()
embedder = SentenceTransformer('BAAI/bge-small-zh')
q_emb = embedder.encode(question)
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = collection.search(data=[q_emb], anns_field='doc_embedding', limit=top_k, param=search_params, output_fields=['doc_text'])
connections.disconnect('default')
optimized_results = [r.entity.get('doc_text') for r in results[0] if r.distance <= 0.6]
return optimized_results
def generate_answer_local(question, contexts):
model = OllamaLLM(base_url="http://localhost:11434",
model="qwen3:4b",
temperature=0.0,
callbacks=[CustomStreamingHandler()])
prompt = build_prompt_template()
chain = prompt | model
chain.invoke({"question": question, "ctx_text": "\n\n".join(contexts)})
def build_prompt_template():
template = """"你是一个专业的技术文档助手。参考下面的上下文片段回答问题,回答要简明扼要。\n\n
上下文片段:\n{ctx_text}\n\n
用户问题:{question}\n\n
要求:用中文回答,若证据不足请说明。最后标注来源片段编号。"""
prompt = ChatPromptTemplate.from_template(template)
return prompt
if __name__ == '__main__':
q = "什么是RAG?"
contexts = retrieve(q, top_k=3)
if not contexts:
print("未检索到足够相关内容,无法生成答案。")
else:
generate_answer_local(q, contexts)
九、今日小结
- 第6天主要完成“检索结果 → LLM生成答案”的接入与实战示例;
- 推荐先用本地轻量模型验证流程,再迁移到云端模型以提升效果;
- 关键调参点:检索阈值(Day4)、生成时
temperature与max_tokens; - 下一步:使用FastAPI搭建API接口,实现远程调用。
我是老赵,一名程序员老兵,全程真实记录RAG从零搭建全过程。本系列会持续更新,最后整理成完整视频教程+源码+部署手册。
关注 老赵全栈实战,不迷路,一起从0落地RAG系统。