一、引言:为什么你需要一个"会搜会引"的 Agent
大模型的知识是"静态"的------它的训练数据截止于某个时间点,无法回答"昨天发布的新版本有什么变化""本月发生的行业事件"这类时效性问题。更麻烦的是,模型在不确定时会一本正经地编造:把不存在的 API 参数写得像模像样,把过时的接口路径当成现役标准。这就是行业里常说的"幻觉"(Hallucination)。
解决这个问题的主流方案是 RAG(检索增强生成),但传统 RAG 只检索私有知识库,覆盖的是企业内部文档。当问题超出知识库范围------比如竞品动态、最新法规、技术趋势------检索器就"无米下锅"了。
联网搜索 Agent (Web Search Agent)正是为补上这块拼图而生:它把搜索引擎当作动态知识源,让大模型在回答前先"上网查证",再基于检索结果组织答案,并逐条标注引用来源。这不只是把搜索 API 接到对话里那么简单,一套生产级方案要解决四个关键问题:
- 查询改写:用户口语化提问如何变成搜索引擎友好的查询词?
- 多源检索:单一搜索源覆盖率不够、结果质量不稳定怎么办?
- 相关性过滤:搜索结果鱼龙混杂,如何只保留高质量片段?
- 引用溯源:回答的每一句话,如何回追到可验证的来源?
本文基于华为云 MaaS 平台的 DeepSeek-V3/R1 商用推理服务 + Flexus X 实例一键部署的 Dify 平台,从零构建一个带引用溯源的企业级联网搜索 Agent。全文约 5500 字,所有工作流节点配置、Prompt 模板和 Python 代码均可直接复用,希望帮你建立一套属于自己的搜索 Agent 工程方法论。
本文为华为云 Flexus+DeepSeek 征文投稿,基于 MaaS 平台 DeepSeek 商用服务与 Flexus X 一键部署 Dify 方案的真实开发实践整理。
二、整体架构:搜索 Agent 的四层流水线
在动手之前,先明确目标形态。我们要构建的联网搜索 Agent,输入是用户的一句话提问,输出是一段带 123 角标引用、文末附来源列表的回答。它跑在 Dify 工作流(Workflow)上,整体是一条四层流水线:
用户提问
│
▼
┌─────────────────────────────────────────────────┐
│ 第 1 层:查询理解与改写(LLM 节点) │
│ 口语 → 结构化查询:主查询 + 时间限定 + 关键词扩展 │
└──────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 第 2 层:多源检索(代码节点 / HTTP 节点) │
│ 搜索引擎A ─┐ │
│ 搜索引擎B ─┼─→ 合并去重 → 片段切分 → 打分排序 │
│ 内部知识库 ┘ │
└──────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 第 3 层:相关性重排(LLM 节点) │
│ 逐条判断:与问题相关?来源权威?时间是否过时? │
└──────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 第 4 层:生成与溯源(LLM 节点 + 模板) │
│ 基于入选片段回答,句末标注 [n],文末附来源清单 │
└─────────────────────────────────────────────────┘
▼
带引用的回答
这套流水线有两个设计要点:
- 每一层都是独立节点,可以单独调试、单独替换。比如第 2 层今天接的是搜索引擎 A,明天想换成自建检索服务,只改一个节点。
- 第 3 层重排是灵魂。很多搜索 Agent 效果差,不是模型不行,而是"垃圾进、垃圾出"------搜索结果没过滤就塞给大模型,模型再强也答不好。重排层决定了回答质量的上限。
三、地基:MaaS DeepSeek 接入与 Flexus X 上的 Dify
3.1 开通 MaaS 商用推理服务
华为云 ModelArts Studio(MaaS)平台提供 DeepSeek-V3/R1 的商用级推理服务,按 token 计费、开箱即用。开通流程三步走:
- 登录华为云控制台,进入 ModelArts Studio 的"模型推理-在线推理"模块;
- 选择"商用服务",开通 DeepSeek-V3(通用对话/工具调用)和 DeepSeek-R1(复杂推理/数学代码)两个模型;
- 在"API 凭证"页获取 API Key 与 Endpoint 地址。
MaaS 提供 OpenAI 兼容接口,这意味着一行代码即可替换调用端:
from openai import OpenAI
client = OpenAI(
api_key="your-maas-api-key",
base_url="https://infer-modelarts.xxx.myhuaweicloud.com/v1"
)
resp = client.chat.completions.create(
model="DeepSeek-V3",
messages=[{"role": "user", "content": "你好"}],
temperature=0.3,
)
在我们这套架构中,DeepSeek-V3 负责查询改写、相关性判断、回答生成 (它指令遵循能力强、延迟低),DeepSeek-R1 负责复杂推理型问题(比如"比较两家云厂商最新定价策略的差异"),通过 Dify 的模型路由按需切换。
3.2 Flexus X 一键部署 Dify
Dify 是开源的 LLM 应用开发平台,提供可视化工作流编排、知识库、Agent、观测等能力。使用华为云"快速搭建 Dify-LLM 应用开发平台"方案,在 Flexus X 实例上可以分钟级完成部署:
# 拉取部署方案(华为云实现中心一键部署,也可手动 docker compose)
git clone https://github.com/huaweicloud/dify-deploy-solution.git
cd dify-deploy-solution
# 1. 准备环境变量(填 MaaS 的 key 与 endpoint)
cp .env.example .env
sed -i 's/^OPENAI_API_KEY=.*/OPENAI_API_KEY=your-maas-key/' .env
sed -i 's|^OPENAI_API_BASE=.*|OPENAI_API_BASE=https://infer-modelarts.xxx.myhuaweicloud.com/v1|' .env
# 2. 启动整套服务(API/Worker/Web/DB/Redis/向量库)
docker compose up -d
# 3. 验证
curl -s http://localhost/health | jq .
Flexus X 实例的柔性算力配置让资源选型很灵活:我们的搜索 Agent 属于 IO 密集 + 轻计算负载,选 4vCPU/8GB 规格即可;如果后续要接向量检索做"联网+私有知识库"混合问答,再平滑升配到 8vCPU/16GB。相比固定配比的传统规格,这种"按需柔性"在成本上更可控------测试期用完即可降配或释放,避免资源闲置浪费。
成本提醒:一键部署方案为按需计费,开通即计费,测试完毕务必删除资源,避免产生非预期费用。
3.3 在 Dify 中配置模型供应商
在 Dify 后台「设置-模型供应商」中新增 OpenAI-API-Compatible 供应商,填入:
| 配置项 | 值 |
|---|---|
| API Key | MaaS 平台获取的密钥 |
| API Base URL | MaaS 的 OpenAI 兼容 Endpoint |
| 模型 | DeepSeek-V3 / DeepSeek-R1(按需勾选) |
配置完成后,Dify 工作流的 LLM 节点即可直接选用这两个模型,后续所有 Prompt 工程都在可视化界面里完成。
四、核心组件一:查询改写(Query Rewriting)
4.1 为什么不能直接拿用户原话去搜
用户在对话框里的表达和搜索引擎能消化的查询,完全是两种语言:
| 用户原话 | 问题 |
|---|---|
| "那个华为新出的服务器咋样?" | 口语化、指代不明(哪个服务器?) |
| "2026 年 AI 芯片市场格局" | 缺少时间限定,搜出来一堆旧文 |
| "帮我查查 DeepSeek 最近有啥动作" | 意图模糊(发布?融资?开源?) |
直接把原话丢给搜索引擎,召回结果往往答非所问。查询改写的目标,是把模糊的用户意图翻译成 1-3 条高质量查询词。
4.2 改写 Prompt 设计
在 Dify 工作流中新建一个 LLM 节点,模型选 DeepSeek-V3,使用如下系统提示词:
你是一名搜索查询专家。你的任务是把用户的问题改写为适合搜索引擎的查询。
要求:
1. 输出 1-3 条查询,每条一行,不要编号
2. 如果原问题包含口语词,改写为标准书面表达
3. 补充必要的限定词:时间(如"2026年")、地域(如"中国市场")、对象全称
4. 如果问题涉及对比(A vs B),分别输出针对 A 和 B 的查询
5. 只输出查询语句本身,不要任何解释
用户问题:{{question}}
示例效果:
用户:那个新的云服务器比老的好在哪?
改写:
2026年 华为云 Flexus X 实例 与 上一代云服务器 对比
Flexus X 实例 性能 优势 评测
4.3 时间感知改写(进阶)
对于时效性问题,可以在改写时注入"当前时间",让模型自动加时间限定:
当前日期:{{current_date}}(由 Dify 的"当前时间"节点注入)
规则:如果用户问题涉及"最新""最近""今年"等时效词,在查询中追加
"{{current_date 所在年份}}";如果问题本身带明确时间,保留原时间。
这一步能显著提升时效性问题的召回质量------搜索"2026年AI芯片市场格局"和搜索"AI芯片市场格局",返回的结果时效性完全不同。
五、核心组件二:多源检索(Multi-Source Retrieval)
5.1 为什么要多源
单靠一个搜索引擎,结果集通常存在三个问题:
- 覆盖盲区:技术问答类内容在开发者社区(CSDN、GitHub、Stack Overflow)质量最高,但通用搜索引擎排序未必优先;
- 时效滞后:搜索引擎索引有延迟,最新发布的信息搜不到;
- 单一偏见:某搜索引擎对某些站点降权或过滤,导致系统性漏检。
多源检索的做法是:同时查询 2-3 个互补源,合并去重后再统一打分。我们的方案采用"搜索引擎 + 开发者社区 API + 内部知识库"三源结构:
| 源 | 类型 | 覆盖优势 |
|---|---|---|
| 源 A:Bing Web Search API | 通用网页搜索 | 广覆盖、时效好 |
| 源 B:CSDN / GitHub 搜索 | 垂直社区搜索 | 技术内容质量高、代码可执行 |
| 源 C:Dify 知识库(可选) | 向量检索 | 企业内部资料、私有文档 |
5.2 代码节点实现多源检索
Dify 的"代码节点"(Python 3)里可以并发调用多个搜索源。核心实现:
import requests, json, hashlib
def search_bing(query, key, count=5):
"""Bing Web Search API"""
url = "https://api.bing.microsoft.com/v7.0/search"
r = requests.get(url, params={"q": query, "count": count},
headers={"Ocp-Apim-Subscription-Key": key}, timeout=8)
return [{"title": x["name"], "url": x["url"],
"snippet": x.get("snippet", ""),
"source": "bing"} for x in r.json().get("webPages", {}).get("value", [])]
def search_github(query, token, count=5):
"""GitHub 仓库/代码搜索"""
url = "https://api.github.com/search/repositories"
r = requests.get(url, params={"q": query, "per_page": count},
headers={"Authorization": f"token {token}"}, timeout=8)
return [{"title": x["full_name"], "url": x["html_url"],
"snippet": (x.get("description") or ""),
"source": "github"} for x in r.json().get("items", [])]
def main(query: str, bing_key: str, gh_token: str) -> dict:
"""并发调用多源,合并去重后输出"""
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
f1 = ex.submit(search_bing, query, bing_key)
f2 = ex.submit(search_github, query, gh_token)
bing, github = f1.result(), f2.result()
# 合并 + 按 URL 去重(保留第一个出现的结果)
seen, merged = set(), []
for item in bing + github:
h = hashlib.md5(item["url"].encode()).hexdigest()
if h in seen:
continue
seen.add(h)
merged.append(item)
# 简单切分:片段超过 300 字符则截断
for it in merged:
it["snippet"] = it["snippet"][:300]
return {"results": json.dumps(merged, ensure_ascii=False)}
要点说明:
- 并发而非串行 :多源检索的目的是"快",用
ThreadPoolExecutor让各源并行查询,总耗时≈最慢的源; - 按 URL 去重:不同源可能返回同一篇文章,用 URL 的 MD5 去重,避免重复内容撑爆上下文;
- 超时兜底:每个请求 8 秒超时,某源故障时自动跳过,不让单点拖垮整体。
5.3 可选:接入内部知识库
如果希望 Agent 在"联网查不到"时回退到企业内部资料,把 Dify 知识库的检索结果也并入合并列表即可。Dify 的知识检索节点支持 Rerank 模型,与联网结果统一进入第 3 层重排,实现"外部公网 + 内部私网"的混合检索------这也是活动的加分方向(企业知识库 + 联网搜索助手的组合能力)。
六、核心组件三:相关性重排(Re-ranking)
6.1 为什么需要重排
多源合并后的结果可能有 10-15 条,但真正与用户问题相关的可能只有 3-4 条。搜索引擎的排序逻辑(PageRank、点击率等)与"是否回答得了当前问题"不是一回事。直接全量塞给大模型,有两个坏处:
- 上下文污染:不相关片段会干扰模型判断,甚至被错误引用;
- Token 成本浪费:每多塞 1000 token,生成成本就高一分。
重排层的职责:逐条打分,只保留高相关片段。
6.2 LLM 重排 vs 向量重排
| 方式 | 优点 | 缺点 |
|---|---|---|
| LLM 重排(DeepSeek-V3) | 理解语义、能判断"是否回答了问题" | 慢(每条都要过模型)、贵 |
| 向量 Rerank 模型 | 快、便宜 | 只做语义相似度,不理解"问题意图" |
生产实践中常用两段式:先用便宜的向量/BM25 粗筛(Top 20 → Top 10),再用 LLM 精排(Top 10 → Top 4)。本文演示 LLM 精排,因为它还能顺带完成"时效性判断"和"来源权威性判断",一举三得。
6.3 重排 Prompt 模板
你是搜索结果评估专家。请判断以下搜索结果是否与用户问题相关,并打分。
用户问题:{{question}}
判断标准:
- 相关度(0-10):是否直接回答了问题或包含关键信息
- 时效性(0-10):信息是否过时(问题涉及时效则权重加倍)
- 权威性(0-10):来源是否可信(官方文档>技术社区>个人博客>营销软文)
输出格式(严格 JSON):
{"items": [{"index": 0, "score": 8.5, "keep": true, "reason": "一句话理由"}]}
只保留 score >= 7 的结果,其他 keep 设为 false。
搜索结果:
{{search_results}}
在 Dify 中,这个 LLM 节点的输出接一个代码节点解析 JSON,过滤出 keep=true 的条目,拼装成"精选片段列表",供第 4 层生成使用。
七、核心组件四:回答生成与引用溯源
7.1 生成时如何"绑定"引用
这是搜索 Agent 与普通 RAG 问答最大的区别:回答必须可验证。实现方法是给每个入选片段分配一个稳定编号,并要求模型在回答中显式引用:
系统提示词(关键部分):
你将基于下面的"参考资料"回答用户问题。规则:
1. 每个参考资料有固定编号 [1][2][3]...
2. 回答中引用某个资料时,在该句末尾标注对应编号,如"......华为云Flexus X实例采用柔性算力架构[1]"
3. 一个句子若综合多个资料,标注多个编号,如 [1][3]
4. 参考资料中没有的信息,明确回答"资料中未提及",禁止编造
5. 回答最后附"参考资料"列表:编号 + 标题 + URL
参考资料:
[1] 标题:xxx | URL:https://... | 内容:...
[2] 标题:xxx | URL:https://... | 内容:...
用 Dify 的"模板节点"把精选片段自动格式化为上面的 [n] 编号格式,再传给 LLM 节点。编号与 URL 的映射在模板节点中固化,生成节点只负责引用编号,这样无论模型怎么发挥,来源列表都不会出错。
7.2 防幻觉的兜底设计
光有提示词还不够,还要加两道工程兜底:
-
无引用惩罚 :工作流里加一个"后处理代码节点",正则检查回答中是否包含
\[\d+\]引用标记。若完全无引用,说明模型在自由发挥,直接丢弃该回答、改用更保守的模板:"抱歉,未能检索到足够可信的资料"。import re
def main(answer: str) -> dict:
has_ref = bool(re.search(r"\d+", answer))
return {"pass": has_ref, "answer": answer if has_ref else ""} -
来源白名单:在重排层对 URL 域名做白名单加权(官方文档域名加分),对营销软文域名降权,从源头降低被低质内容污染的概率。
7.3 复杂问题路由到 R1
当问题属于"多条件对比、数学计算、逻辑推理"类型时,把问题交给 DeepSeek-R1(思维链推理更强):
路由规则(放在工作流的条件分支节点):
- 若问题包含"对比/区别/差异/哪个更好/为什么" → 走 R1 推理分支
- 否则 → 走 V3 快速回答分支
R1 分支在生成时额外注入一条指令:"请先梳理对比维度,再基于参考资料逐维度对比,每个维度结论后标注引用编号。"R1 的长思维链在这里的价值是:把"搜索到的零散事实"组织成"结构化的对比结论",而不是罗列一堆互相矛盾的片段。
八、端到端工作流编排与效果演示
8.1 Dify 工作流节点清单
把以上组件在 Dify 可视化画布中串起来,完整节点清单如下:
| 序号 | 节点类型 | 作用 |
|---|---|---|
| 1 | 开始 | 接收用户输入 question |
| 2 | 当前时间 | 注入 current_date(供时效改写) |
| 3 | LLM | 查询改写(DeepSeek-V3) |
| 4 | 代码 | 多源检索(Bing + GitHub,并发 + 去重) |
| 5 | 代码 | 片段切分与格式化 |
| 6 | LLM | 相关性重排(DeepSeek-V3,输出 JSON) |
| 7 | 代码 | 解析重排结果,过滤低分条目 |
| 8 | 模板 | 生成带 n 编号的参考资料块 |
| 9 | 条件分支 | 复杂问题路由(V3 / R1) |
| 10 | LLM | 生成回答(带引用标注) |
| 11 | 代码 | 引用校验(无引用则兜底) |
| 12 | 结束 | 返回最终回答 + 参考资料列表 |
8.2 实测效果
在 Flexus X 实例部署的 Dify 平台上,对搜索 Agent 进行一组典型测试:
提问:"2026 年上半年,国产大模型市场有哪些值得关注的新发布?"
改写层输出:
2026年上半年 国产大模型 新发布 盘点
2026年 中国 大模型 厂商 发布 汇总
重排后保留的片段 (示意):
-
1 来源:某科技媒体 2026-06 报道《上半年国产大模型发布盘点》
-
2 来源:某厂商官网公告(2026-05)
-
3 来源:CSDN 技术社区讨论帖(2026-06)
最终回答(节选):
2026 年上半年国产大模型市场保持高密度发布节奏。基础模型层面,多家厂商在长上下文与推理效率上展开竞争,例如某厂商于 5 月发布的旗舰模型将上下文窗口提升至 256K2;开源生态方面,社区对轻量化小模型的关注度明显上升1。值得注意的是,行业重心正从"参数竞赛"转向"推理成本与落地效率",多家厂商同步发布了配套的推理优化工具链3。资料中未提及具体市场份额数据,如需该数据可进一步检索行业报告。
可以看到:每个结论都有编号对应,无法证实的部分明确标注"资料中未提及"------这正是联网搜索 Agent 区别于"裸聊大模型"的核心价值。
8.3 延迟与成本实测参考
| 阶段 | 耗时(参考) | 说明 |
|---|---|---|
| 查询改写 | ~0.8s | V3 短输出,一次调用 |
| 多源检索 | ~1.5s | 并发执行,受最慢源限制 |
| 相关性重排 | ~2.0s | 10 条片段逐条判断 |
| 回答生成 | ~3-5s | 流式输出,与答案长度相关 |
| 端到端 | ~8-10s | 首次回答延迟(不含流式渲染) |
成本侧,一次完整问答的 token 消耗约为:改写 200 + 重排 1500 + 生成 1500 ≈ 3200 token(含输入输出),按 MaaS 商用服务单价折算单次问答成本在几分钱量级------这也是"MaaS 按量付费"模式在搜索 Agent 这类"低频但高质量"场景下的成本优势。
九、踩坑指南:四个高频问题
9.1 搜索结果"答非所问"
现象 :改写后的查询与原问题偏离。
排查:先单独看改写层输出------这是 Dify 工作流可观测性的用武之地,每个节点都能看到输入输出。若改写失败,多半是 Prompt 中示例不足,给改写节点补充 2-3 个"口语→查询"的 few-shot 示例即可。
9.2 引用编号错乱
现象 :回答中的 1 与文末来源对不上。
根因 :生成节点直接拿到了原始列表,而不是编号格式化后的模板。
修复:确保生成节点只读取"模板节点"的输出,且模板节点中编号与 URL 的映射顺序保持稳定(不要在同一工作流里对列表排序)。
9.3 某搜索源频繁超时
现象 :端到端延迟飙到 30s+。
根因 :某源 API 不稳定,拖慢整体。
修复:代码节点中给每个源独立 try/except,超时直接返回空列表并记录日志;同时把"并发数"与"超时时间"设为工作流变量,便于线上调参。
9.4 回答太长、超出预算
现象 :生成 token 超支。
修复 :在 LLM 节点设置 max_tokens 上限(如 1500),并在提示词中限定"回答控制在 300 字以内,结论先行"。对搜索问答场景,短而准 远胜于长而全。
十、总结与延伸
本文基于华为云 MaaS 平台的 DeepSeek 商用推理服务和 Flexus X 一键部署的 Dify 平台,从零构建了企业级联网搜索 Agent。回顾四个核心组件:
- 查询改写------把口语翻译成搜索引擎听得懂的话,附时间感知;
- 多源检索------通用搜索 + 垂直社区 + 内部知识库,并发合并去重;
- 相关性重排------LLM 逐条打分,只留高相关、高时效、高权威的片段;
- 引用溯源------编号绑定来源,无引用即兜底,让回答可验证、可追溯。
这套架构的最大价值不是"能上网搜",而是**"每一步都可观测、可替换、可兜底"**------每一层都是独立节点,出了问题能精确定位;每一层都有降级方案,单点故障不会拖垮整体。
延伸方向(也是继续探索的空间):
- 联网 + 私有知识库混合问答:把 Dify 知识库检索并入多源层,实现"公网查证 + 私网补全";
- 搜索记忆与追问:接入对话历史,让 Agent 能基于上文追问("再具体说说第二家的方案");
- 定时自动检索:用 Dify 的定时触发能力,让 Agent 周期性抓取竞品动态并生成摘要报告;
- 结果聚类与去重增强:引入向量聚类,把多个来源对同一事件的报道合并成"综合结论",减少信息冗余。
搜索 Agent 的工程本质,是在"模型的生成能力"和"世界的实时信息"之间架一座可验证的桥。桥的每一根梁柱------改写、检索、重排、溯源------都是可以独立打磨的工程组件,也是拉开普通 Demo 与生产系统差距的地方。
DeepSeek 实战指南系列 🔗 从零手写 DeepSeek 推理优化 | MaaS 平台 DeepSeek 部署全攻略 | DeepSeek R1 + Dify Agent 企业级实战
Dify 实战系列 🔗 Dify 知识库问答 Agent 从零搭建 | Flexus X 实例性能深度评测