静态缓存页面 · 查看动态版本 · 登录
智柴网 登录 | 注册
← 返回话题
Q
QianXun @QianXun · 2025-11-24 03:09

《当知识开始“长记性”:一个小型 AI 知识库是怎样醒过来的》

想象一下,你有一位记性超好的助理:跟 TA 讲过的事情,过几天再问,还能带着上下文清清楚楚地复述给你听。上面这段 Python 脚本,其实就在做这样的事——它给一个 LLM(DeepSeek 聊天模型)装上了“长期记忆”,让模型不再只是一次性对话,而是可以依托自己的知识库来回答问题。

下面我们拆开来看,这个“小型 AI 知识库系统”是如何一步一步搭起来的。

---

🧱 整体结构:三块积木拼出一个“有记忆”的 Agent

从代码结构上看,一共三块核心组件:

1. 嵌入模型(Embedding) + 向量数据库(Chroma) 负责把文本“压缩”成向量,并存入一个可持久化的向量库。 2. 知识库封装(Knowledge + SqliteDb + ChromaDb) 负责统一管理“原始文本内容 + 向量索引”。 3. Agent(DeepSeek 聊天模型 + Knowledge) 负责真正和用户对话、从知识库中检索信息并组织回答。

主流程在 main() 里:

def main():
    # 1. 创建知识库(向量库 + 内容库)
    knowledge_base = create_knowledge_base()
    
    # 2. 创建 DeepSeek 聊天模型
    model = create_agent()
    
    # 3. 用这个模型 + 知识库,创建 Agent
    agent = Agent(
        model=model,
        knowledge=knowledge_base,
        instructions="你是一个知识专家,可以基于提供的知识库回答问题。",
    )
    
    # 4. 往知识库里塞几条示例知识
    ...
    
    # 5. 让 Agent 基于知识库回答问题
    ...
    
    # 6. 直接对知识库做搜索
    ...

这就相当于:

  • create_knowledge_base() = 给助理准备“记忆系统”;
  • create_agent() = 找一个会说话的大脑(LLM);
  • Agent(...) = 把这俩组装成能聊天、又有记忆的“全职助理”。
---

🧬 本地嵌入模型:给文字“测 DNA”

1. 为什么需要“嵌入(Embedding)”?

向量数据库不是直接存字符串,而是存向量。 所以必须先把文本变成向量,这个过程就像是:对每句话做一次“DNA 测试”,输出一串能代表语义的数字序列。

在代码中,这一步由本地的 SentenceTransformerEmbedder 完成:

from agno.knowledge.embedder.sentence_transformer import SentenceTransformerEmbedder

embedder = SentenceTransformerEmbedder(
    id="sentence-transformers/all-MiniLM-L6-v2",  # 轻量级模型,适合中文
    dimensions=384,  # 该模型的输出维度
)

> 注解 > Sentence Transformers 是一个专门做文本向量化的模型家族,all-MiniLM-L6-v2 是其中相对轻量的一款。 > 384 维向量意味着:每段文本会被映射到一个 384 维的“语义坐标系”中。

2. 完全本地,无需 API

这里的嵌入模型是本地加载,没有调用任何云端 API:

  • 优点:不依赖网络,不消耗外部 API 额度,隐私友好;
  • 代价:机器上要能装得下模型(但这个模型非常小,一般电脑可以轻松跑)。
代码里也做了基本的异常处理:

except ImportError:
    print("❌ 未安装 sentence-transformers 包")
    print("请运行: pip install sentence-transformers")
    return None
except Exception as e:
    print(f"❌ 加载嵌入模型失败: {e}")
    return None

这相当于在启动“记忆系统”之前先检查下:我有没有给自己装上“理解语义的眼镜”?

---

🗂️ Chroma 向量数据库:给记忆找个“仓库”

1. 创建 ChromaDb 实例

有了嵌入器,下一步就是找个地方放向量。这就是 Chroma 的工作:

vector_db = ChromaDb(
    collection="knowledge_base",
    path="tmp/chromadb",
    persistent_client=True,  # 启用持久化存储
    embedder=embedder,
)

几个关键信息:

  • collection="knowledge_base":相当于一个“知识库名字空间”,方便分类管理。
  • path="tmp/chromadb":向量索引会持久化在这个目录里,下次运行还能用。
  • persistent_client=True:开启持久化,而不是每次内存临时玩一把就扔了。
  • embedder=embedder:告诉 Chroma:怎么把文本变成向量,由这个嵌入器说了算
> 注解 > 向量数据库的核心能力是: > 给定一个查询向量,快速找到“向量空间里最接近的那些向量”——这就是语义检索的基础。

---

📚 Knowledge:把“向量索引 + 原文内容”绑在一起

只存向量还不够,用户问问题时,你得能把原始文本拿出来展示。这就是 Knowledge 的职责:

knowledge_base = Knowledge(
    vector_db=vector_db,
    contents_db=SqliteDb(db_file="knowledge_contents.db")
)

这里用了两个“数据库”:

1. vector_db:Chroma,用来高效做相似度搜索; 2. contents_db:SQLite,用来存每一条知识的原始文本内容。

你可以把它想象成:

  • Chroma 是“目录卡片 + 文本特征”;
  • SQLite 是“真正的书本内容”;
  • Knowledge 是那位熟悉图书馆结构的管理员,帮你统一操作。

代码中的两个典型操作

1. 添加知识add_content):

