第 38 章 · Agno Knowledge 与 RAG

手册在那里,模型却不知道

还是那个跨境卖家。他有一份厚厚的《选品与合规手册》——哪些品类进哪个国家要认证、哪些材质在欧盟受限、锂电池玩具的运输和申报要求是什么。上一章接上工具和 MCP 之后,Agent 已经能联网搜索、能查自家销量,看起来“能干活”了。可他随手一问:“这款含锂电池的玩具能发德国吗?”

Agent 答得头头是道,却是编的。联网搜索能查到公开新闻,查不到他压在电脑里的这份内部手册;自定义工具能查销量,却没人告诉它这份 PDF 存在。模型的博学止步于训练数据和能调用的工具,公司内部资料它一无所知——这正是第 11 章开篇讲的问题:模型不知道你的私有资料。

第 11 章给出的答案是 RAG(检索增强生成):回答前先查资料,再基于资料作答。这一章要做的,是看 Agno 怎么把这套模式变成几行可运行的代码。

概念与主书映射:Agno 把 RAG 做成一等能力

第 11 章讲得很清楚:pi 不内建知识库,它把 RAG 当作一种“接入的能力”——可以做成工具(search_knowledge)、可以接一个 RAG 类 MCP Server、也可以打包成技能。三种方式共享同一个内核:Agent 本身不存知识,需要时“去查”,检索就是一次工具调用。

Agno 的取向不同。它把 Knowledge、Vector DB(向量数据库)、Embedder(嵌入模型)这些组件直接放进框架里,构成 Agno 五级能力模型里靠后的一级:给 Agent 装上外部知识。用 Agno 的话说,这是一等能力(first-class citizen)——不需要你自己写检索工具、自己管理向量库客户端,配置好 Knowledge 对象、挂到 Agent(knowledge=...) 上,剩下的检索、拼接上下文、生成回答都由框架接管。

落到术语上,Agno 和主书用的是同一套词汇,只是包装方式不同:

  • Embedding(文本向量):把文字转成能表示语义的一串数字。在 Agno 里对应 OpenAIEmbedder 之类的嵌入器,负责把文档片段和用户问题都转成向量。
  • Vector DB(向量数据库):存向量、支持“语义最接近”检索的数据库。Agno 支持多种向量库后端,LanceDb 是文档里最常见的一种,轻量、可本地跑。
  • 切片(Chunking):长文档要先切成小段再分别生成 Embedding。Agno 的知识库加载器在读取 PDF、网页等资料时会做这一步,具体切法可以配置。
  • Hybrid Search(混合检索):关键词匹配 + 语义匹配一起用。LanceDb 支持 SearchType.hybrid,查“CE 认证”这种精确术语和查“能不能安全出口”这种模糊问法都能兼顾。

这四个概念串起来,就是“建库 + 检索”两阶段的数据流,和第 11 章的描述完全一致——只是在 Agno 里,这条流水线被封装成了几个类的组合,而不是你手写的 embed() / vectorDb.search() 调用。

可运行的 Agno 代码

示例一:菜谱知识库(最小可跑通版本)

先看一个最小示例,来自 Agno 的常见用法:读取一份在线 PDF 菜谱,切片、嵌入、存进向量库,Agent 基于资料回答做菜问题。

from agno.agent import Agent
from agno.embedder.openai import OpenAIEmbedder
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.models.openai import OpenAIChat
from agno.vectordb.lancedb import LanceDb, SearchType

knowledge = PDFUrlKnowledgeBase(
    urls=["https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"],
    vector_db=LanceDb(
        uri="tmp/lancedb",                       # 本地向量库存储位置
        table_name="recipe_knowledge",
        search_type=SearchType.hybrid,           # 关键词 + 语义混合检索
        embedder=OpenAIEmbedder(id="text-embedding-3-small"),
    ),
)

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    knowledge=knowledge,
    instructions=[
        "你是一个中文资料问答助手。",
        "回答资料相关问题时,优先使用知识库内容。",
        "资料里没有就明确说明没有找到。",
    ],
    markdown=True,
)

# 首次运行需要显式加载知识库:解析 PDF → 切片 → 生成向量 → 写入 LanceDb
if agent.knowledge is not None:
    agent.knowledge.load()

agent.print_response("如何制作 Pad Thai?", stream=True)

