Higress ai-search 插件深度解析:为 LLM 接入 Google/Bing/Arxiv/Elasticsearch/夸克搜索引擎增强回答能力
【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress
Higress 的ai-search插件通过在请求转发到 LLM 之前并行调用多个搜索引擎,把实时搜索结果注入到提示模板中,并可自动在最终回答里追加引用来源。本文基于插件的官方文档 README.md 与源码实现(main.go、engine/types.go 及各引擎实现),完整讲解其运行属性、全部配置字段、搜索重写机制与各类配置示例,帮助你在 Higress 网关上为 DeepSeek 等模型构建带联网检索、论文检索或私有知识库检索能力的 AI 服务。
一、功能定位与运行机制
插件的核心逻辑是:拦截 OpenAI 兼容格式的聊天请求,从messages中取出最后一条 user 消息作为查询词,向配置的搜索引擎发起检索,将结果格式化后填充进提示模板并替换原请求体,再放行到上游 LLM;响应阶段则根据配置决定是否把引用来源(标题+链接列表)插入回答内容中。
关键运行属性:
| 属性 | 值 | 说明 |
|---|---|---|
| 执行阶段 | 默认阶段 | 在转发到 LLM 供应商之前执行,保证能先改写 prompt |
| 执行优先级 | 460 | 优先级数值越大越先执行,需排在请求改写类插件之前 |
从源码结构看,插件通过 main.go 的init()注册了完整的请求/响应处理链:
onHttpRequestHeaders:校验content-type是否为 JSON(非 JSON 直接跳过),移除Accept-Encoding、Content-Length头并设置 100MB 的请求体缓冲上限;onHttpRequestBody:提取用户查询、执行搜索(或先执行搜索重写)、替换请求体;onHttpResponseHeaders/onStreamingResponseBody/onHttpResponseBody:仅在开启needReference时介入,负责将引用来源分别插入流式 SSE 响应或非流式响应。
一个值得注意的设计是失败快速降级:当搜索重写调用 LLM 失败、或所有引擎都没有返回结果时,插件会记录日志并直接ResumeHttpRequest放行原请求,即"搜索失败不阻塞对话"。
二、插件配置字段详解
2.1 顶层配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| defaultEnable | bool | 选填 | true | 插件功能默认是否开启。设置为 false 时,仅当请求中包含web_search_options字段时才启用插件功能 |
| needReference | bool | 选填 | false | 是否在回答中添加引用来源 |
| referenceFormat | string | 选填 | "**References:**\n%s" | 引用内容格式,必须包含%s占位符,配置不合法会在启动时直接报错 |
| referenceLocation | string | 选填 | "head" | 引用位置:"head"在回答开头,"tail"在回答结尾 |
| defaultLang | string | 选填 | - | 默认搜索语言代码(如 zh-CN/en-US) |
| promptTemplate | string | 选填 | 内置模板 | 提示模板,必须包含{search_results}和{question}占位符,缺少任一占位符配置校验会失败 |
| searchFrom | array of object | 必填 | - | 搜索引擎配置列表,至少配置一个引擎,否则配置解析报错no available search engine found |
| searchRewrite | object | 选填 | - | 搜索重写配置,用于使用 LLM 服务优化搜索查询 |
关于promptTemplate,如果不开启needReference,内置模板只要求模型"综合多个网页回答但不给出网页引用来源";如果开启needReference,内置模板会额外要求模型在正文对应位置以[X]编号形式引用(可多引用如[3][5]),并要求区分列举类、创作类、客观问答类问题分别采用不同的回答策略。两种内置模板都包含{cur_date}占位符,由插件在运行时填入北京时间当日日期(格式:2006年1月2日),可用于增强时效性问答的准确性。
2.2 搜索引擎通用配置
searchFrom数组中每个引擎项共享以下字段:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| type | string | 必填 | - | 引擎类型(google/bing/arxiv/elasticsearch/quark) |
| serviceName | string | 必填 | - | 后端服务名称(Higress 中的服务来源 FQDN 集群) |
| servicePort | number | 必填 | - | 后端服务端口 |
| apiKey | string | 必填* | - | 搜索引擎 API 密钥(Arxiv 免费接口不需要) |
| count | number | 选填 | 10 | 单次搜索返回结果数量 |
| start | number | 选填 | 0 | 搜索结果偏移量(从第 start+1 条结果开始返回) |
| timeoutMillisecond | number | 选填 | 5000 | API 调用超时时间(毫秒) |
| optionArgs | map | 选填 | - | 搜索引擎特定参数(key-value 格式,直接拼接到请求 URL 上) |
源码中每个引擎都实现了统一的 SearchEngine 接口(NeedExectue/Client/CallArgs/ParseResult),各引擎通过NeedExectue判断是否响应当前搜索上下文(见下文搜索重写),这是多引擎并行检索的扩展点。
三、各搜索引擎的具体实现与特定配置
3.1 Google 搜索
特定配置:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| cx | string | 必填 | - | Google 自定义搜索引擎 ID,用于指定搜索范围 |
从 google.go 源码可以看到几个关键实现细节:
- 参数校验约束:初始化时强制要求
count小于 10 且start + count <= 100,超限直接配置失败。这与 Google Custom Search API 单页最多 10 条、最大偏移 100 条的协议限制一致,因此要取更多结果必须用"多条目 + start 递增"的并发方案(见第五节配置示例); - 请求构造:调用
customsearch.googleapis.com/customsearch/v1,start参数按 Google 协议传的是start+1;defaultLang会转换成lr=lang_XX参数附加到 URL 上; - 结果解析:除
snippet摘要外,还会读取pagemap.metatags.0.og:description(网页的 og:description 元数据)并以...\n分隔拼接到正文内容中,为 LLM 提供更丰富的上下文; - 去重:多引擎结果合并时以
Link为键去重(见 main.go)。
3.2 Bing 搜索
apiKey通过Ocp-Apim-Subscription-Key请求头传递,调用api.bing.microsoft.com/v7.0/search,defaultLang映射为mkt参数。
从 bing.go 的解析逻辑看,Bing 引擎不只返回webPages.value网页结果,还会解析:
deepLinks:每个网页下的站内子链接,作为独立结果展开;news.value:新闻结果,以description字段作为正文内容。
因此在optionArgs中传入answerCount、responseFilter(如包含 news)等参数可以进一步控制返回类型。
3.3 Arxiv 论文搜索
特定配置:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| arxivCategory | string | 选填 | - | 搜索的论文类别(如 cs.AI, cs.CL 等,Arxiv 官方类别分类法) |
Arxiv 是免费开放接口,不需要 API Key。从 arxiv.go 看,其请求与解析特点是:
- 查询词会被转换成
all:关键词形式,多关键词之间以+AND+连接;如果搜索重写识别出了论文类别(运行时优先级高于静态配置的arxivCategory),还会追加+AND+cat:类别限定; - 最终请求
export.arxiv.org/api/query?search_query=...&max_results=...&start=...,返回 Atom XML 格式; - 解析每条论文时提取 title、alternate 链接、摘要(summary)、作者列表与发布时间,并格式化为
摘要 + Authors + Publication time的正文喂给 LLM。
3.4 Elasticsearch 私有知识库搜索
特定配置:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| index | string | 必填 | - | 要搜索的 Elasticsearch 索引名称 |
| contentField | string | 必填 | - | 要查询的内容字段名称 |
| semanticTextField | string | 必填 | - | 要查询的 embedding 字段名称 |
| linkField | string | 选填 | - | 结果链接字段名称,当配置needReference时需要填写(否则初始化报错) |
| titleField | string | 选填 | - | 结果标题字段名称,当配置needReference时需要填写 |
| username | string | 选填 | - | Elasticsearch 用户名 |
| password | string | 选填 | - | Elasticsearch 密码 |
从 elasticsearch.go 可以看到,插件发送的是一个混合检索(Hybrid Search)请求体:使用rrf(Reciprocal Rank Fusion,倒数排名融合)检索器,同时执行两路检索——
- 基于
contentField的match标准全文匹配(BM25 语义); - 基于
semanticTextField的semantic向量检索;
再由 RRF 融合两路排名得到最终结果。请求 URL 为/{index}/_search?from={start}&size={count},认证走 Basic Auth 头。
版本与 License 前提:
- RRF 查询要求 Elasticsearch 版本在8.8 及以上;
- 文档向量化依赖 Elasticsearch 内置 Embedding 模型(semantic_text 能力),该功能需要 Elasticsearch 企业版 License 或 30 天 Trial License;如需改用第三方 Embedding 模型,可按 Elasticsearch 官方向量搜索文档自行部署。
另外,main.go 中在初始化 ES 引擎时会把needReference传入,用于在插件启动阶段就校验linkField/titleField是否齐全,避免运行期才发现配置缺失。
3.5 夸克(Quark)搜索
特定配置:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| contentMode | string | 选填 | "summary" | 内容模式:"summary"使用摘要(snippet),"full"使用正文(优先 markdownText,为空则用 mainText) |
从 quark.go 看,夸克引擎调用阿里云 IQS 的cloud-iqs.aliyuncs.com通用搜索接口,apiKey以X-API-Key头传递;结果按pageItems数组解析,并在客户端侧按count截断(index < count)。contentMode取值非法时配置会直接校验失败,这是五个引擎中少数在启动期做枚举值校验的字段。
一个实现细节:如果searchFrom中只配置了 quark 引擎,搜索重写会自动选用中文互联网专用提示词(见 4.2 节),以更好地适配中文互联网内容检索。
四、搜索重写(searchRewrite):用 LLM 优化检索
4.1 功能与适用场景
搜索重写功能先调用一个可配置的 LLM 服务对用户的原始查询进行分析,作用包括:
- 判断是否需要搜索——如果用户消息不是提问(如闲聊、翻译要求),LLM 会返回
none,插件直接放行原请求,完全不触发搜索逻辑; - 查询改写——把自然语言问题转成更适合搜索引擎的关键词组合;
- Arxiv 类别识别——自动判断问题所属论文领域并添加
cat:类别限定; - 私有库关键词拆分——把长查询拆成多个精准关键词组合(逗号分隔)。
官方文档强烈建议在使用 Arxiv 或 Elasticsearch 引擎时启用此功能:对 Arxiv 搜索它能准确识别论文领域并优化英文关键词;对私有知识库搜索它能提供更精准的关键词匹配。
4.2 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| llmServiceName | string | 必填 | - | LLM 服务名称(Higress 服务来源) |
| llmServicePort | number | 必填 | - | LLM 服务端口 |
| llmApiKey | string | 选填 | - | LLM 服务 API 密钥(以 Bearer Token 传递) |
| llmUrl | string | 必填 | - | LLM 服务 API 地址(OpenAI 兼容 chat/completions) |
| llmModelName | string | 必填 | - | LLM 模型名称 |
| timeoutMillisecond | number | 选填 | 30000 | API 调用超时时间(毫秒) |
| maxCount | number | 选填 | 3 | 搜索重写生成的最大查询次数 |
配置解析逻辑在 main.go:四个必填字段任一缺失都会导致插件启动失败;maxCount会被注入到重写提示词的{max_count}占位符中。
4.3 提示词按引擎组合自动选择
插件内置了五份搜索重写提示词(prompts 目录),配置加载时按已启用引擎的组合自动选用:
| 已配置的引擎组合 | 选用的提示词文件 | 说明 |
|---|---|---|
| Arxiv + 私有库 + 互联网 | full.md | 全场景 |
| Arxiv + 互联网(无私有库) | arxiv.md | 含完整的 Arxiv Category 枚举表 |
| 私有库 + 互联网 | private.md | 区分 internet:/private: 两类查询 |
| 仅互联网(含 google/bing 等) | internet.md | 基础联网查询改写 |
| 仅 quark | chinese-internet.md | 面向中文互联网优化 |
以 internet.md 为例,重写提示词要求 LLM 按 What → How → Adjust → Final 的思路分析,并按固定格式输出:
internet: 黄金价格走势 internet: The trend of gold prices每行以internet:/private:/ Arxiv Category(如cs.AI:)为前缀,多行用换行分隔,总条数不超过maxCount;若判断无需搜索则只输出none。arxiv.md 中还内嵌了完整的 Arxiv Category 枚举清单(cs.、math.、quant-ph、stat.* 等),供 LLM 判定论文领域时查表使用。
4.4 重写结果到搜索上下文的映射
main.go 将 LLM 返回的每行解析为SearchContext:
internet: xxx→ 互联网引擎(google/bing/quark 均响应,其NeedExectue对internet/空上下文均返回 true);private: a,b→ 按逗号拆分为多个关键词,仅 Elasticsearch 引擎响应;- 其他前缀(即 Arxiv Category)→ 仅 Arxiv 引擎响应,并携带类别限定。源码中还有一个提升召回率的策略:当识别出 Arxiv 类别时,会同时再追加一个不带类别限定的备份查询,确保不因类别误判而漏检;
- 重写请求失败或输出中不含任何有效上下文时,均直接放行原始请求(不搜索)。
五、完整配置示例
以下示例全部继承自官方 README,可直接复制到 Higress 控制台或WasmPlugin资源中使用(注意serviceName需先在服务来源中配置好对应域名,如customsearch.googleapis.com、api.bing.microsoft.com、cloud-iqs.aliyuncs.com、export.arxiv.org,可参考仓库内同目录的 guide.md 中的分步教程)。
5.1 基础配置(单搜索引擎)
needReference: true searchFrom: - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443 count: 5 optionArgs: fileType: "pdf"5.2 Arxiv 搜索配置
searchFrom: - type: arxiv serviceName: "arxiv-svc.dns" servicePort: 443 arxivCategory: "cs.AI" count: 105.3 夸克搜索配置
searchFrom: - type: quark serviceName: "quark-svc.dns" servicePort: 443 apiKey: "quark api key" contentMode: "full" # 可选值:"summary"(默认)或"full"5.4 多搜索引擎配置
defaultLang: "en-US" promptTemplate: | # Search Results: {search_results} # Please answer this question: {question} searchFrom: - type: google apiKey: "google-key" cx: "github-search-id" # 专门搜索GitHub内容的搜索引擎ID serviceName: "google-svc.dns" servicePort: 443 - type: google apiKey: "google-key" cx: "news-search-id" # 专门搜索Google News内容的搜索引擎ID serviceName: "google-svc.dns" servicePort: 443 - type: bing apiKey: "bing-key" serviceName: "bing-svc.dns" servicePort: 443 optionArgs: answerCount: "5"5.5 并发查询(分页取更多结果)
由于搜索引擎对单次查询返回结果数量有限制(如 Google 单次最多 100 条且单页不超过 10 条),可以通过"小 count + start 偏移 + 多条目并发"的方式获取更大结果集。例如要获取 30 条结果,可配置 count=10 并配置三个查询,start 分别为 0、10、20:
searchFrom: - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443 start: 0 count: 10 - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443 start: 10 count: 10 - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443 start: 20 count: 10注意过高的并发可能会导致被搜索引擎限流,需要根据实际情况调整。
5.6 Elasticsearch 配置(对接私有知识库)
searchFrom: - type: elasticsearch serviceName: "es-svc.static" index: "knowledge_base" contentField: "content" semanticTextField: "semantic_text" # username: "elastic" # password: "password"5.7 自定义引用格式与位置
needReference: true referenceFormat: "### 数据来源\n%s" searchFrom: - type: bing apiKey: "your-bing-key" serviceName: "search-service.dns" servicePort: 8080needReference: true referenceLocation: "tail" # 在回答结尾添加引用,而不是开头 searchFrom: - type: bing apiKey: "your-bing-key" serviceName: "search-service.dns" servicePort: 80805.8 搜索重写配置
searchFrom: - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443 searchRewrite: llmServiceName: "llm-svc.dns" llmServicePort: 443 llmApiKey: "your-llm-api-key" llmUrl: "https://api.example.com/v1/chat/completions" llmModelName: "gpt-3.5-turbo" timeoutMillisecond: 150005.9 按需启用插件(兼容 OpenAI 搜索模型协议)
defaultEnable: false searchFrom: - type: google apiKey: "your-google-api-key" cx: "search-engine-id" serviceName: "google-svc.dns" servicePort: 443配置defaultEnable: false后,只有当请求体中包含web_search_options字段时插件才激活(即使是空对象"web_search_options": {}也会激活),可以兼容 OpenAI 的搜索模型协议,让同一 LLM 端点根据客户端是否要求联网来动态开关搜索增强。
5.10 动态调整搜索深度(search_context_size)
在请求的web_search_options中携带search_context_size参数,可动态调整搜索查询次数:
{ "web_search_options": { "search_context_size": "medium" } }search_context_size支持三个级别(见 main.go):
| 取值 | 效果 | 适用场景 |
|---|---|---|
| low | 生成 1 个搜索查询 | 简单问题 |
| medium | 生成 3 个搜索查询(默认值) | 常规问题 |
| high | 生成 5 个搜索查询 | 复杂问题 |
该设置会覆盖配置中的maxCount值,并在每次请求时基于原始提示词模板重新替换{max_count}占位符,允许客户端按问题复杂度动态调整搜索深度。传入未知值时插件会打警告日志并回退使用配置的maxCount。
六、引用来源注入的实现细节
开启needReference后,插件在搜索阶段就为每条结果生成了[序号] 标题形式的引用列表(main.go),并按referenceLocation在响应阶段注入:
非流式响应(onHttpResponseBody):读取choices.0.message.content,若回答以<think>开头(如 DeepSeek-R1 等推理模型),默认会把引用插在</think>之后,即思考过程与正式回答之间;否则按 head/tail 位置拼接后整体替换。
流式 SSE 响应(onStreamingResponseBody):实现更为精细——
- head 模式:对
choices.0.delta.content做 30 字节滑动缓冲,确认首段内容不是<think>后在首个 delta 前拼接引用;若首段是思考内容,则持续缓冲直到检测到完整的</think>标签再插入,并能处理 `
【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考