WeKnora 如何新增一个网络搜索引擎 Provider:注册类型、实现接口与容器装配
2026/9/15 12:04:31 网站建设 项目流程

WeKnora 如何新增一个网络搜索引擎 Provider:注册类型、实现接口与容器装配

【免费下载链接】WeKnoraOpen-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.项目地址: https://gitcode.com/GitHub_Trending/we/WeKnora

在 WeKnora 的源码里接入一个新的网络搜索引擎类型(例如 Brave Search),需要同时完成三件事:注册类型常量与元数据、实现WebSearchProvider接口、在 DI 容器中注册工厂函数。全部完成后,新引擎会出现在GET /api/v1/web-search-providers/types返回的类型列表中,前端可以据此创建 Provider 实例。适用前提是你有 WeKnora 源码环境,能执行go build ./...并本地启动服务后调用 API 验证。

四个文件构成一个完整的扩展点

WeKnora 的联网搜索扩展模式是「接口实现 + 注册表 + DI 注册」,涉及以下位置(以仓库根目录为起点):

internal/ ├── types/ │ └── web_search_provider.go # 实体定义 + Provider 类型元数据 ├── infrastructure/ │ └── web_search/ │ ├── registry.go # Provider 工厂注册表 │ ├── bing.go # Bing 实现 │ ├── google.go # Google 实现 │ ├── duckduckgo.go # DuckDuckGo 实现 │ └── tavily.go # Tavily 实现 ├── container/ │ └── container.go # DI 注册(registerWebSearchProviders) └── types/interfaces/ └── web_search.go # WebSearchProvider 接口

其中 接口定义 只有两个方法:

type WebSearchProvider interface { Name() string Search(ctx context.Context, query string, maxResults int, includeDate bool) ([]*types.WebSearchResult, error) }

一个关键设计约束:搜索引擎的 API 端点硬编码在实现代码中,不向用户暴露 BaseURL,从源头消除 SSRF 风险。实现新 Provider 时请遵循这一点。

步骤 1:在 types/web_search_provider.go 中注册类型常量

编辑 internal/types/web_search_provider.go,在WebSearchProviderType常量块中新增一行。文档以 Brave Search 为例:

const ( WebSearchProviderTypeBing WebSearchProviderType = "bing" WebSearchProviderTypeGoogle WebSearchProviderType = "google" WebSearchProviderTypeDuckDuckGo WebSearchProviderType = "duckduckgo" WebSearchProviderTypeTavily WebSearchProviderType = "tavily" WebSearchProviderTypeBrave WebSearchProviderType = "brave" // ← 新增 )

常量的字符串值就是后续 API 中provider字段的取值,写入数据库后不可更改。

步骤 2:在 GetWebSearchProviderTypes() 中添加类型元数据

同一个文件的GetWebSearchProviderTypes()函数返回所有类型的元数据,GET /types接口依赖它让前端动态渲染添加表单。为 Brave 新增一条:

{ ID: "brave", Name: "Brave Search", RequiresAPIKey: true, SupportsProxy: true, Description: "Brave Search API", DocsURL: "https://brave.com/search/api/", },
字段说明
ID唯一标识,存入数据库,不可更改
Name前端展示名称
RequiresAPIKey是否需要 API Key
RequiresEngineID是否需要额外 ID(如 Google CSE)
Description简短描述
DocsURL官方文档链接,前端在添加对话框中显示

注意:早期文档示例中的元数据还包含Free字段,但当前仓库的WebSearchProviderTypeInfo结构体已不含该字段,并扩展了SupportsProxyRequiresBaseURLSupportsOptionalAPIKeyConfigFields等字段。新增元数据时请以 internal/types/web_search_provider.go 中WebSearchProviderTypeInfo的当前定义和已有条目为准。

步骤 3:创建 Provider 实现

新建internal/infrastructure/web_search/brave.go(以 Brave 为例,换成你的引擎时替换端点、请求头与响应结构)。文档给出的完整示例:

package web_search import ( "context" "encoding/json" "fmt" "io" "net/http" "time" "github.com/Tencent/WeKnora/internal/types" "github.com/Tencent/WeKnora/internal/types/interfaces" ) const defaultBraveSearchURL = "https://api.search.brave.com/res/v1/web/search" type BraveProvider struct { client *http.Client apiKey string } // NewBraveProvider 从参数创建实例(不读环境变量) func NewBraveProvider(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error) { if params.APIKey == "" { return nil, fmt.Errorf("API key is required for Brave provider") } return &BraveProvider{ client: &http.Client{Timeout: 10 * time.Second}, apiKey: params.APIKey, }, nil } func (p *BraveProvider) Name() string { return "brave" } func (p *BraveProvider) Search( ctx context.Context, query string, maxResults int, includeDate bool, ) ([]*types.WebSearchResult, error) { // 构造请求 — BaseURL 硬编码 req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s?q=%s&count=%d", defaultBraveSearchURL, query, maxResults), nil) if err != nil { return nil, err } req.Header.Set("X-Subscription-Token", p.apiKey) req.Header.Set("Accept", "application/json") resp, err := p.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 解析响应 body, _ := io.ReadAll(resp.Body) var data braveResponse if err := json.Unmarshal(body, &data); err != nil { return nil, err } results := make([]*types.WebSearchResult, 0, len(data.Web.Results)) for _, r := range data.Web.Results { results = append(results, &types.WebSearchResult{ Title: r.Title, URL: r.URL, Snippet: r.Description, Source: "brave", }) } return results, nil } type braveResponse struct { Web struct { Results []struct { Title string `json:"title"` URL string `json:"url"` Description string `json:"description"` } `json:"results"` } `json:"web"` }

关键要求:

  1. 构造函数签名必须为func(types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error),注册表按这个签名调用工厂。
  2. API 端点硬编码为常量,不从参数中读取。
  3. 实现interfaces.WebSearchProvider接口:Name()Search()

可选分支:如果新引擎支持按区域、时间范围过滤,可以额外实现 FilteredWebSearchProvider 接口(SearchWithFilters方法)。接口注释明确要求:不具备该能力的 Provider 不能静默丢弃请求的过滤条件。参考仓库中现有的 brave.go、zhipu.go 实现即可了解这一分支的完整写法。

步骤 4:在 Service 的参数校验中添加新类型

编辑 internal/application/service/web_search_provider.go 的isValidProviderType,把新类型加入白名单;创建 Provider 实例时它会校验provider字段,不在白名单内会直接报invalid provider type

func isValidProviderType(provider types.WebSearchProviderType) bool { switch provider { case types.WebSearchProviderTypeBing, types.WebSearchProviderTypeGoogle, types.WebSearchProviderTypeDuckDuckGo, types.WebSearchProviderTypeTavily, types.WebSearchProviderTypeBrave: // ← 新增 return true default: return false } }

如果你的引擎对必填参数有特殊要求(如 API Key 必填),文档示例只在构造函数内检查;更完整的做法是参照同文件中validateProviderParameters的 switch,为types.WebSearchProviderTypeBrave增加分支,在参数为空时返回API key is required for Brave provider一类的明确错误。

步骤 5:在 DI 容器中注册

编辑 internal/container/container.go 的registerWebSearchProviders函数。Registry.Register的签名是Register(id string, factory ProviderFactory)(见 registry.go),第一个参数是类型 ID 字符串,第二个参数是步骤 3 的构造函数:

func registerWebSearchProviders(registry *infra_web_search.Registry) { // ... 已有注册 ... // Register Brave provider type registry.Register("brave", infra_web_search.NewBraveProvider) }

该函数通过container.Invoke在服务启动时执行,Provider 实例在租户创建配置时才按参数即时创建。

步骤 6:编译与 API 验证

先确认代码能编译:

go build ./...

编译通过后启动服务,按文档给出的顺序调用两个接口验证(http://localhost:8080为文档中的服务地址,your_key替换为你实际有效的 API Key,请求体中的BSA...替换为你的真实 Brave API Key):

# 启动后调用 API 验证类型列表 curl http://localhost:8080/api/v1/web-search-providers/types \ -H 'X-API-Key: your_key' # 创建 Brave 搜索引擎实例 curl -X POST http://localhost:8080/api/v1/web-search-providers \ -H 'X-API-Key: your_key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Brave Search", "provider": "brave", "parameters": { "api_key": "BSA..." }, "is_default": true }'

判断标准:第一个请求返回的列表中应出现"id": "brave"的条目及其元数据;第二个请求成功创建实例。如果isValidProviderType漏加新类型,创建请求会被拒绝并报invalid provider type;如果registerWebSearchProviders漏注册,运行期创建实例时会报web search provider type ... not registered

需要额外参数时怎么办

如果新引擎需要 API Key 以外的参数(类似 Google 的engine_id),文档给出两种方式:

方式一:使用ExtraConfig。利用WebSearchProviderParameters.ExtraConfig字段,不需要改类型定义:

func NewFooProvider(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error) { region := params.ExtraConfig["region"] if region == "" { region = "us" } // ... }

前端在GetWebSearchProviderTypes()中可以标注需要哪些 extra 字段(后续支持动态表单渲染)。当前仓库中ConfigFields字段即用于描述这类由前端动态渲染的非密文配置项,值持久化在ExtraConfig中。

方式二:添加专用字段。如果参数非常通用(多个引擎都需要),可以在WebSearchProviderParameters中添加新字段,例如Region string,同时在WebSearchProviderTypeInfo中添加RequiresRegion一类的字段,让前端据此动态显示输入框。

文件变更清单

文件操作
internal/types/web_search_provider.go添加常量 + 类型元数据
internal/infrastructure/web_search/<你的引擎>.go新建Provider 实现
internal/application/service/web_search_provider.goisValidProviderType加新类型
internal/container/container.goregisterWebSearchProviders注册

完整的扩展说明见 docs/添加新的网络搜索引擎.md,Wiki 侧的对应条目为 添加网络搜索引擎。如果你要做的是同类的向量数据库扩展,文档指出其模式一致(接口实现 + 注册 + DI),可对照 集成向量数据库。

【免费下载链接】WeKnoraOpen-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.项目地址: https://gitcode.com/GitHub_Trending/we/WeKnora

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询