要让 Nemotron-Labs-Diffusion 基于自有文档回答问题,不能直接把文档全文塞进提示词。先要把文档切成可检索的片段,用向量表示后建索引;每次提问时取回最相关的几个片段,再让生成模型在限定上下文里作答。这套流程在城市内部文档问答、产品说明书问答等场景中比较通用,关键是控制切分粒度、检索数量和上下文长度。
适用场景:本地文档库规模不大,回答需要能追溯到原文。操作动作:先切分文档,用本地向量模型生成表征,存入 FAISS;提问时检索 top-k 片段,拼入提示词并限制总长度。验证方式:打印检索片段与最终回答进行对照。风险边界:检索召回不全会导致答非所问,提示词过长会超出模型上下文窗口。
准备文档切分与向量化工具
文档切分决定后续检索的粒度。通常按固定字符数切,并保留少量重叠,防止一句话被截成两段。LangChain 的 RecursiveCharacterTextSplitter 可以按分隔符递归切;纯 Python 也可以按段落或句子手工切。建议先按 400-600 字试,重叠 50 字左右,具体数值需要结合文档类型确认。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=['\n\n', '\n', '。', '!', '?', ' ', '']
)
chunks = splitter.split_text(raw_document)
向量化模型的选择原则:优先选中文效果稳定的本地 embedding 模型,例如 BAAI/bge-small-zh-v1.5 或同级别模型;模型输出维度会直接决定索引大小,维度越高检索越慢。如果文档是专业领域内容,建议用领域语料微调过的 embedding 模型,否则用通用模型先验证流程。
构建本地向量索引并保存
用 FAISS 做本地索引是成本最低的起步方式。它将向量保存在内存中,适合百万级以内的小型文档库。需要同时保存原始片段列表,否则检索结果无法映射回文本。
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
embeddings = model.encode(chunks)
dim = embeddings.shape[1]
index = faiss.IndexFlatIP(dim) # 使用内积,向量已归一化时等价余弦相似度
index.add(np.asarray(embeddings, dtype='float32'))
faiss.write_index(index, 'doc_index.faiss')
np.save('chunks.npy', np.array(chunks, dtype=object))
如果后续要换用其他向量数据库,这段代码中只有“初始化索引”和“写入向量”两个动作需要替换,不需要改动切分逻辑。
实现检索函数返回相关片段
检索时先把问题转成向量,再在索引中搜索最相似的向量。FAISS 的 search 返回得分和下标,用下标从保存的片段列表里取出原文。
def retrieve(question, k=5):
q_vec = model.encode([question])
q_vec = q_vec / np.linalg.norm(q_vec, axis=1, keepdims=True)
scores, indices = index.search(q_vec.astype('float32'), k)
return [chunks[i] for i in indices[0]]
top-k 控制每次取回多少片段。k 太小可能漏掉关键信息,k 太大则无关内容增多,提示词也会变长。建议先从 5 开始,观察回答质量后再调整。
将检索结果与用户问题拼装进提示词
把检索到的片段放入提示词的“参考资料”区域,再给出明确指令,让 Nemotron-Labs-Diffusion 只依据资料回答。下面是一个可直接替换的模板。
prompt_template = '''以下是参考资料:
{context}
用户问题:{question}
请只根据参考资料回答。如果参考资料中没有答案,请回答“资料中未找到相关信息”。'''
上下文超长时需要截断。常见做法是限制总字符数,按检索得分从高到低加入,超出上限就停止,保证模型输入在可处理范围内。
def build_prompt(question, k=5, max_chars=2500):
refs = retrieve(question, k)
context = ''
for ref in refs:
if len(context) + len(ref) > max_chars:
break
context += ref + '\n'
return prompt_template.format(context=context, question=question), refs
启动完整问答服务并验证引用来源
最后把上述步骤串成一个函数。Nemotron-Labs-Diffusion 的生成接口需要结合本地部署方式调整,这里用一个通用的 generate 函数占位。运行后同时打印引用片段和回答,用来核对回答是否真正基于检索内容。
def generate(prompt):
# 用已部署的 Nemotron-Labs-Diffusion 生成回答,返回字符串
return model_generate(prompt)
def answer(question):
prompt, refs = build_prompt(question, k=5)
output = generate(prompt)
print('引用片段:')
for i, ref in enumerate(refs, 1):
print(f'{i}. {ref}')
print('回答:', output)
return output
answer('公司报销流程中需要哪些附件?')
验证时重点看回答中的关键信息是否能在引用片段里找到。如果回答内容不在引用片段中,说明检索召回有问题或生成模型没有遵守“只根据资料回答”的指令。建议先固定测试几组问题,确认引用与回答的对应关系后,再接入服务。