WeKnora 网络搜索与网页抓取实战指南:13 种搜索引擎接入、Agent 联网检索与 SSRF 安全体系
2026/9/13 2:02:44 网站建设 项目流程

WeKnora 网络搜索与网页抓取实战指南:13 种搜索引擎接入、Agent 联网检索与 SSRF 安全体系

【免费下载链接】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 是开源的 LLM 知识平台,其网络搜索能力用于补充知识库之外的信息:智能体通过web_search查找结果,再通过web_fetch读取网页正文,两者共同构成 Agent 的外部信息通道。本文以 website-docs/03-features/11-web-search.md 为核心骨架,结合仓库源码与部署配置,系统讲解如何在「设置 → 网络搜索」中接入 DuckDuckGo、Google、Bing、Tavily、SearXNG 等 13 个搜索引擎,深入分析web_search/web_fetch两个 Agent 工具的参数语义、续读机制与失败语义,并剖析底层 SSRF 防护链路与新增搜索引擎的扩展步骤,帮助你在一线部署与二次开发中直接落地。

网络搜索在 WeKnora 中的角色

WeKnora 的知识问答能力以知识库(Knowledge Base)为主体,但现实中的问题往往需要补充知识库之外的最新或外部信息。此时智能体按以下分工工作:

  • web_search:负责「发现」,根据用户问题检索网页标题、摘要、域名、日期等搜索结果;
  • web_fetch:负责「读取」,按 URL(可能是 wN 页面 ID、用户直接给出的网页地址,或页面中发现的链接)抓取正文供模型分析。

两个工具配合形成完整的「搜索 → 精选 → 读页 → 综合作答」闭环。在使用层面,管理员在「设置 → 网络搜索」中选择提供商、填写凭据并测试连接,然后在智能体的运行配置中选择该搜索配置即可;搜索结果数受智能体的最大结果数设置约束(上限 20)。

一个重要设计变化是:Agent 搜索不再强制先调用grep_chunksknowledge_search。是否检索知识库取决于任务相关性与当前可用的工具,知识库检索与网络搜索不再有强制的先后顺序——网络搜索的定位是「按需启用的外部信息来源」,而非知识库流程的附属环节。

支持的搜索引擎与提供商差异

所有搜索引擎在 internal/container/container.go 中通过registry.Register注册为「provider 类型 → 工厂函数」的映射。当前仓库注册了 13 个引擎:

registry.Register("duckduckgo", infra_web_search.NewDuckDuckGoProvider) registry.Register("google", infra_web_search.NewGoogleProvider) registry.Register("bing", infra_web_search.NewBingProvider) registry.Register("tavily", infra_web_search.NewTavilyProvider) registry.Register("ollama", infra_web_search.NewOllamaProvider) registry.Register("baidu", infra_web_search.NewBaiduProvider) registry.Register("searxng", infra_web_search.NewSearxngProvider) registry.Register("keenable", infra_web_search.NewKeenableProvider) registry.Register("zhipu", infra_web_search.NewZhipuProvider) registry.Register("metaso", infra_web_search.NewMetasoProvider) registry.Register("exa", infra_web_search.NewExaProvider) registry.Register("bocha", infra_web_search.NewBochaProvider) registry.Register("brave", infra_web_search.NewBraveProvider)

各引擎的源码文件均位于 internal/infrastructure/web_search/,其关键差异如下:

