跳转到内容

上下文提供者:30+ 插件注入代码上下文

上下文提供者:30+ 插件注入代码上下文

学习目标

本章要解决的核心问题:AI 编程助手如何让用户灵活地选择哪些代码上下文发送给 LLM,同时保持可扩展性和按需加载的性能?

你将学到:

  • Continue 的 ContextProvider 双方法接口设计
  • 36 个内置提供者的分类与职责
  • 子菜单按需加载机制
  • Token 预算和 isItemTooBig 检查

前置知识

本章涉及上下文提供者插件的通用模式,建议先阅读:

下文假设你已理解上下文提供者的通用概念,直接聚焦 Continue 的具体实现。


项目实践

双方法接口

Continue 的 ContextProvider 接口定义了两个核心方法:

interface ContextProvider {
// 1. 加载子菜单项(用于 @mention 下拉列表)
loadSubmenuItems(config: Config, extras): Promise<SubmenuItem[]>;
// 2. 根据查询返回上下文片段
getContextItems(query: string, extras: ContextProviderExtras): Promise<ContextItem[]>;
}

关键设计细节:

  • loadSubmenuItems轻量级操作,只返回元数据(文件名、标题等),不读取实际内容
  • getContextItems重量级操作,可能涉及文件读取、网络请求、向量检索等
  • 两者共享 extras 对象,包含 config、llm、ide、fetch 等运行时信息

36 个内置提供者分类

Continue 在 core/context/providers/ 中实现了 36 个内置提供者:

代码类(核心)

  • codebase — 整个代码库的语义搜索
  • code — 选中代码或指定文件
  • currentFile — 当前打开的文件
  • open — 所有打开的文件
  • fileTree — 项目目录结构
  • folder — 指定文件夹内容
  • file — 指定文件
  • diff — Git 工作区变更
  • problems — IDE 诊断(错误/警告)
  • terminal — 终端输出

文档类

  • docs — 外部文档索引(向量检索)
  • url — 单个 URL 内容
  • web — 网络搜索
  • google — Google 搜索
  • greptile — Greptile 代码搜索服务

外部集成类

  • github — GitHub Issues/PRs
  • gitlab — GitLab MR
  • jira — Jira Issues
  • discord — Discord 频道消息
  • http — 自定义 HTTP 端点

系统类

  • os — 操作系统信息
  • database — PostgreSQL 查询结果
  • debug — 调试本地变量
  • clipboard — 剪贴板内容

配置类

  • rules — 项目规则文件
  • repo-map — 项目结构地图

子菜单按需加载

当用户输入 @ 时,GUI 请求 context/loadSubmenuItems

// core/core.ts 消息处理
on("context/loadSubmenuItems", async (msg) => {
const { config } = await this.configHandler.loadConfig();
const items = await config.contextProviders
?.find(p => p.description.title === msg.data.title)
?.loadSubmenuItems({ config, ide: this.ide, fetch: ... });
return items || [];
});

这确保了:

  • 只在用户输入 @ 时才计算子菜单项
  • 每个提供者独立计算,不互相阻塞
  • 如果某个提供者加载失败,只影响它自己,不影响其他提供者

Token 预算控制

isItemTooBig 检查确保上下文片段不会超过 LLM 的上下文窗口:

private async isItemTooBig(item: ContextItemWithId) {
const llm = config.selectedModelByRole.chat;
const tokens = countTokens(item.content, llm.model);
if (tokens > llm.contextLength - llm.completionOptions.maxTokens) {
return true; // 片段太大,不应发送
}
return false;
}

问题与规避

陷阱 1:@codebase 检索返回过多内容

现象:查询返回大量代码片段,超出上下文窗口。

规避:检索管线内部有 nFinal 参数控制最终返回数量(默认 25 个片段)。每个片段还要通过 isItemTooBig 检查。

陷阱 2:远程提供者(GitHub/Jira)加载慢

现象:输入 @github 后子菜单加载需要几秒。

原因loadSubmenuItems 需要调用外部 API 获取 Issues 列表。

规避:子菜单加载是异步的,GUI 显示 loading 状态。用户可以设置超时或缓存结果。

陷阱 3:自定义提供者注册失败

现象:通过 CustomContextProviderClass 注册的自定义提供者不生效。

规避:自定义提供者必须正确实现 description 元数据(title 和 displayName)。title 重复会覆盖之前的提供者。


设计取舍

为什么用统一接口而非每种数据源独立接口

36 种数据源如果各有接口,核心代码需要 36 种不同的处理路径。统一 ContextProvider 接口使得:

  • 核心代码简单getContextItems 的调用者不关心具体类型
  • 用户可扩展:新提供者只需实现一个接口
  • GUI 统一:所有提供者通过 @mention 触发,交互一致

代价:接口需要足够通用,某些数据源可能需要适配才能符合接口约束。

内置提供者 vs MCP 工具

Continue 有 36 个内置提供者,同时支持 MCP 扩展。两者的边界:

  • 内置提供者:与代码/IDE 紧密相关,需要访问 IDE API、文件系统、Git 等
  • MCP 工具:外部服务通过 MCP 协议暴露,不依赖 IDE 内部能力

这不是技术上的必须区分,而是职责边界的设计决策。


参考来源