Django 搭建 AI 本地知识库:文档上传、向量检索与智能问答

Django 搭建 AI 本地知识库:文档上传、向量检索与智能问答

把 PDF 上传到服务器,再调用一次大模型,并不等于拥有"本地知识库"。一个真正可用的 Django 知识库至少要解决六件事:文件归属、文本切块、向量生成、相似度检索、权限过滤和答案引用。

本文以 PostgreSQL、pgvector 和 Django ORM 为主线,并把方案放进本地 RuyiDjangoCRM 的文档与多租户搜索边界中验证。最重要的结论是:先确定用户能看哪些文档,再在可见集合内做向量排序;不能先搜全库再补权限。

一条完整的知识库链路

上传后的原始文件仍然要保留,便于重新解析和审计;真正用于检索的是切块后的文本。每个文本块至少记录:

  • 原文档主键和组织主键;
  • 页码或章节位置;
  • 原始文本;
  • 嵌入模型名称与版本;
  • 固定维度的向量;
  • 内容哈希,避免重复入库。

安装扩展:

bash 复制代码
pip install pgvector psycopg[binary]

在 PostgreSQL 中启用向量类型:

sql 复制代码
CREATE EXTENSION IF NOT EXISTS vector;

用 Django 模型保存文本块和向量

python 复制代码
from django.db import models
from pgvector.django import HnswIndex, VectorField


class KnowledgeChunk(models.Model):
    org_id = models.UUIDField(db_index=True)
    document = models.ForeignKey("common.Document", on_delete=models.CASCADE)
    position = models.PositiveIntegerField()
    content = models.TextField()
    content_hash = models.CharField(max_length=64, db_index=True)
    embedding_model = models.CharField(max_length=80)
    embedding = VectorField(dimensions=1536)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["document", "content_hash"],
                name="uq_document_chunk_hash",
            )
        ]
        indexes = [
            HnswIndex(
                name="chunk_embedding_hnsw",
                fields=["embedding"],
                m=16,
                ef_construction=64,
                opclasses=["vector_cosine_ops"],
            )
        ]

向量维度必须和嵌入模型一致。换模型时不要悄悄覆盖旧向量,应记录模型版本并安排重建,否则同一列里混入不同向量空间,排序结果没有意义。

切块不是越碎越好

固定字符数切块适合做最小实验,但生产系统应优先保留标题、段落、列表和代码块等结构。块太大,召回内容噪声多;块太小,语义被截断,还会增加向量数量和成本。

python 复制代码
def chunk_text(text: str, size: int = 800, overlap: int = 120):
    start = 0
    while start < len(text):
        yield text[start:start + size]
        start += size - overlap

这个函数足以验证流程,但面对 Markdown、PDF 表格和代码文档时,应替换为结构化解析器,并把页码或标题锚点一起保存。

先做权限过滤,再做向量排序

RuyiDjangoCRM 的真实文档查询会同时检查组织、创建者、被分享用户和团队。向量检索必须复用同一套可见性条件:

python 复制代码
from django.db.models import Q
from pgvector.django import CosineDistance


def search_chunks(profile, query_vector, limit=5):
    visible_documents = Document.objects.filter(
        org=profile.org,
        status="active",
    ).filter(
        Q(created_by=profile.user)
        | Q(shared_to=profile)
        | Q(teams__in=profile.teams.all())
    )

    return (
        KnowledgeChunk.objects
        .filter(org_id=profile.org_id, document__in=visible_documents)
        .annotate(distance=CosineDistance("embedding", query_vector))
        .order_by("distance")[:limit]
    )

如果先从全库取前 20 个相似块,再在 Python 中删除无权访问的结果,既可能泄露标题、分数和片段,也可能导致合法结果被越权结果挤出前 20 名。权限条件必须进入数据库查询本身。

管理员分支也要显式处理。不要为了"方便"把所有知识库查询统一成管理员视角,更不能依赖前端隐藏结果。

让回答带上可核验引用

检索结果不要只拼正文,还要给每一块分配稳定引用编号:

python 复制代码
context = "\n\n".join(
    f"[{i}] {chunk.document.title} / 片段 {chunk.position}\n{chunk.content}"
    for i, chunk in enumerate(chunks, start=1)
)

prompt = f"""
只依据下面资料回答。每个关键结论使用 [1] 这样的编号引用;
资料不足时明确说不知道,不要补写不存在的事实。

{context}
"""

引用不是装饰。服务端还应保存本次回答命中的块主键、距离和文档版本,以便用户点击原文,也便于后续复盘错误召回。

本地验证暴露出的两个误区

我用一个可重复的关键词向量实验验证了上传后切成 3 块、余弦排序、返回引用片段的完整链路,相关 11 项单元测试全部通过。这个实验故意不冒充真实语义模型:关键词向量只能证明检索管线正确,不能证明语义质量。

第二个误区是只测"能搜到"。RuyiDjangoCRM 的搜索测试还验证了跨组织隔离、普通用户只能看到自己创建或被分配的对象,以及组织内知识条目的可见规则。AI 知识库接入后,这些测试应继续存在,并增加"越权向量即使更相似也不能返回"的回归用例。

结论

一个可靠的本地知识库,不是模型回答得像不像人,而是每条材料从哪里来、谁有权看到、命中了哪一段、换模型后如何重建,都能被解释和复现。

参考资料

相关推荐
何以解忧,唯有..6 分钟前
Python time 模块详解:时间处理与日期操作指南
开发语言·python
淼澄研学7 分钟前
宇树H1机器人控制原理与Python SDK实操接入指南
人工智能
帅哥的AI自修课14 分钟前
弥合RAG语义鸿沟——从知识图谱到跨模态对齐的工程实践
人工智能·知识图谱
梅雅达编程笔记18 分钟前
Day 18 · 综合实战 B:AI 客服 Agent(专栏收官)
python·智能客服·ai agent·意图识别·ai客服·大模型应用·ai办公自动化
测试老哥39 分钟前
Pytest自动化测试框架tep环境变量、fixtures、用例三者之间的关系
自动化测试·软件测试·python·测试工具·测试用例·pytest·职场发展
Buke..1 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(下):OLLVM 混淆还原 sign 算法
java·javascript·爬虫·python·算法
'pi%'1 小时前
RAG系统的“最后一公里“:数据清洗、特征工程与向量化策略的工程化实践
人工智能
JacksonMx1 小时前
Java CompletableFuture 异步编程实战:从入门到架构师避坑指南
linux·数据库·python
tachibana21 小时前
性能指标的口径选择
数据库·人工智能·架构·大模型·llm
HIT_Weston1 小时前
183、【Agent】【OpenCode】TuiThreadCmd(JS&TS 历史)
人工智能·agent·opencode