前言

问大模型"特斯拉今天发生了什么",它给你的答案是八年前的旧闻。这不是模型不够聪明,而是它的知识停在了训练截止的那一天。检索增强解决了"知道什么"的问题,但解决不了"此刻正在发生什么"。

这篇文章记录我如何把腾讯元宝搜索(SearchPro)做成 RAGFlow 的一等公民:既是画布上可拖拽的工作流节点,也是智能体可自主调用的工具。改造完成后,工作流里的每一个答案都能带上来源链接、发布时间和检索时间,并且可以点击跳转回原始报道。

选择元宝搜索的原因很实际:返回结果自带发布时间和正文段落,不需要再去抓取原网页;时效过滤、信源模式、领域筛选都是接口原生参数,不用自己在应用层拼装;而且开通就有免费额度,个人开发者拿来试错没有成本压力。

全文包含完整的实现架构、关键代码解析和真实运行效果,所有截图均来自实际部署。你可以按文章顺序从零复现整个过程。

第一步:开通元宝搜索服务

https://console.cloud.tencent.com/wsapi/index?tab=standard

打开腾讯云控制台的这个地址,进入元宝搜索(SearchPro)的开通页面:

image.png

这里可以使用腾讯云密钥或者是元宝单独的api key进行接入,整个开通过程几分钟就能完成。版本上有个细节需要注意:元宝搜索分 lite、standard、flagship 三个版本,极速版(lite)只能拿到十条基础结果,不支持自定义条数、信源模式和领域过滤,也不返回正文。本次集成的默认参数在极速版下即可运行,日常每个账号有免费调用额度,做原型验证完全够用;如果你想用后面提到的权威垂直信源和领域过滤,建议直接开通标准版。开通后在密钥管理里拿到 SecretId 和 SecretKey,这就是后文所有请求的签名凭据。

第二步:本地拉取项目ragflow

https://github.com/infiniflow/ragflow.git

image.png

我这里将ragflow进行了相关的改动,在节点列表新增了一个元宝搜索的节点。改动覆盖前后两端一共 37 个文件、四千余行,但真正的核心逻辑只有三百行 Go 代码,下面我会把最关键的代码逐段拆开讲。

这个节点是怎么做出来的

新增一个节点不是改一个文件就能完成的事。RAGFlow 的 Agent 体系横跨前端画布、DSL 编译、Go 运行时三层,元宝搜索需要在每一层各落一颗钉子,最终收敛到同一份实现上。

整体架构

两条入口,一份实现:画布节点 YuanbaoSearch 和智能体工具 yuanbao_search 共用同一个 yuanbaoTool,不需要任何额外服务,工作流直接在 Go API 进程内执行。

关键代码一:注册,三行接通两条链路

后端注册只需要三行。第一处在工具工厂 internal/agent/tool/registry.go,把模型侧的工具名 yuanbao_search 指向构造函数;第二处在 internal/agent/component/tool_component.go,把画布组件名 YuanbaoSearch 和工具名绑定:

// internal/agent/tool/registry.go
var registry = map[string]Factory{
    // ... 其他工具
    "yuanbao_search": buildYuanbaoTool,
}

// internal/agent/component/tool_component.go
{componentName: "YuanbaoSearch", toolName: "yuanbao_search"},

注册完成后,画布编译器遇到 YuanbaoSearch 节点、或智能体决定调用 yuanbao_search 工具时,都会走到同一个构造函数,这就是"两条入口一份实现"的由来。

关键代码二:凭据解析与请求生命周期

InvokableRun 是每次搜索的执行入口。它先做参数校验,再解析凭据——优先用节点表单里填写的密钥,留空才回退到服务端环境变量,且只填一半会直接报错,绝不混用两套身份:

// internal/agent/tool/yuanbao.go
id, key, token := t.secretID, t.secretKey, t.token
if id == "" && key == "" {
    id = strings.TrimSpace(os.Getenv("TENCENTCLOUD_SECRET_ID"))
    key = strings.TrimSpace(os.Getenv("TENCENTCLOUD_SECRET_KEY"))
    token = strings.TrimSpace(os.Getenv("TENCENTCLOUD_TOKEN"))
}
if id == "" || key == "" {
    return yuanbaoError("configure both secret_id and secret_key, ..."), nil
}