这段代码把第 11 章的“建库两阶段”封装了起来:PDFUrlKnowledgeBase 负责资料解析和切片,LanceDb + OpenAIEmbedder 负责生成向量并存储,agent.knowledge.load() 触发整条建库流程,之后每次 print_response 调用都会自动检索、拼接上下文、再回答。

示例二:跨境电商合规手册知识库

把同样的模式换到贯穿案例上——那份《选品与合规手册》。假设手册已经整理成公司内部可访问的 PDF(或多份 PDF),做法完全一致,只是资料源和 instructions 换成合规场景:

from agno.agent import Agent
from agno.embedder.openai import OpenAIEmbedder
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.models.openai import OpenAIChat
from agno.vectordb.lancedb import LanceDb, SearchType

compliance_knowledge = PDFUrlKnowledgeBase(
    urls=[
        "https://internal.example.com/docs/selection-compliance-manual.pdf",
        "https://internal.example.com/docs/eu-battery-regulation.pdf",
    ],
    vector_db=LanceDb(
        uri="tmp/lancedb",
        table_name="compliance_knowledge",       # 单独一张表,和菜谱知识库物理隔离
        search_type=SearchType.hybrid,           # 型号、认证编号这类术语需要关键词精确匹配
        embedder=OpenAIEmbedder(id="text-embedding-3-small"),
    ),
)

compliance_agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    knowledge=compliance_knowledge,
    instructions=[
        "你是跨境电商合规助手,负责回答选品、认证、运输相关问题。",
        "回答必须基于知识库中的手册内容,禁止使用未经检索验证的常识。",
        "如果资料里没有明确条款,直接说明“手册中未找到相关依据”,不要猜测。",
        "涉及具体国家、认证标准(如 CE、UN38.3)时,注明来自手册的哪个章节。",
    ],
    markdown=True,
    show_tool_calls=True,     # 观察 Agent 是否真的触发了检索
)

if compliance_agent.knowledge is not None:
    compliance_agent.knowledge.load()

compliance_agent.print_response(
    "这款含锂电池的儿童玩具可以发德国吗?需要哪些认证?",
    stream=True,
)

和菜谱示例的差别不在 API,而在工程约束:table_name 单独隔离、instructions 里明确“没有依据就拒答”、show_tool_calls=True 便于确认检索确实发生了。这几点正是“能跑通的 demo”和“能上生产的 RAG”之间的距离,后面几节会展开。

示例三:结合已有工具,让 Agent 同时具备检索和查询能力

延续上一章的自定义工具,Knowledge 和 Tools 并不冲突——一个 Agent 可以同时具备“查手册”和“查销量”两种能力,模型自己判断该用哪个:

from agno.agent import Agent
from agno.models.openai import OpenAIChat

def query_sales(start: str, end: str, limit: int = 10) -> dict:
    """查询指定时间段内销量最高的 SKU。只读,不修改任何数据。"""
    rows = sales_db.readonly_top_skus(start=start, end=end, limit=limit)
    return {"rows": rows, "source": "sales_read_replica"}

research_agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    knowledge=compliance_knowledge,      # 复用示例二里建好的知识库
    tools=[query_sales],
    instructions=[
        "回答合规问题时查知识库;回答销量问题时查销量工具;两者都需要时都要查。",
        "合规结论必须引用手册依据,销量数据必须说明统计口径。",
    ],
    markdown=True,
    show_tool_calls=True,
)

research_agent.print_response(
    "这批含锂电池玩具最近卖得怎么样?如果要追加发德国的库存,有什么合规要求?",
    stream=True,
)

这一段呼应第 11 章“RAG 只是众多工具能力之一”的定位——即便 Agno 把 Knowledge 做成了一等能力,它在 Agent 眼里仍然是和其他工具并列的一种“可调用的信息来源”,而不是取代工具系统的独立机制。

深入一层:参数怎么选,和 pi 的做法有什么不同

Embedder 怎么选? 决定检索质量的第一个变量是嵌入模型。text-embedding-3-small 便宜、够用,适合大多数中英文混合的业务文档;如果资料里专业术语密集(法规条文、化学成分表),可以考虑更大的嵌入模型或者领域专用模型。这一步在 pi 里对应你自己调用 embed() 时选的模型——Agno 只是把这个选择放进了 OpenAIEmbedder(id=...) 这一个参数位。