引擎源码文件是否需要 API Key端点备注
DuckDuckGoduckduckgo.goHTML 抓取优先,API 兜底免费;可配proxy_url
Googlegoogle.go是(还需engine_idGoogle Custom Search API(官方 SDKcustomsearch/v1
Bingbing.gohttps://api.bing.microsoft.com/v7.0/search(硬编码)
Tavilytavily.gohttps://api.tavily.com/search(硬编码)
Ollama Web Searchollama.gohttps://ollama.com/api/web_search(硬编码)最多 10 条结果
百度千帆 AI 搜索baidu.gohttps://qianfan.baidubce.com/v2/ai_search/web_search(硬编码)
SearXNGsearxng.go租户自填base_url(自托管实例)唯一允许自定义地址的引擎,需过 SSRF 校验
Keenablekeenable.go可选https://api.keenable.ai(硬编码)无 Key 走公共限速端点,有 Key 解除限制
智谱搜索zhipu.gohttps://open.bigmodel.cn/api/paas/v4/web_search(硬编码),默认引擎search_std
秘塔 Metasometaso.gohttps://metaso.cn/api/v1/searchextra_config.scope 选择资源范围,默认 webpage
Exaexa.gohttps://api.exa.ai/search默认 highlights,可用 extra_config.include_text 获取正文
博查 Bochabocha.gohttps://api.bochaai.com/v1/web-searchextra_config.freshness、summary
Brave Searchbrave.gohttps://api.search.brave.com/res/v1/web/search支持按次传 country/freshness

除了 SearXNG,所有引擎端点均硬编码、租户不可配置——源码注释明确写道Not configurable by tenants — prevents SSRF,这是防 SSRF 的第一道措施。这也意味着租户只能通过base_url影响 SearXNG 一个引擎的请求目标。

提供商附加配置(extra_config)

部分引擎支持通过extra_config传入提供商特定的非密钥参数,这些字段在 internal/types/web_search_provider.go 的GetWebSearchProviderTypes中声明为动态表单字段(ConfigFields),前端据此渲染配置表单:

提供商附加配置
Metaso scopewebpage(默认)、document、scholar、podcast、video、image
Exa include_text字符串布尔值,例如"true";默认不取正文
Bocha freshnessnoLimit(默认)、oneDay、oneWeek、oneMonth、oneYear
Bocha summary字符串布尔值,决定是否请求摘要
Brave 按次过滤country/freshness 是 web_search 工具参数,见下文;与 Bocha 固定配置的字段取值不同

以智谱搜索为例,ConfigFields中声明了search_engine(可选search_std/search_pro/search_pro_sogou/search_pro_quark,对应不同的价格档位)与content_sizemedium/high)两个选择型字段;Metaso 的scope字段则列出六种内容源。这些字段全部通过ExtraConfig以非加密形式随 Provider 实体持久化。

搜索引擎配置(Provider 实体)

每个工作空间(Workspace)可以创建多个搜索引擎配置实例(例如 "Production Bing"、"Test Google"),存储为web_search_providers表的WebSearchProviderEntity(定义见 internal/types/web_search_provider.go),Agent 按 ID 引用对应配置。

实体字段包括:UUID 主键、租户 ID、用户友好名称、provider 类型、描述、参数(加密 JSON)、是否工作空间默认、时间戳。BeforeCreateGORM 钩子会自动为新增记录生成 UUID。

WebSearchProviderParameters是核心参数结构,各字段语义如下:

名称类型默认值说明
api_keystring搜索服务密钥,AES-GCM 加密落库;仅通过/credentials子资源修改,响应中从不返回
engine_idstring仅 Google Custom Search 需要
base_urlstring仅 SearXNG:自托管实例地址;经utils.ValidateURLForSSRF校验,内网地址须加入SSRF_WHITELIST
proxy_urlstring可选出站 HTTP/HTTPS 代理(仅隧道流量,不替换 API 端点),同样过 SSRF 校验
extra_configmap[string]stringnil提供商特定参数,如 Metaso scope、Exa include_text、Bocha freshness/summary

凭据安全:AES-GCM 加密与独立凭据接口

api_key的安全处理在源码中有三层保障:

  1. 落库加密WebSearchProviderParameters.Value()(GORM 的driver.Valuer)在写入数据库前用utils.EncryptAESGCM(p.APIKey, key)加密;读取时Scan()utils.DecryptStoredSecretLenient解密,密钥来自SYSTEM_AES_KEY
  2. 响应永不返回明文:处理器通过dto.NewWebSearchProviderResponse序列化,APIKey在构造响应时被省略。
  3. 独立凭据子资源:凭据变更走专门的/credentials子资源(PUT /:id/credentials),避免通过普通更新接口泄露或误改密钥。

CRUD 路由由RegisterWebSearchProviderRoutes注册(见 internal/router/router.go),挂在/web-search-providers路径下,包含增删改查、POST /test(用存量凭证探测外部服务,Admin 权限)、POST /:id/testPUT /:id/credentials;另有GET /web-search/providers返回可用引擎类型目录,供前端动态渲染表单。

Agent 搜索与读页:web_search 与 web_fetch

web_search负责发现来源,web_fetch负责读取选中的页面。用户指定网页时可直接读页;用户要求外部或实时信息时可直接搜索。整个流程可表示为:

web_search 工具

调用示例:

{"query":"Python release notes","count":5} {"query":"Rust release notes","country":"DE","freshness":"pw","content":true}

参数语义:

  • count:指定结果数量,范围是 1 到当前 Agent 配置的最大结果数(最多 20);省略时沿用现有 Agent 默认值。上游引擎侧同样有限制,例如 Brave 的SearchWithFilters会将maxResults钳制在 20 以内,Ollama 最多返回 10 条。
  • country/freshness:通过新增的 Brave 提供商生效。地区接受两字母代码或ALL,时效接受pd(过去 24 小时)/pw(过去一周)/pm(过去一月)/py(过去一年)或YYYY-MM-DDtoYYYY-MM-DD日期区间。省略country时不向 Brave 传该参数(Brave 自身默认 US);显式ALL表示全球结果。其它提供商暂不支持这些过滤,显式传入时返回错误,不会静默忽略。接口层面,支持按次过滤的提供商实现了可选的FilteredWebSearchProvider接口(见 internal/types/interfaces/web_search.go),从而与不支持过滤的提供商明确区分。
  • content:默认关闭。设为true时,并行抓取前 3 条结果的正文(整批 15 秒预算,每页最多 5,000 字符摘录);其余结果保留搜索摘要,需用web_fetch继续读页。抓取失败仍保留摘要;完整正文地址通过full_output_path返回。搜索和独立web_fetch共用本轮快照,短超时不会取消正在进行的共享抓取。
  • Brave 的相对age原样保留,避免把"2 days ago"伪造为精确发布日期——源码中Age字段直接取自 Brave API 的age/page_age字段。

结果处理方面:去除空查询、无效 URL、重复结果;最大结果数来自 Agent 配置,上限 20。Agent 搜索不再调用CompressWithRAG,不创建临时知识库,不依赖嵌入/重排模型或 Redis 临时状态(聊天快速回答管线的 RAG 压缩配置仍由原管线处理)。模型输出包含标题、域名、可用日期与 wN 页面 ID。摘要与 provider content 标为未经页面验证的搜索证据;每段最多 1,500 字符,整批证据预算 16,000 字符。

web_fetch 工具

调用示例:

{"items":[{"url":"w1"},{"url":"https://example.com/guide","limit":4000}]}
  • 输入:接受已知的 wN 页面 ID,也接受用户提供或页面中发现的 HTTP(S) URL。短 ID 在模型上下文边界还原,UI 与持久化结果保留真实 URL。已移除prompt参数,工具 schema 仅暴露urloffsetlimit——不再调用第二个模型进行摘要,主 Agent 直接分析网页正文。
  • HTML 提取:先用 Readability 提取正文;成功时直接转换完整提取结果,失败时才回退到 main/article/body,避免二次选择内部.content节点丢失相邻段落。转为 Markdown 后保留标题、段落、链接、表格和代码。相对链接以最终 HTTP URL 解析;嵌入资源不会自动下载。提取逻辑实现在 internal/infrastructure/web_fetch/markdown.go,本地回归样例覆盖完整文章的相邻段落、结构化内容、链接目录及代码缩进,可通过go test ./internal/infrastructure/web_fetch -run TestMarkdownExtractionFixtures验证。
  • 非 HTML 内容:纯文本、Markdown、JSON/XML 直接读取,避免把<...>当 HTML 丢掉;二进制格式明确报告unsupported_content。内容类型判定见 internal/infrastructure/web_fetch/fetcher.go。
  • 渲染兜底:HTTP 优先,现有 Chromium 动态页面兜底保留。Fetcher会先尝试静态抓取,当内容为空、仅含 "enable javascript"/"loading..." 占位、或页面带有id="app"/id="root"且含<script>时判定需要浏览器渲染,此时通过 chromedp 以 headless Chromium 渲染——且渲染也复用 DNS pinning 的 SSRF 防护(通过host-resolver-rules将目标主机映射到已解析并校验过的 IP)。
  • 批处理:每批最多 8 项;相同规范 URL、offset、limit 去重。各项独立返回success/failed/skipped,部分失败保留成功正文。
  • 分页与续读offset是从 0 开始的 Unicode 字符偏移,limit默认及上限均为 8,000。批次按输出预算分配正文空间,返回offsetreturned_charscontent_lengthtruncated;有剩余内容时返回next_offset。使用同一 URL 与offset=next_offset续读,内存缓存最多 8 个页面快照,仅用于本次运行的字符续读。
  • 持久化与跨轮读取:快照被淘汰后可通过返回的full_output_path继续读取同一份完整正文,不必重新抓网页。旧式字符续读在缓存失效时返回可重试的snapshot_expired(从 offset 0 重抓,或改用read_file),避免拼接不同版本页面。抓取后完整 Markdown 保存到会话所属租户的文件存储,返回web://...格式的full_output_pathread_file可跨轮读取这些文件,无需启用沙箱。正文与生成它的 assistant 消息绑定,读取时校验租户、会话所有者、会话、消息和网页专用绑定;普通附件不能作为网页读出。删除消息或会话后不可访问。单个保存的 Markdown 上限 8 MiB;保存失败不会丢弃已抓正文(结果含storage_error,此时续读仅限本轮内存缓存)。
  • read_file 续读细节read_fileoffset是从 1 开始的行号,limit最多 2,000 行,网页读取最多 50 KiB,并继续受 Agent 输出预算约束;遇到超长单行时返回next_offsetnext_line_offset,用offsetline_offset续读原行——这样无沙箱 Agent 也不需要执行 shell。
  • 大小与超时限制:Agent 单页下载上限 2 MiB(maxAgentBodySize),超限报告body_too_large,不会把静默截断的 HTML 冒充完整页面;请求超时 60 秒(fetchTimeout),Agent 抓取接受 HTTP 2xx 响应。
  • 失败语义:失败返回稳定错误码与可重试标记(ErrorCode枚举覆盖invalid_urldns_failedconnection_timeouttls_failedhttp_403http_429http_5xxssrf_rejectedredirect_rejectedbody_too_largesnapshot_expired等,见 fetcher.go)。临时失败可合理重试;永久失败可选择其他相关来源,证据不足时说明缺口。一次整批失败不会强制终止研究,也不能视为验证成功。
  • 开关语义:关闭联网时,无论旧allowed_tools是否列出这两个工具,运行时均不注册;失败网页不再作为成功网页引用展示。

共享抓取器的快速回答路径继续使用NewPipelineFetcher:15 秒超时、100 KiB 下载上限、HTTP-only 与原纯文本抽取(见 fetcher.go)。

行为调整与回归验证

相比调整前的 Agent 行为,当前实现的差异可总结为:

行为调整前 WeKnora Agent调整后
搜索前置强制两个 KB 工具,即使未注册根据任务与可用来源选择
搜索附带处理可自动入临时 KB 做 RAG直接返回搜索证据,按需读页
读页参数强制 url + prompturl,按需 offset/limit
正文分析每页再调用模型摘要主 Agent 直接读 Markdown
截断每页/整批限额,后续页面可能空白,无续读每页保留份额、完整正文存储、跨轮按行续读
失败整批失败强制停止搜索保留已有证据,合理重试或换源

调整后仍保留多搜索引擎、wN 引用、批量调用、租户开关与 SSRF 防护;通过 Brave 适配器支持 country/freshness,不支持过滤的提供商返回明确错误;显式content=true与独立web_fetch都可读页。

自托管 SearXNG:免 API Key 的默认可选搜索后端

SearXNG 是自托管的元搜索引擎(聚合上游多个引擎),WeKnora 把它作为免 API Key 的默认可选搜索后端打包在 docker-compose.yml 的searxng/fullprofile 中。

关键定制项

docker/searxng/settings.yml 包含如下定制:

  • search.formats开启json(WeKnora 后端走/search?format=json);
  • server.limiter: false:关闭 IP 限流,否则后端会被节流;若公开部署需重新开启并配置放行名单;
  • secret_key: "ultrasecretkey":由入口脚本以SEARXNG_SECRET环境变量替换;
  • 其它:image_proxy: truehttp_protocol_version: "1.0"method: "GET"outgoing.request_timeout: 6.0outgoing.max_request_timeout: 10.0safe_search: 0等。

部署编排细节

  • searxng-init辅助容器先把模板复制进独立 volume,避免 SearXNG 入口脚本原地 sed 修改把解析后的密钥写回仓库工作区(docker-compose 中通过cp /template/settings.yml /etc/searxng/settings.yml完成)。
  • 应用容器默认把searxng主机名并入 SSRF 白名单:SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-searxng,qdrant,milvus,weaviate,doris-fe,doris-be,minio},因此租户配置base_url: http://searxng:8080开箱即用,无需额外修改白名单。
  • 客户端超时 12s(defaultSearxngTimeout,见 searxng.go),略高于 SearXNG 的outgoing.max_request_timeout: 10.0,让上游慢引擎表现为 SearXNG 侧错误而非客户端取消。
  • ValidateSearxngBaseURL在"保存"与"使用"两处共享(服务层参数校验与 provider 构造函数都调用它),保证配置校验一致——它要求非空、绝对 http(s) URL、无 query/fragment,并通过utils.ValidateURLForSSRF
  • SearXNG 请求构造时以language=all显式表示"不过滤语言"(auto是 UI 侧默认值,不是合法的/search参数),且有意不设置safesearch,以尊重实例 settings.yml 中的配置;空结果时会透出unresponsive_engines便于诊断上游引擎连通性。