image.png

注意错误是以正常返回值的形式输出的:失败时返回带 _ERROR 字段的 JSON,下游 LLM 能读到"搜索失败"的明确提示,而不是把失败伪装成"查过了,没有新闻"。这对情报类工作流至关重要——证据缺失和证据为空是两回事。

参数校验同样严格,全部在发起网络请求之前完成:查询词限 2000 字以内;count 只接受 10/20/30/40/50;freshness 支持 d1–d30、m1–m12、y1–y5 或递增的 ISO 日期区间,且不能晚于北京时间今天;site 只允许一个域名或 exclude: 加最多五个排除域名。这套时效过滤是元宝接口原生支持的,也是我最喜欢的一个参数:想要"只要今天的新闻"时,一个 d1 就能解决,不用拿到结果后再按日期做二次筛选。

关键代码三:TC3-HMAC-SHA256 签名

腾讯云 API 要求对请求做三层 HMAC 派生签名。我没有引入腾讯云 SDK,而是按官方规范手写实现,零新增第三方依赖:

// internal/agent/tool/yuanbao.go
func signYuanbaoRequest(body []byte, id, key string, now time.Time) map[string]string {
    date := now.UTC().Format(time.DateOnly)
    timestamp := strconv.FormatInt(now.Unix(), 10)
    canonical := "POST\n/\n\ncontent-type:application/json\nhost:wsa.tencentcloudapi.com\nx-tc-action:searchpro\n\ncontent-type;host;x-tc-action\n" + hash(body)
    scope := date + "/wsa/tc3_request"
    toSign := "TC3-HMAC-SHA256\n" + timestamp + "\n" + scope + "\n" + hash([]byte(canonical))
    signingKey := mac(mac(mac([]byte("TC3"+key), date), "wsa"), "tc3_request")
    return map[string]string{
        "Authorization": "TC3-HMAC-SHA256 Credential=" + id + "/" + scope +
            ", SignedHeaders=content-type;host;x-tc-action, Signature=" + hex.EncodeToString(mac(signingKey, toSign)),
        "X-TC-Action": "SearchPro", "X-TC-Version": "2025-05-08", "X-TC-Timestamp": timestamp,
    }
}

image.png

signingKey 的派生链是 TC3+SecretKey → 日期 → 服务名 wsa → tc3_request 逐层 HMAC,这是腾讯云签名的核心。签名只出现在 Authorization 请求头里,上游传输错误也不会回显任何可能泄露凭据的内容。腾讯云的接入文档把签名规范写得很清楚,照着实现一次就能通过,调试上没有踩坑。

关键代码四:把搜索结果接入引用体系

SearchPro 返回的 Pages 是字符串数组,每个元素本身又是一段 JSON。解码阶段逐条解析,剔除重复 URL、非 http/https 链接和无正文结果,单条正文截断到一万字符。随后通过 BuildReferences 把每条结果转成 RAGFlow 的标准引用 chunk:

// internal/agent/tool/yuanbao.go
chunks = append(chunks, map[string]any{
    "id": displayID, "chunk_id": id, "doc_id": id,
    "content": content, "docnm_kwd": r["title"],
    "url": link, "similarity": 1, "score": 1,
    "published_at": r["date"], "retrieved_at": envelope["retrieved_at"],
});

image.png

就是这几行,让搜索结果进入了 RAGFlow 现有的引用渲染管线——最终简报里每个结论后面那个可点击跳转的来源链接,数据全部来自这里。这里值得说一句元宝返回数据的完整度:标题、URL、正文、发布时间全部齐备,published_at 直接取自结果自带的日期字段。做事实核查类应用时,"这条消息是什么时间发布的"往往比内容本身更重要,很多搜索接口恰恰不返回这个字段,需要自己去网页里解析,元宝把这一步省了。同时输出的 formalized_content 把结果排版成带 [ID:x] 标识的文本,下游 LLM 可以直接阅读并在回答中引用。

前端:节点、表单与密钥脱敏

前端改动集中在 web/src/pages/agent/,关键有三处。

第一处是默认值定义,query 默认绑定系统变量 {sys.query},用户提问直接进入搜索词,时效默认 d1(当天):