Vector DB 怎么选? LanceDb 适合起步和中小规模场景,本地文件即可跑起来,不需要额外部署服务。生产规模变大、需要多副本或者更强的运维能力时,通常会换成托管的向量数据库服务。这个决策和 pi 里“你自己挑一个向量库、自己维护客户端”是同一个决策,只是 Agno 把切换成本压低到了换一个类的构造参数。

Hybrid Search 什么时候必须开? 只用语义检索,查“CE-EN71”这种精确编号命中率会明显下降,因为语义相近的词很多,编号却要求精确匹配。SearchType.hybrid 把关键词检索和语义检索的结果做融合,这正是第 11 章反复强调的“精确术语用关键词、模糊问法用语义”。合规手册、产品型号这类资料,hybrid 几乎是必选项,不是可选项。

引用追踪怎么做? 这是 Agno 默认封装和主书 pi 做法差别最大的地方。pi 的标准做法是让检索工具显式返回 {text, source, score, updatedAt} 这样的结构化字段(回顾第 11 章的返回结构),后续的引用展示、评测、观测都基于这些字段。Agno 把检索到回答这一步做得更“黑盒”——框架自动完成检索和上下文拼接,你不需要手写这部分逻辑,但也意味着如果要在最终回答里显式标注“这段话来自手册第几节”,需要你在 instructions 里明确要求模型引用来源,或者在知识库入库时把 source、章节信息作为文档元数据附加上去,让模型在生成时“看得到”这些信息。换句话说:pi 把引用信息当作检索结果的必选字段来设计;Agno 默认给你的是便捷,引用的颗粒度需要你自己在资料整理和 instructions 里补回来。

知识库更新怎么处理? agent.knowledge.load() 是一次性触发,資料变了要重新调用它才能反映最新内容(部分场景可以配置增量加载,避免每次全量重建)。这和 pi 里“把建库做成可重跑的流程”是同一个工程要求——区别只是 pi 需要你自己写这个重跑脚本,Agno 提供了 load() 这个统一入口。

动手看看

拿示例二的合规知识库做起点,做两个小实验:

  1. search_typeSearchType.hybrid 改成纯向量检索(如果 LanceDb 支持对应选项),问一次“UN38.3 认证要求”,对比命中的片段有什么变化——你会直观感受到 hybrid 对精确术语的帮助。
  2. compliance_agent 追加第二份资料(比如一份关于包装标识的 PDF),重新调用 load(),问一个跨两份文档的问题,观察 Agno 是否能同时检索到两份资料里的相关片段并综合回答。

实战中的几个坑

坑一:切片粒度不当,检索回来的片段要么太碎要么太大。

  • 现象:命中的资料片段读起来上下文不完整,或者一次塞进太长一段导致回答变得笼统。
  • 原因:知识库加载时的默认切片策略不一定适合你的文档结构,法规条文和菜谱说明的理想切片长度并不一样。
  • 对策:关注知识库加载器是否支持自定义切片参数,按语义单元(条款、小节)切,而不是按固定字数硬切。

坑二:以为接了 Knowledge 就自动会引用来源。

  • 现象:回答听起来很像基于资料,但完全看不出是手册第几节、哪份文档,无法核实真伪。
  • 原因:Agno 默认只是把检索到的内容拼进上下文让模型参考,不会自动在回答里标注来源,这一步需要显式要求。
  • 对策:在 instructions 里明确要求“注明引用的文档和章节”,并在文档入库前给内容打好元数据(文件名、版本号、章节标题)。

坑三:忘记调用 load(),或者资料更新后没有重新加载。

  • 现象:换了新版手册,Agent 还在按旧条款回答;或者第一次跑起来时 Agent 说“知识库里没有任何内容”。
  • 原因:load() 是显式触发的,不是资料一放进 urls 就自动生效;也不会自动感知源文件变化。
  • 对策:把“资料更新 → 重新 load()”做成可重复执行的运维步骤,而不是手工一次性操作。

坑四:把 Knowledge 和 Memory 混在一起用。

  • 现象:某次检索命中的政策片段,被误当作“用户偏好”或“长期事实”存进了记忆系统,后续对话里反复被引用,即使政策已经过期。
  • 原因:没分清 Knowledge(外部资料证据)和 Memory(用户偏好、长期事实)的边界,见下一节详述。
  • 对策:知识库命中的内容只应该进入本轮 Context,不要写入 Memory;需要长期记住的是“用户是谁、偏好什么”,不是“某次查到的资料内容”。

Knowledge 不是 Memory:四个概念的边界