出站请求的 SSRF 防护体系

SSRF 防护贯穿搜索与抓取两条链路,是网络搜索功能的底层安全基石。

搜索链路的统一安全客户端

internal/infrastructure/web_search/proxy.go 的NewSearchHTTPClient为所有引擎构造统一的安全 HTTP 客户端:

  • DialContext使用utils.SSRFSafeDialContext(拨号时校验目标 IP,防 DNS rebinding);
  • 重定向逐跳经ssrfSafeRedirect复验ValidateURLForSSRF,超过最大跳数直接失败;
  • 显式proxy_url需通过 SSRF 校验(ValidateProxyURL委托给utils.ValidateURLForSSRF,只允许 http/https),未配置时回落ProxyFromEnvironment

Brave 还额外禁用了重定向跟随(CheckRedirect返回http.ErrUseLastResponse),避免订阅令牌被转发到任何重定向目标。

抓取链路的 DNS pinning

internal/infrastructure/web_fetch/fetcher.go 的pinnedDialContext实现 DNS pinning:拨号前先解析主机 IP,逐一校验为公网地址(或命中 SSRF 白名单),随后直接连接到已校验的 IP 而非重新解析主机名,彻底阻断 TOCTOU 型 DNS rebinding;Chromium 渲染路径同样通过host-resolver-rules把目标主机映射到已校验的 IP。

