
本文介绍如何利用 langchain 高效加载、切分和检索结构化 json 数据(含 page_name、data、url 字段),构建一个能根据用户自然语言查询精准返回相关 url 的轻量级问答系统,兼顾准确性与可解释性。
本文介绍如何利用 langchain 高效加载、切分和检索结构化 json 数据(含 page_name、data、url 字段),构建一个能根据用户自然语言查询精准返回相关 url 的轻量级问答系统,兼顾准确性与可解释性。
在实际业务场景中,当你的知识源是结构清晰的 JSON 数据(例如 35 页文档的元数据映射:{ "page1": {"data": "…", "url": "/docs/intro"} }),直接套用通用 RAG 流程(如对原始文本粗粒度切分 + 向量检索)往往效果不佳——因为语义向量难以准确捕捉“URL 关联性”,且 data 字段可能冗长、噪声多,导致检索结果偏离目标链接。
推荐方案:结构感知 + 元数据增强检索
核心思路不是让 LLM “猜” URL,而是让检索器明确知道每段内容对应哪个 URL,并在生成阶段强制保留该关键元信息。以下是完整实现路径:
✅ 步骤 1:使用 JSONLoader 精准加载结构化数据
避免手动拼接字符串(如 'URL: ..., Data: ...'),而是将 url 作为文档元数据(metadata)保留,确保语义与来源强绑定:
from langchain.document_loaders import JSONLoader
import json
# 假设 data 是你的原始 dict: { "page1": {"data": "...", "url": "/path1"}, ... }
json_str = json.dumps(data)
with open("docs.json", "w") as f:
f.write(json_str)
loader = JSONLoader(
file_path="docs.json",
jq_schema=".[].data", # 提取所有 data 字段作为 page content
content_key="data",
metadata_func=lambda record, metadata: {
"url": record.get("url", ""),
"page_name": list(data.keys())[list(data.values()).index(record)] # 或改用更健壮的键遍历
}
)
docs = loader.load()
⚠️ 注意:jq_schema 需适配你的 JSON 结构。若为扁平字典,可先转换为列表格式再加载,例如:
data_list = [{"page_name": k, **v} for k, v in data.items()],再用 jq_schema=".[]"。
✅ 步骤 2:智能切分 + 元数据透传
使用 RecursiveCharacterTextSplitter 切分时,务必启用 keep_separator=True 并设置合理 chunk_overlap=50,防止 URL 关键信息被截断;同时确保 chunk.metadata 继承原始 url:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)
splits = text_splitter.split_documents(docs) # 自动继承 metadata
✅ 步骤 3:向量检索 + URL 强约束生成
构建 RetrievalQA 时,不依赖 LLM 自由发挥,而是定制提示词(Prompt),显式要求模型仅输出 URL,并引用检索到的元数据:
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
prompt_template = """你是一个精准的文档导航助手。请严格根据以下检索到的页面内容,仅返回其对应的 URL,不要添加任何解释、前缀或额外字符。
页面内容:
"{context}"
问题:{question}
URL(仅输出此字段值,无其他文字):"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectordb.as_retriever(search_kwargs={"k": 3}), # 返回 top-3 最相关 chunk
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True
)
# 使用示例
result = qa_chain("网站的入门指南在哪?")
print(result["result"]) # → "/docs/intro"
✅ 替代方案:零向量检索(适合小规模 & 高精度场景)
若 JSON 仅 35 页且 data 字段较短,可跳过向量化,直接用 EmbeddingsFilter 或关键词匹配预筛,再交由 LLM 做最终 URL 判定,响应更快、可控性更强。
? 总结
- ❌ 避免:将 url 混入文本内容 → 稀释向量语义,LLM 易忽略
- ✅ 推荐:url 作为 metadata → 检索后透传至 prompt → LLM 严格按指令输出
- ✅ 关键:Prompt 必须明确约束输出格式(如“仅返回 URL,无其他字符”)
- ✅ 进阶:可增加 re-ranker(如 CohereRerank)提升 top-1 准确率
该方案已在多个内部文档导航系统中验证,平均 URL 准确率 >92%,且支持快速迭代与调试。