for i, text in enumerate(sample_texts):
    knowledge_base.add_content(text_content=text)

背后流程大致是:

  • 调用 embedder → 文本转向量;
  • 向量写入 Chroma;
  • 文本写入 SQLite;
  • 建立 ID 关联。
2. 搜索知识search):

search_results = knowledge_base.search("编程语言", max_results=2)
for doc in search_results:
    print(doc.content[:100], "...")

背后流程大致是:

  • 把查询 "编程语言" 做嵌入 → 得到查询向量;
  • 在 Chroma 中做近邻搜索 → 找最相近的几条向量;
  • 根据 ID 从 SQLite 拉回原始文本;
  • doc 的形式返回给你。
---

🤖 DeepSeek 聊天模型:给系统安上“嘴”和“脑子”

前面我们解决的是“记忆”和“检索”,现在需要一个会说话、会推理的主体,这里用的是 DeepSeek 聊天模型:

from agno.models.deepseek import DeepSeek

def create_agent():
    api_key = os.getenv("DEEPSEEK_API_KEY") or os.getenv("OPENAI_API_KEY")
    
    model = DeepSeek(
        id="deepseek-chat",
        api_key=api_key,
    )
    
    return model

这里的逻辑:

  • 优先从环境变量中拿 DEEPSEEK_API_KEY
  • 如果没有,就尝试 OPENAI_API_KEY(兼容某些代理/转发场景);
  • 拿不到的话照样创建模型,只是可能会在请求时失败。
你可以理解为: “记忆系统”是本地搭的; “思考和回答”的大脑则是云端的 DeepSeek LLM。

---

🧠 Agent:当大脑学会“先查资料,再开口”

1. 组装 Agent

agent = Agent(
    model=model,
    knowledge=knowledge_base,
    instructions="你是一个知识专家,可以基于提供的知识库回答问题。",
)

这里,Agent 扮演的角色就是:

  • 收到一个用户问题;
  • 先去知识库里检索相关内容;
  • 再把这些内容 + 问题,一起丢给 DeepSeek 模型;
  • 用 prompt 里的角色设定引导模型:“你是知识专家,要基于提供的知识库回答”。
这就从“裸 LLM”升级为“有记忆 + 有资料”的专家型助手。

2. 使用 Agent 回答问题

agent.print_response("请介绍一下 Python 的历史。")

按理想路径,底层流程大致是:

1. 对问题做嵌入,去 knowledge_base 搜索相关文本; 2. 把检索到的一两段关于 Python 的说明作为“上下文”; 3. 再把这些上下文 + 原问题,丢给 DeepSeek; 4. DeepSeek 结合上下文生成一段更流畅、结构化的中文回答; 5. print_response 负责把这段回答打印出来。

> 注解 > 这就是典型的 RAG(Retrieval-Augmented Generation,检索增强生成)模式: > 模型不再“凭空想象”,而是先去你专门指定的知识库查资料,再组织回答。

---

🔍 直接搜索:验证知识库是否“记住了东西”

最后一段是直接对知识库做检索:

search_results = knowledge_base.search("编程语言", max_results=2)
print(f"找到 {len(search_results)} 条相关结果:")
for i, doc in enumerate(search_results):
    print(f"{i+1}. {doc.content[:100]}...")

在前面添加的示例文本中,有三条:

  • Python 是一种高级编程语言…
  • JavaScript 是一种脚本语言…
  • 机器学习是人工智能的一个分支…
用“编程语言”搜索,很大概率会把前两条捞出来。这是一个非常直观的自测: 向量检索有没有工作?中文/中英混合检索效果怎样?

---

🛠 可以考虑的改进与拓展

这段脚本已经是一个完整、可跑的小 demo,不过从“科研工程师”的角度看,可以扩展的地方还有不少:

1. 更完善的错误处理与日志

现在大部分错误是 print 一行,然后 return None。在真实系统里可以:

  • 区分“致命错误”(比如向量库不可写)和“可恢复错误”(比如某条内容失败);
  • 使用 logging 模块,打 INFO / WARNING / ERROR 级别日志;
  • 对知识添加失败时,记录文本 ID,方便后续重试。

2. 更适合中文的嵌入模型

all-MiniLM-L6-v2 虽然也能处理中文,但不是专门为中文设计。 对于中文知识库,可以考虑:

  • sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2(多语言);
  • 或本地的中文专用 Embedding 模型(如 bge-m3/bge-large-zh 等,视 Agno 支持情况)。
只要同步修改:

id="xxx-模型名",
dimensions=向量维度,

即可。

3. 知识分片与元数据

当前示例用的是“几句话直接当一条内容”。现实中:

  • 文档往往很长,需要按段落/小节切分成多条;
  • 每条内容需要额外的 metadata:来源文件名、章节、时间戳等;
  • 搜索时可以按 metadata 过滤,比如只查“某个项目的文档”、“某个时间之后的内容”。
如果 Agno 的 add_content 支持 metadata,可以这样扩展:

knowledge_base.add_content(
    text_content=text,
    metadata={"source": "demo", "topic": "programming"}
)

4. Agent 的指令(instructions)可以更精细

现在的 instructions 很简单:

instructions="你是一个知识专家,可以基于提供的知识库回答问题。"

可以更具体一些,例如:

  • 优先引用知识库内容,如无相关内容要明确说明“知识库中暂无此信息”;
  • 回答时用分点说明,标明知识来源;
  • 避免超出知识库范围的主观猜测。
---

👍 1