Agno 可插拔上下文提供者协议
Agno 可插拔上下文提供者协议
学习目标
本章将分析 Agno 的上下文提供者系统:
- ContextProvider 抽象基类的统一接口设计
- 三种暴露模式(default/agent/tools)的适用场景
- 10+ 内置提供者的功能概览
- 健康检查(Status)协议
前置知识
建议先阅读:
- ContextEngine 可插拔上下文处理管道 — 处理器管道模式
下文假设你已理解上述概念,直接聚焦 Agno 的具体实现。
项目实践
ContextProvider 抽象基类
Agno 将所有外部上下文源统一为 ContextProvider 协议:
# 伪代码:ContextProvider 协议class ContextProvider(ABC): def __init__(self, id: str, mode: ContextMode = ContextMode.default, model: Optional[Model] = None): self.id = id self.mode = mode self.model = model
@abstractmethod def query(self, question: str) -> Answer: ...
@abstractmethod async def aquery(self, question: str) -> Answer: ...
@abstractmethod def status(self) -> Status: ...
@abstractmethod async def astatus(self) -> Status: ...
# 可选:写入支持(默认抛出 NotImplementedError) async def aupdate(self, document: Document) -> None: raise NotImplementedError(f"{self.id} is read-only")关键设计:
Answer返回结构化的results(Document 列表)和text(自然语言摘要)。查询方可以选择使用结构化结果或直接使用文本Status提供健康检查能力(ok: bool+detail: str)- 只读提供者继承
NotImplementedError,调用方通过<name> is read-only错误消息获得清晰反馈
三种暴露模式
ContextMode 控制 Provider 如何暴露给调用 Agent:
| 模式 | 行为 | 适用场景 |
|---|---|---|
| default | Provider 自己决定最佳暴露方式 | 通用场景,让 Provider 自行优化 |
| agent | 包装为子 Agent,调用方获得单一 query_<id> 工具 | Agent 需要自然语言查询能力,不需要了解 Provider 的底层细节 |
| tools | 直接暴露 Provider 的底层工具(如 search_slack_messages、read_file) | 调用方需要精细控制,自己决定查询策略 |
mode=agent 的包装逻辑:
# 伪代码:将 Provider 包装为子 Agentsub_agent = Agent( name=f"{provider.id}_context", tools=[provider.query_tool], # 暴露单一查询工具 system_message=provider.instructions(), # 注入使用指南)# 调用方 Agent 获得一个 query_<id> 工具# 如:query_slack(question: str) -> strmode=tools 的暴露逻辑:
# 伪代码:直接暴露 Provider 的底层工具tools = provider.get_tools() # 返回多个工具# 如 SlackProvider 暴露:search_slack_messages, read_channel, get_thread_replies# 调用方 Agent 可以直接编排这些工具内置提供者概览
| 提供者 | 数据源 | 写入支持 | 核心工具 |
|---|---|---|---|
| WebContextProvider | 搜索引擎(Exa/Parallel) | 否 | 搜索网页、提取摘要 |
| SlackContextProvider | Slack 工作区 | 否 | 搜索消息、读取频道、获取线程 |
| GoogleDriveContextProvider | Google Drive | 否 | 浏览文件、搜索文档 |
| GmailContextProvider | Gmail | 否 | 搜索邮件、读取邮件 |
| FileSystemContextProvider | 本地文件系统 | 是 | 浏览目录、读取文件 |
| DatabaseContextProvider | SQL 数据库 | 是 | 执行查询、列出表 |
| WikiContextProvider | 知识库 | 否 | 查询知识库条目 |
| MCPContextProvider | 外部 MCP 服务器 | 取决于服务器 | 通过 MCP 协议暴露 |
| CalendarContextProvider | 日历服务 | 否 | 查询日程 |
| WorkspaceContextProvider | 工作区文件 | 是 | 管理工作区文件 |
健康检查协议
每个 Provider 实现 status() 返回健康状态:
# 伪代码:健康检查@dataclassclass Status: ok: bool # 是否可达 detail: str = "" # 详细信息(失败原因或连接状态)Agent 可以在 run 前检查所有 Provider 的 status(),提前发现配置问题。
问题与规避
1. 只读 Provider 的写入错误
调用方尝试对只读 Provider(如 Web、Slack)执行写入操作,会收到 NotImplementedError。
规避:Agent 的系统提示中包含 Provider 的 instructions(),指导哪些操作不可执行。但 Agent 可能仍然尝试写入——错误消息 <name> is read-only 提供了清晰的反馈。
2. 外部服务不可用
Web 搜索、Slack、Drive 等外部服务可能因网络或认证问题不可用。
规避:status() 方法可以在 run 前检查。但对于 run 过程中的临时不可用,需要在工具调用层面处理(重试、降级)。
3. 三种模式的选择困惑
用户可能不确定该用 default/agent/tools 哪个模式。
规避:
default是最安全的默认选择- 如果需要自然语言查询能力(“帮我搜索…”),用
agent - 如果需要精细控制(先列出目录再读取特定文件),用
tools
设计取舍
为什么统一抽象而非各自为战?
Agno 选择将所有上下文源统一为 ContextProvider:
- 优势:统一的 query/status 接口,新增提供者只需实现协议
- 代价:抽象层可能丢失某些提供者的特有功能(如 Slack 的线程回复)
- 替代方案:每种上下文源独立 API——保留全部功能但使用更复杂
为什么 mode=agent 包装为子 Agent 而不是直接工具?
优势:子 Agent 有自己的系统提示和上下文理解,可以处理复杂查询(如”找到最近关于 X 的讨论,总结分歧”) 代价:子 Agent 调用增加一次额外的 LLM 调用 替代方案:直接暴露工具——更快但需要调用方 Agent 自己构建查询