Agno 初学者最容易把 Knowledge、Memory、Storage、Context 混在一起,尤其是在 Knowledge 已经“检索到内容”之后,很容易觉得“顺手存进记忆就好”。第 11 章和附录 B 的边界在 Agno 里同样适用,而且更要小心,因为框架把检索这一步做得很顺滑,容易让人忘了这是四个完全不同的机制:

概念回答的问题在 Agno 里的形态
Knowledge外部资料里有什么证据?PDFUrlKnowledgeBase + LanceDb 等,检索命中即证据
Memory下次还值得记住什么用户偏好/长期事实?Memory + enable_user_memories=True(第 39 章展开)
Storage这次聊过什么、状态如何恢复?SqliteStorage 等会话持久化(第 39 章展开)
Context这一轮模型实际看见什么?检索命中的片段被临时拼进本轮提示

一个具体的判断标准:知识库命中的片段,无论多么“重要”,都不应该自动流入长期记忆。政策、手册、产品资料会更新,今天检索命中的一段条款明天可能已经作废;而 Memory 应该保存的是相对稳定的东西——这个用户偏好哪类产品、上次沟通中确认过的业务规则。把二者分开管理,才不会让 Memory 变成一个装满过期资料的噪声仓库。

Agno vs 主书 pi 做法

能力Agno 怎么做pi(harness)怎么做各自适合谁
知识库定义Knowledge 对象(如 PDFUrlKnowledgeBase)+ vector_db= 一站式配置自己维护向量库客户端,封装 search_knowledge 工具Agno 适合快速搭建;pi 适合要完全掌控索引和检索逻辑的场景
检索触发方式挂到 Agent(knowledge=...),框架自动判断何时检索并拼接上下文作为工具/MCP/技能显式接入,检索即一次工具调用Agno 更省心;pi 让“是否检索、检索了什么”在 ReAct 轨迹里显式可见
Hybrid SearchLanceDb(search_type=SearchType.hybrid) 一个参数开启自己在检索服务层实现关键词 + 语义融合逻辑Agno 一行代码;pi 灵活但要自己写融合与排序
引用追踪需要在 instructions 和文档元数据里自行补齐来源信息检索结果结构化返回 {text, source, score, updatedAt},天然带来源pi 对“可审计”要求高的场景更省心;Agno 需要额外设计
知识库更新agent.knowledge.load() 显式重新加载把建库做成可重跑的独立流程/脚本两者都要求“更新可重跑”,Agno 提供了统一入口

分野和上一章一致:Agno 是应用框架,把 RAG 的“建库 + 检索 + 拼接上下文”整体封装成开箱即用的组件,几行代码就能有一个能回答资料问题的 Agent;pi 是 harness,把 RAG 当作“你接入的一种能力”,检索本身是 ReAct 循环里一次显式的、可审计的工具调用。两者背后是同一套 RAG 方法论,只是抽象层次不同——Agno 换来速度,代价是引用颗粒度、检索融合逻辑这些细节需要你在它的接口里主动补齐;pi 换来掌控力,代价是你要多写这部分工程代码。

小结

  1. Agno 把 Knowledge/RAG 做成一等能力PDFUrlKnowledgeBase + LanceDb + OpenAIEmbedder 几行代码就能搭出一个能检索资料的 Agent,对应主书第 11 章的 RAG 模式。
  2. 核心概念和主书一致:Embedding(语义向量)Vector DB(向量库)切片(Chunking)Hybrid Search(混合检索),只是被封装进了几个类的构造参数。
  3. Knowledge 不是 Memory:检索命中的片段只应进入本轮 Context,不该被当作长期偏好写进 Memory——四个概念(Knowledge/Memory/Storage/Context)各自回答不同的问题。
  4. 生产级 RAG 需要在 Agno 默认封装之上自己补齐引用来源、切片策略、更新机制——框架给的是便捷,工程边界仍要你设计。
  5. 和主书 pi 相比,Agno 换速度,pi 换掌控力:同一套 RAG 方法论,落地深度不同,选哪个取决于你是要快速搭应用,还是要把检索每一步都做成可审计的工具调用。

Agent 现在既能动手(工具)又能查资料(知识库),但这些能力都只在“当前这一次调用”里有效——对话一结束,它就把用户是谁、聊过什么、进行到哪一步全部忘光。下一章,我们看 Storage、Memory、State 如何让 Agent 从一次性调用变成一个记得住事情的长时系统。