// web/src/pages/agent/constant/index.tsx
export const initialYuanbaoValues = {
  secret_id: '', secret_key: '', token: '',
  query: AgentGlobals.SysQuery,
  freshness: 'd1', count: 10, mode: 0, industry: '', site: '',
  outputs: {
    formalized_content: { value: '', type: 'string' },
    json: { value: {}, type: 'object' },
  },
};

第二处是表单校验,用 zod 把后端的参数边界在前端再拦一道,用户填错条数或领域时提交前就提示:

// web/src/pages/agent/form/yuanbao-form/index.tsx
export const YuanbaoSettingsSchema = z.object({
  secret_id: z.string().optional(),
  secret_key: z.string().optional(),
  token: z.string().optional(),
  freshness: z.string(),
  count: z.coerce.number().int().min(10).max(50).multipleOf(10),
  mode: z.coerce.number().int().min(0).max(2),
  industry: z.enum(['', 'news', 'gov', 'acad', 'finance']),
  site: z.string(),
});

智能体工具配置面板复用同一份 YuanbaoSettingsSchema 和 YuanbaoWidgets,只是去掉了 query——因为工具模式下搜索词由模型在调用时生成。这套复用保证了两个入口的配置体验完全一致。

第三处最容易被忽略但最关键:导出脱敏。utils/clear-sensitive-fields.ts 在导出工作流 DSL 时,自动清空节点参数、智能体工具配置和画布表单里的三个密钥字段:

// web/src/pages/agent/utils/clear-sensitive-fields.ts
const clearYuanbao = (params: Record<string, unknown>) => ({
  ...params,
  secret_id: '', secret_key: '', token: '',
});

你可以放心把工作流 JSON 发给任何人或提交到公开仓库,密钥不会跟着走。

两套开箱即用的模板

提交里还内置了两个模板,Go 服务启动时自动写入模板表:

  • 元宝实时情报助手:Begin → Agent(可调用元宝搜索)→ Message,适合连续追问和临时事实核查。
  • 元宝实时情报研判与行动简报:十节点完整流水线,三路搜索并行覆盖当日动态(d1)、七日背景(d7)、官方与政策信源,再经事件归并、追查核实、影响研判、时效审稿,最终输出带来源的 Markdown 简报。

其中"官方与政策"这一路用到了元宝的信源模式和领域过滤:mode=1 走权威垂直信源,industry=gov 锁定政务领域。查政策类信息时这个组合很实用,结果直接来自政府站点和官方发布渠道,避开了转载和自媒体解读,做合规或政策追踪的团队可以重点关注这个能力。

验证不是走过场

单元测试覆盖参数边界、签名、官方 Pages 格式解析、去重、缺失凭据和错误传播,不需要任何外部服务;另有直连真实 SearchPro 接口和真实模型的端到端测试,用项目自己的 Canvas 编译器跑完整流水线,不替换任何被测实现。前端 36 个相关测试加 Vite 生产构建全部通过。

第三步:部署与模型配置

将项目部署好了之后,首先先配置自己的ai模型,这里我使用gpt。在模型供应商页面选择 OpenAI API Compatible,填入模型名、API Key 和 Base URL:

image.png

这一步决定的是工作流里五个分析节点的"大脑",元宝搜索节点本身不依赖任何模型。凭据有两种提供方式:在节点表单里直接填写,或者在 Go API 服务的环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY(临时凭据还需 TENCENTCLOUD_TOKEN),节点留空时自动使用服务端配置。多人协作时推荐环境变量方式,密钥不进入任何工作流定义。Docker 部署记得把环境变量传进容器,只在宿主机导出是不够的:

services:
  ragflow:
    environment:
      TENCENTCLOUD_SECRET_ID: ${TENCENTCLOUD_SECRET_ID}
      TENCENTCLOUD_SECRET_KEY: ${TENCENTCLOUD_SECRET_KEY}
      TENCENTCLOUD_TOKEN: ${TENCENTCLOUD_TOKEN:-}

第四步:搭建三路搜索工作流

我们创建一个工作流:

image.png

进入到工作流界面,我们这里在开始节点后面加上了三个元宝搜索节点,并且每个节点的功能都不同:

image.png

通过腾讯元宝搜索实时信息,返回来源、发布时间和可引用的证据。三个节点并行执行,分工是当日动态(d1)、七日背景(d7)、官方与政策(d7 加权威信源模式)。并行三路而不是串行一路,是为了让"最新消息"和"背景脉络"互不稀释——如果只用一次搜索,当日快讯和历史背景会挤在同一个结果池里,下游模型很难分清哪些是刚发生的、哪些是一周前的。元宝的时效参数粒度够细,从一天到五年都能圈定,三路节点只是改一个 freshness 值的事。

后面跟上了5个gpt组成的节点链,事件归并与证据分级-》追查与交叉核实、影响与行动研判-》时效与证据审稿-》最后就是生成发布时间简稿了:

image.png

这条链的设计逻辑是"先收敛再深挖":归并节点把三路结果合并去重、区分原始消息与转载;核查节点针对待确认的事实调用元宝搜索补查原始发布方;研判节点基于核实后的证据给出优先级和行动建议;审稿节点专门检查时效标注和引用是否站得住,最后才输出简报。

在工作流中穿插元宝搜索的节点,让ai生成的数据更加真实可信:

image.png

第五步:运行,看证据说话

这里简单的运行这个工作流,查看节点相关的数据,这里能通过元宝搜索这个节点获取到特斯拉飞天的事情的相关的数据:

image.png

注意右侧面板里的结构:每条结果都有标题、URL 和正文,这正是 BuildReferences 写入的引用数据,运行面板直接复用了 RAGFlow 知识库检索结果的展示组件。从发起请求到拿到结构化结果,整个过程响应很快,实测单次搜索的耗时在工作流里几乎不构成瓶颈。

3个元宝节点都很清晰的将数据结构化的展示出来了:

image.png

三路结果分别对应不同的时效窗口和信源范围,展开任意一路都能核对它的时间过滤是否生效——d1 那一路的结果日期全部落在当天,d7 那一路则覆盖最近一周,过滤是实打实生效的。

并且交给下面的ai进行相关分析操作:

image.png

分析节点收到的不是一堆链接,而是 formalized_content 排版好的带 [ID:x] 标识的文本,模型在回答里引用 [ID:3] 时,前端能把它渲染回对应的来源。

最后能生成出十分详细的,并且具有时效性的总结:

image.png

简报中每个关键结论后面都跟着引用编号,编号和来源一一对应。

对应位置还有事件的跳转链接:

image.png

点击引用标识会直接跳转到原始报道页面。到这一步,从"模型凭记忆回答"到"每个结论都可回溯到具体网页"的闭环就完整了。

对照实验:没有元宝节点时

当我不加上元宝搜索的节点,这里我以三个很简单的工作流进行示范,这里同样的问题,但是这里他就反馈的时间节点则是2018年了,就是8年之前的事情了,所以ai如果自己本身回答的话,是不具备时效性的,但是如果加上元宝这个节点的话,就能获取到最近发生的事情了:

image.png

这组对照是整件事最直观的证明:同一个模型、同一个问题,唯一的差别是工作流里有没有元宝搜索节点。没有实时证据输入时,模型只能复述训练数据里的旧闻;接入之后,答案的时效取决于搜索结果,而不是模型的训练截止日期。

写在最后

回看这次接入,从开通服务到工作流跑通,真正花在元宝接口本身的时间很少,大部分工作量在 RAGFlow 自己的节点体系上。这得益于几个设计:时效、信源、领域这些过滤条件都是接口原生参数,不需要应用层二次加工;返回结果自带发布时间和正文,直接达到了"可引用证据"的标准;响应速度也撑得起工作流里多路并行调用的场景。

如果你也在做需要实时信息的 AI 应用——资讯早报、竞品监控、政策追踪,或者只是想让智能体回答"今天发生了什么"——可以试试把元宝搜索接进去。开通就有免费额度,文档和签名规范都很清晰,不用 RAGFlow 也没关系,照着我上面那段签名代码,几十行就能在任何项目里跑通第一次调用。我的完整改动都在仓库里,欢迎参考,也欢迎交流你的接法。 Gitee-ragflow

Logo

更多推荐