你有过这种体验吗?问大模型「2026年最新发布的显卡有哪些」,它一本正经地编出几个不存在的型号。或者问「今天的天气怎么样」,它直接拒绝回答。
这是因为大模型的知识截止于训练数据,它不是「实时联网」的。今天我们就来彻底解决这个问题——用 联网搜索 API + 本地 RAG 构建一个混合检索架构,让大模型既能查网络最新资讯,又能检索你的私有文档。
为什么需要混合检索?
纯联网搜索和纯 RAG 各有硬伤,合在一起才是完整方案:
| 维度 | 纯联网搜索(Search API) | 纯 RAG | 混合检索 |
|---|---|---|---|
| 实时性 | ✅ 最新信息 | ❌ 依赖已有文档 | ✅ 互补 |
| 私有知识 | ❌ 无法访问 | ✅ 精确检索 | ✅ 兼顾 |
| 稳定性 | ❌ 依赖搜索结果质量 | ✅ 本地可控 | ✅ 降级策略 |
| 延迟 | 中(API 调用) | 低 | 中(并行执行) |
| 成本 | 中 | 低 | 可控 |
| 适用场景 | 新闻/热门话题 | 企业文档/FAQ | 全场景 |
核心思路:用户提问时,同时查询搜索引擎和本地向量数据库,把两边结果融合后喂给大模型,由大模型综合判断回答。
架构设计
用户提问
│
├──→ SerpAPI / Bing API ──→ 网络搜索结果 ──┐
│ ├──→ 合并去重 ──→ 提示词组装 ──→ 大模型 ──→ 最终回答
└──→ 向量数据库(ChromaDB)──→ 本地文档片段 ──┘
第 1 步:配置联网搜索 API
先准备联网搜索能力。这里我用 SerpAPI(支持 Google/Bing 等多个搜索引擎),国内用户也可以选择 Bing Web Search API(无需翻墙,Azure 即可开通)。
# serpapi_search.py — 联网搜索封装
import os
import requests
from typing import List, Dict
class WebSearcher:
"""联网搜索封装,支持 SerpAPI 和 Bing API"""
def __init__(self, provider: str = "serpapi", api_key: str = None):
self.provider = provider
self.api_key = api_key or os.getenv("SEARCH_API_KEY")
def search(self, query: str, num_results: int = 5) -> List[Dict]:
"""执行联网搜索,返回格式化结果"""
if self.provider == "serpapi":
return self._search_serpapi(query, num_results)
elif self.provider == "bing":
return self._search_bing(query, num_results)
else:
raise ValueError(f"不支持的搜索引擎: {self.provider}")
def _search_serpapi(self, query: str, num: int) -> List[Dict]:
"""SerpAPI Google 搜索"""
url = "https://serpapi.com/search"
params = {
"q": query,
"api_key": self.api_key,
"engine": "google",
"num": num,
"gl": "cn", # 中国地区
"hl": "zh-cn" # 中文结果
}
resp = requests.get(url, params=params, timeout=10)
data = resp.json()
results = []
for item in data.get("organic_results", [])[:num]:
results.append({
"title": item.get("title", ""),
"snippet": item.get("snippet", ""),
"link": item.get("link", ""),
"source": "web"
})
return results
def _search_bing(self, query: str, num: int) -> List[Dict]:
"""Bing Web Search API(Azure 版本,国内直连)"""
url = "https://api.bing.microsoft.com/v7.0/search"
headers = {"Ocp-Apim-Subscription-Key": self.api_key}
params = {"q": query, "count": num, "mkt": "zh-CN"}
resp = requests.get(url, headers=headers, params=params, timeout=10)
data = resp.json()
results = []
for item in data.get("webPages", {}).get("value", [])[:num]:
results.append({
"title": item.get("name", ""),
"snippet": item.get("snippet", ""),
"link": item.get("url", ""),
"source": "web"
})
return results
# 使用示例
if __name__ == "__main__":
searcher = WebSearcher(provider="serpapi", api_key="your-key-here")
results = searcher.search("2026年最新AI大模型发布", num_results=5)
for r in results:
print(f"标题: {r['title']}")
print(f"摘要: {r['snippet'][:100]}...")
print(f"链接: {r['link']}\n")
第 2 步:搭建本地向量检索(RAG 部分)
使用 ChromaDB 做向量存储,用通义千问 Embedding API 做文本向量化(国内稳定直连):
# rag_retriever.py — 本地知识检索
import chromadb
from openai import OpenAI # 兼容通义千问 API
class LocalRAGRetriever:
"""本地 RAG 检索器"""
def __init__(self, collection_name: str = "my_knowledge"):
self.client = chromadb.PersistentClient(path="./chroma_db")
self.collection = self.client.get_or_create_collection(
name=collection_name,
metadata={"hnsw:space": "cosine"}
)
# 使用通义千问 Embedding(兼容 OpenAI SDK)
self.embed_client = OpenAI(
api_key="your-qwen-api-key",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def embed(self, texts: List[str]) -> List[List[float]]:
"""调用通义千问 Embedding API"""
resp = self.embed_client.embeddings.create(
model="text-embedding-v3",
input=texts
)
return [d.embedding for d in resp.data]
def add_documents(self, documents: List[str], metadatas: List[Dict] = None):
"""向向量库添加文档"""
ids = [f"doc_{i}" for i in range(len(documents))]
embeddings = self.embed(documents)
self.collection.add(
ids=ids,
documents=documents,
embeddings=embeddings,
metadatas=metadatas or [{}] * len(documents)
)
def search(self, query: str, top_k: int = 5) -> List[Dict]:
"""检索相关文档"""
query_embedding = self.embed([query])[0]
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
docs = []
for i in range(len(results["documents"][0])):
docs.append({
"content": results["documents"][0][i],
"score": 1 - results["distances"][0][i], # cosine → 相似度
"source": "local"
})
return docs
# 初始化并添加文档
if __name__ == "__main__":
retriever = LocalRAGRetriever()
# 添加企业私有文档
retriever.add_documents([
"公司内部 API 文档:订单接口 POST /api/v1/orders,需要传 product_id 和 quantity",
"数据库连接信息:MySQL 主库地址 10.0.1.100:3306,读写分离从库 10.0.1.101",
])
results = retriever.search("订单接口怎么调用", top_k=3)
for r in results:
print(f"[{r['score']:.3f}] {r['content'][:80]}...")
第 3 步:混合检索核心引擎
把联网搜索和本地 RAG 的结果合并、去重、排序:
# hybrid_search.py — 混合检索引擎
from typing import List, Dict
from serpapi_search import WebSearcher
from rag_retriever import LocalRAGRetriever
class HybridSearcher:
"""混合检索引擎:联网搜索 + 本地 RAG"""
def __init__(self):
self.web_searcher = WebSearcher()
self.rag_retriever = LocalRAGRetriever()
def search(self, query: str, web_k: int = 5, local_k: int = 5) -> List[Dict]:
"""并行执行联网搜索和本地检索"""
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
web_future = executor.submit(
self.web_searcher.search, query, web_k
)
local_future = executor.submit(
self.rag_retriever.search, query, local_k
)
web_results = web_future.result()
local_results = local_future.result()
# 去重:基于内容相似度的简单去重
all_results = self._deduplicate(web_results + local_results)
return all_results
def _deduplicate(self, results: List[Dict]) -> List[Dict]:
"""去重:去除高度重复的片段"""
seen = set()
unique = []
for r in results:
# 取前 50 个字符作为指纹
fingerprint = (r.get("snippet") or r.get("content", ""))[:50]
if fingerprint not in seen:
seen.add(fingerprint)
unique.append(r)
return unique
def build_prompt(self, query: str, results: List[Dict]) -> str:
"""组装给大模型的提示词"""
context_parts = []
web_results = [r for r in results if r.get("source") == "web"]
local_results = [r for r in results if r.get("source") == "local"]
if web_results:
context_parts.append("## 网络搜索结果\n")
for i, r in enumerate(web_results, 1):
context_parts.append(
f"{i}. **{r['title']}**\n"
f" {r['snippet']}\n"
f" 来源: {r['link']}\n"
)
if local_results:
context_parts.append("\n## 本地知识库结果\n")
for i, r in enumerate(local_results, 1):
context_parts.append(
f"{i}. {r['content']}\n"
f" (相关度: {r['score']:.2f})\n"
)
prompt = f"""你是一个智能助手,根据以下检索结果回答用户问题。
如果检索结果不足以回答,请诚实说明。
{"".join(context_parts)}
用户问题: {query}
请基于以上信息给出准确、有引用的回答。如果是网络信息,请标注来源链接。"""
return prompt
# 使用示例
if __name__ == "__main__":
hybrid = HybridSearcher()
results = hybrid.search("2026年AI编程工具排行", web_k=5, local_k=3)
prompt = hybrid.build_prompt("2026年AI编程工具排行", results)
print(prompt)
第 4 步:接入大模型,生成最终回答
# assistant.py — 大模型对话
from openai import OpenAI
from hybrid_search import HybridSearcher
class AIAssistant:
def __init__(self):
self.searcher = HybridSearcher()
self.llm = OpenAI(
api_key="your-api-key",
base_url="https://api.deepseek.com/v1" # 或通义千问 / 腾讯混元
)
def chat(self, query: str, model: str = "deepseek-chat") -> str:
# 步骤1:混合检索
results = self.searcher.search(query)
# 步骤2:构建提示词
prompt = self.searcher.build_prompt(query, results)
# 步骤3:调用大模型
response = self.llm.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "你是专业的技术助手,回答需基于给定检索信息,并标注来源。"},
{"role": "user", "content": prompt}
],
temperature=0.3,
max_tokens=2000
)
return response.choices[0].message.content
# 运行
if __name__ == "__main__":
assistant = AIAssistant()
answer = assistant.chat("2026年有什么新出的AI大模型?")
print(answer)
第 5 步:优化——结果融合策略与缓存
最后几个生产级别的优化技巧:
5.1 智能权重融合
不是简单拼接,而是根据问题类型动态调整:
# 判断问题是否需要联网
TRIGGER_KEYWORDS = ["今天", "最新", "最近", "2026", "当前", "实时", "现在", "刚刚"]
def need_web_search(query: str) -> bool:
"""判断问题是否需要联网搜索"""
time_sensitive = any(kw in query for kw in TRIGGER_KEYWORDS)
specific_entity = len(query) > 15 # 长问题通常涉及具体事物
return time_sensitive or specific_entity
5.2 搜索结果缓存
对同一搜索词 10 分钟内不重复调用 API:
import hashlib
import time
from functools import lru_cache
@lru_cache(maxsize=128)
def cached_search(query: str, timestamp: int):
"""按分钟级别缓存搜索结果"""
return hybrid.searcher.search(query)
5.3 降级策略
联网搜索失败时,自动退回纯 RAG 模式,确保服务可用。
性能与成本对比
| 方案 | 首次响应 | 搜索成本 | 适用场景 |
|---|---|---|---|
| 纯大模型(无检索) | 快(~1s) | 仅 Token 费 | 常识问答 |
| 纯 RAG | 中(~2s) | Embedding + Token | 企业文档 QA |
| 纯联网搜索 | 慢(~3-5s) | 搜索 API + Token | 实时资讯 |
| 混合检索(本文) | 中(~2-4s) | 搜索 + Embedding + Token | 全场景 |
| 混合 + 缓存 | 快(~1s) | 最低 | 生产环境推荐 |
总结
混合检索 = 联网搜索 + RAG,核心要点:
- 并行执行:联网搜索和本地检索同时进行,不串行等待
- 结果去重:简单的指纹去重就能避免信息冗余
- 智能路由:不是所有问题都需要联网,通过关键词判断
- 缓存降级:搜索结果缓存 + 降级策略,保障生产可用性
- 国内友好:Bing API + 通义千问 Embedding,全程无需翻墙
这套方案我已在公司内部知识库中落地,日均处理 2000+ 次查询,联网搜索命中率约 35%,整体回答满意度从纯 RAG 的 72% 提升到了 89%。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~
博客:y1cloud.cn