网段与内网地址管理

内网/回环地址默认被拒绝,租户如需让 SearXNG 等自托管服务接受配置,必须把对应主机名加入SSRF_WHITELIST(或SSRF_WHITELIST_EXTRA)。这一规则同样适用于web_fetch抓取内网服务、以及搜索请求的base_url/proxy_url配置。

接口抽象与新增搜索引擎

两层接口

搜索能力由两层接口定义(internal/types/interfaces/web_search.go):

// WebSearchProvider defines the interface for web search providers type WebSearchProvider interface { Name() string Search(ctx context.Context, query string, maxResults int, includeDate bool) ([]*types.WebSearchResult, error) } // WebSearchService defines the interface for web search services type WebSearchService interface { Search(ctx context.Context, providerID string, config *types.WebSearchConfig, query string) ([]*types.WebSearchResult, error) CompressWithRAG(ctx context.Context, sessionID string, tempKBID string, questions []string, ...) (...) }

Provider 层负责具体引擎的协议适配,Service 层负责按 providerID 路由、参数装配与租户隔离。可选的FilteredWebSearchProvider接口让 Brave 等引擎按次支持 country/freshness。

注册表模式

internal/infrastructure/web_search/registry.go 维护provider 类型 → 工厂函数的注册表,实例按租户参数在调用时创建:

