第 15 章:实现本地知识库问答

0 阅读2分钟

第 15 章:实现本地知识库问答

本章目标

这一章把前面的模块串起来:文档切分、向量检索、Prompt 组装、模型回答。

本章效果

这一章完成本地知识库问答主链路:用户提问后,系统返回答案和引用来源。

本地知识库问答转存失败,建议直接上传图片文件

问答链路

本章要完成:

用户问题
  -> 生成问题 embedding
  -> 向量库检索 topK chunks
  -> 组装知识库上下文
  -> 调用模型
  -> 返回答案和引用来源

检索函数

export async function retrieveRelevantChunks(question: string) {
  const embeddings = createEmbeddings();
  const queryEmbedding = await embeddings.embedQuery(question);
  return searchRecords(queryEmbedding, 5);
}

组装上下文

export function buildRagContext(
  results: Array<{ record: VectorRecord; score: number }>
) {
  return results
    .map((item, index) => {
      return [
        `片段 ${index + 1}`,
        `来源:${item.record.metadata.source}`,
        `相似度:${item.score.toFixed(4)}`,
        item.record.content
      ].join("\n");
    })
    .join("\n\n");
}

RAG API

import { createChatModel } from "@/lib/ai/model";
import { KB_ASSISTANT_SYSTEM_PROMPT } from "@/lib/ai/prompts";

export async function POST(request: Request) {
  const { question } = await request.json();

  const results = await retrieveRelevantChunks(question);
  const context = buildRagContext(results);
  const model = await createChatModel();

  const answer = await model.invoke([
    { role: "system", content: KB_ASSISTANT_SYSTEM_PROMPT },
    {
      role: "user",
      content: `
请基于以下知识库片段回答问题。

知识库片段:
${context}

用户问题:
${question}
`
    }
  ]);

  return Response.json({
    answer: answer.text,
    sources: results.map((item) => ({
      title: item.record.metadata.title,
      source: item.record.metadata.source,
      score: item.score
    }))
  });
}

前端展示引用来源

function SourceList({ sources }: { sources: Array<{ title: string; score: number }> }) {
  return (
    <ul>
      {sources.map((source, index) => (
        <li key={index}>
          {source.title} · {source.score.toFixed(3)}
        </li>
      ))}
    </ul>
  );
}

引用来源可以折叠显示,避免干扰主要答案。

拒答策略

如果检索结果分数过低,可以拒答:

const bestScore = results[0]?.score ?? 0;

if (bestScore < 0.2) {
  return Response.json({
    answer: "知识库中没有找到足够相关的资料,暂时无法回答。",
    sources: []
  });
}

阈值要通过真实数据调试,不要拍脑袋定死。

实战任务

完成:

  • /api/kb/ask
  • 检索 topK chunk
  • 组装 Prompt
  • 返回 answer + sources
  • 前端展示答案和引用来源

常见坑

不要把相似度分数直接当成“准确率”。它只是检索相似度。

不要把低质量 chunk 塞给模型。错误上下文会诱导模型给出错误答案。

不要只返回答案。知识库问答必须返回 sources。

本章小结

我们完成了本地知识库问答的主链路。下一章会优化 RAG 效果,包括召回、重排和引用。