type ProviderFactory func(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error) func (r *Registry) Register(id string, factory ProviderFactory) func (r *Registry) CreateProvider(providerType string, params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error)

如何新增一个搜索引擎

按照现有引擎(如 brave.go、searxng.go)的实现模式,接入新引擎共五步:

  1. 在 internal/infrastructure/web_search/ 新建<engine>.go,实现interfaces.WebSearchProviderName()+Search()),并提供工厂函数func New<Engine>Provider(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error);官方端点应硬编码为常量,HTTP 客户端用NewSearchHTTPClient(timeout, params.ProxyURL)构造。
  2. 在 internal/types/web_search_provider.go 增加WebSearchProviderType常量。
  3. 在 internal/container/container.go 的注册处追加registry.Register("<engine>", infra_web_search.New<Engine>Provider)
  4. 如需密钥/额外参数校验,在 web search provider service 的参数校验分支中补充(参考ValidateSearxngBaseURL的共享校验模式),并为前端GET /web-search/providers目录补充展示信息(即GetWebSearchProviderTypes中新增对应的WebSearchProviderTypeInfo,可声明RequiresAPIKeyRequiresEngineIDRequiresBaseURLSupportsOptionalAPIKeySupportsProxyConfigFields动态表单字段)。
  5. 参考searxng_test.go/zhipu_test.go(位于 internal/infrastructure/web_search/)用httptest模拟上游编写单测。

小结

WeKnora 的网络搜索能力由「多引擎 Provider 注册表 + 加密凭据的配置实体 + web_search/web_fetch 双工具 + 统一 SSRF 防护 + 可选 SearXNG 自托管后端」五层构成。部署侧只需在设置中配置提供商并选择到智能体即可启用;二次开发侧则通过实现WebSearchProvider接口并注册工厂函数,即可在半小时内接入新搜索引擎。需要特别注意的三条底线是:除 SearXNG 外的端点一律硬编码、base_urlproxy_url一律过 SSRF 校验、api_key一律加密落库且永不回显。理解了这三条,就理解了 WeKnora 网络搜索模块的安全设计哲学与扩展边界。

【免费下载链接】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),仅供参考

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

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

立即咨询