AI Agent接入Web Search API:从RAG原理到Python实战

发布时间:2026/8/29 6:19:58
AI Agent接入Web Search API:从RAG原理到Python实战 作为长期在做 Agent 类应用的开发者我一直在寻找比“拿传统搜索接口强行套 Prompt”更顺手的方案。如果搜不准、返回噪音大、链接过期Agent 再聪明也容易一本正经地胡说八道。Keenable 这类面向 AI Agent 的 Web Search API 出现后思路确实不太一样它不止是给你 10 条链接而是把检索结果整理成更容易被大模型消费的上下文。这篇文章会围绕“AI Agent 如何正确接入 Web Search API”展开先讲清楚为什么 Agent 需要专用搜索接口再拆解核心概念最后给出完整的 Python 集成案例、常见报错和工程落地建议。无论你是在做聊天机器人、数据分析助手还是自动化工作流都可以直接参考这套思路。1. 为什么 AI Agent 需要独立的 Web 搜索 API1.1 从知识截止到实时检索大语言模型最大的短板之一就是“知识有截止日期”。不管是 GPT 还是开源模型训练数据普遍停留在某个时间点之前。当用户问“今天有什么新闻”“最新版本改了什么”时模型如果只靠内部参数推理结果大概率是过时的甚至直接编造。常见的解决方案是 RAGRetrieval-Augmented Generation检索增强生成。简单说就是先通过检索系统拿到最新的外部信息再把信息拼进提示词让模型基于这些资料生成答案。而 Web Search API 就是 RAG 体系中负责“实时信息获取”的关键环节。也就是说没有 Web Search APIAgent 只能是一个“嘴强王者”有了 Web Search APIAgent 才真正有了“眼睛”。1.2 传统搜索 API 与 Agent 搜索 API 的差异过去几年大家用得比较多的搜索 API绝大多数是为“人类搜索结果页”设计的。返回结果通常是标题URL摘要片段站点信息类似图片、视频、知识面板这类附加模块这种结构对浏览器用户是够用的人眼可以快速扫一下标题和摘要决定要不要点进去。但对 AI Agent 来说问题很明显维度传统搜索 API面向 Agent 的搜索 API输出结构面向网页展示字段多而杂面向 LLM 消费结构精简、语义化结果相关性偏向 SEO 排名更强调答案完整度和上下文上下文友好度需要二次清洗直接给出可拼接的文本块工具调用需要自己封装工具描述原生适配 function calling引用来源依赖自己拼接返回结构化来源元数据Keenable 这类产品的“different”本质上是把过去开发者自己要做的一堆脏活、累活比如结果清洗、去重、摘要截断、来源格式标准化提前在 API 服务端处理掉了。1.3 Keenable 的定位与设计思路从项目定位看Keenable 是“for AI agents”的 Web Search API所以它的设计不是从传统搜索接口改一改参数而是重新考虑了一次“检索结果最终是被谁消费的”。我们可以把这类 API 的特点概括成三点面向工具调用场景返回结果直接符合 function calling 的规范Agent 拿到 JSON 后不用做复杂的二次解析。内容结构化每条结果包含稳定的 title、url、content 字段甚至可能包含发布时间、作者、站点类型等更细的元数据。上下文友好API 返回的摘要内容经过裁剪长度接近 LLM 的单条上下文窗口不会被超长文本撑爆 Token。另外Keenable 在设计上对“多轮检索”场景比较友好。Agent 经常会根据初步结果继续追问比如第一轮搜“Python 3.13 新特性”第二轮可能搜“Python 3.13 free-threading 性能”。好的 Agent 搜索 API 需要能处理这种链条式检索而不是每次都是独立的冷启动查询。2. 面向 Agent 的搜索 API 核心概念拆解2.1 搜索 API 的基本组成部分不管是什么 Web Search API基本组成都绕不开这几个要素Query查询词这是 Agent 传给搜索服务的关键词或自然语言问句。面向 Agent 的 API 一般会建议直接传自然语言问句而不是必须拆成关键词。引擎类型Engine Type有些 API 支持普通网页搜索、新闻搜索、图片搜索、学术搜索等不同引擎。Agent 可以根据任务类型选择合适的引擎。结果条数Max Results / Top K控制每次请求返回多少条结果。结果太多会浪费 Token太少又可能漏掉关键信息。结构化数据Structured Data好的 Agent 搜索 API 会返回足够规范化的字段比如标题、链接、发布时间、摘要正文等方便 Agent 直接使用。下面是一个通用的请求-响应模型示意// 请求体通用结构具体字段以你使用的API文档为准 { query: Whats new in Python 3.13?, max_results: 5, engine: web, time_range: year }// 响应体通用结构具体字段以你使用的API文档为准 { results: [ { title: Python 3.13.0 released, url: https://www.python.org/downloads/release/python-3130/, content: Python 3.13.0 is the newest major release of the Python programming language..., published_at: 2024-10-07T00:00:00Z } ] }这里要特别强调不同 API 字段名的差异非常大。比如有的 API 返回snippet有的返回content有的返回text真实集成时必须以服务商文档为准以上只是通用参考。2.2 面向 Agent 的优化设计点如果要设计一个“面向 Agent 的搜索 API”需要在下面几个方面做特殊优化。第一查询理解。传统关键词搜索要求用户把问题拆成词而 Agent 搜索 API 要能接受完整自然语言。比如用户问“帮我查一下 2025 年全球 AI 芯片市场规模预测”Agent 可能直接把这个完整句子传入 APIAPI 内部再做意图识别和关键词提取。第二结果压缩。网页原文可能非常长一个网页几万字很正常但 LLM 上下文窗口有限。API 需要在服务端做摘要、抽取关键词、识别核心实体把一篇长文压缩成几百字以内的“上下文块”这样 Agent 才能低成本地消费。第三引用追踪。RAG 应用最怕“模型编造来源”。面向 Agent 的搜索 API 会在返回结果中附带原始 URL、标题、发布时间等信息。这样 Agent 在回答时可以明确说“根据 Python 官网 2024 年 10 月的公告……”而不是模糊地说“根据网络资料”。第四时间感知。对很多查询来说时间是一个重要约束。比如“Python 最新版本”和“Python 最受欢迎的版本”是两个完全不同的问题。API 最好能自动识别查询对时效性的敏感度决定是否限制时间范围。2.3 一个搜索请求的完整生命周期为了后面代码示例更容易理解我们先梳理一次 Agent 搜索请求的完整流程。用户问题 ↓ Agent 判断需要实时信息 ↓ 构造搜索工具描述发给 LLM ↓ LLM 返回工具调用指令query、top_k 等 ↓ Agent 调用 Web Search API ↓ 拿到结构化搜索结果 ↓ 拼接上下文重新发给 LLM ↓ LLM 基于搜索内容生成最终答案这个流程看起来不复杂但在真实代码里每一步都有不少细节。比如工具描述怎么写才容易被 LLM 正确触发搜索结果怎么裁剪多轮检索时怎么保留上下文这些我会在第 4 节的代码案例中逐一演示。3. 环境准备与通用配置3.1 运行环境版本说明我默认使用如下环境你在实际项目中可以根据自身情况调整操作系统macOS / Linux / Windows 均可示例代码没有平台依赖Python3.10 及以上示例使用类型注解3.8 也可以运行部分写法需微调依赖库requestsHTTP 客户端、可选openai如果你把 Agent 接 GPT 系列模型IDE任意推荐 VS Code 或 PyCharm安装依赖命令pip install requests openai python-dotenv需要说明的是上面这组版本只是常见组合并不代表 Keenable 或任何具体 API 的强制要求。你使用的搜索 API 版本、LLM 模型版本要以对应官方文档为准。3.2 获取 API KeyWeb Search API 一般都需要 API Key 鉴权。不同平台的获取流程不同但大同小异注册开发者账号。在控制台创建一个应用。获取 API Key有时也叫 Access Token。根据套餐开通对应的搜索能力。安全提醒API Key 一定要放在环境变量或配置中心不要硬编码到代码仓库里。# 在项目根目录创建 .env 文件 SEARCH_API_KEYyour_search_api_key_here SEARCH_API_BASE_URLhttps://api.example.com # 可选如果你接 OpenAI OPENAI_API_KEYyour_openai_api_key_here然后通过python-dotenv加载# 文件路径config.py import os from dotenv import load_dotenv load_dotenv() SEARCH_API_KEY os.getenv(SEARCH_API_KEY) SEARCH_API_BASE_URL os.getenv(SEARCH_API_BASE_URL) OPENAI_API_KEY os.getenv(OPENAI_API_KEY)3.3 项目基础结构为了让代码清晰可维护我建议按下面的结构组织项目agent-search-demo/ ├── .env # 环境变量不要提交到仓库 ├── config.py # 配置读取 ├── search_client.py # 搜索 API 客户端封装 ├── agent.py # Agent 主逻辑 ├── main.py # 入口脚本 └── requirements.txt # 依赖声明这样做的原因是把“搜索能力”和“Agent 逻辑”解耦。如果以后从某个搜索 API 迁移到另一个只需要改search_client.pyAgent 主流程不用动。4. 完整实战让 AI Agent 具备搜索能力4.1 设计 Agent 搜索流程我们这次要实现一个比较真实的最小 Agent它接收用户问题判断是否需要搜索如果需要就调用 Web Search API然后把搜索结果拼接进提示词最后让 LLM 生成带引用的答案。为了不把示例绑定到某个具体搜索服务商我先把 HTTP 调用部分抽象出来。无论你用的是 Keenable 还是其他 web search API只需要调整search_client.py里的base_url和请求体字段名。4.2 封装搜索客户端先写搜索客户端的核心类。这里使用通用的requests来实现 POST 请求并将 JSON 响应解析为统一的数据结构。# 文件路径search_client.py from typing import Dict, List, Optional import requests class WebSearchClient: 通用 Web Search API 客户端封装。 这是一个通用骨架真实对接时请以你使用的 API 文档为准 主要调整 _request_params 方法和 _parse_response 方法即可。 def __init__( self, api_key: str, base_url: str, timeout: int 10, ): self.api_key api_key self.base_url base_url.rstrip(/) self.timeout timeout self.session requests.Session() self.session.headers.update({ Authorization: fBearer {api_key}, Content-Type: application/json, }) def search( self, query: str, max_results: int 5, time_range: Optional[str] None, ) - List[Dict]: 执行一次搜索返回统一结构的结果列表。 参数 query: 自然语言查询 max_results: 返回结果条数 time_range: 时间范围例如 day / week / month / year resp self.session.post( f{self.base_url}/search, jsonself._build_payload(query, max_results, time_range), timeoutself.timeout, ) resp.raise_for_status() data resp.json() return self._parse_response(data) def _build_payload( self, query: str, max_results: int, time_range: Optional[str], ) - Dict: payload: Dict { query: query, max_results: max_results, } if time_range: payload[time_range] time_range return payload def _parse_response(self, data: Dict) - List[Dict]: 把不同 API 的响应统一成易消费结构。 很多 API 返回的字段可能是 title / url / snippet 也可能是 title / link / content。这里统一做映射。 raw_results data.get(results, data.get(items, [])) parsed [] for item in raw_results: parsed.append({ title: item.get(title, ), url: item.get(url) or item.get(link, ), content: ( item.get(content) or item.get(snippet) or item.get(text) or ), published_at: item.get( published_at, item.get(publish_time, ), ), }) return parsed4.3 基于搜索结果增强提示词拿到搜索结果之后不能直接把整个返回体丢给 LLM那样会浪费 Token而且可能因为结构复杂影响模型理解。建议先做一步“上下文构建”。# 文件路径agent.py部分代码 from typing import Dict, List def build_context(results: List[Dict], max_items: int 5) - str: 把搜索结果整理成提示词友好文本。 blocks [] for idx, item in enumerate(results[:max_items], start1): title item.get(title, ).strip() url item.get(url, ).strip() content item.get(content, ).strip() block f[{idx}] {title}\n来源: {url}\n内容: {content} blocks.append(block) return \n\n.join(blocks)这样拼接出来的文本每一个来源都带序号和 URL后续让 LLM 回答时可以按序号引用便于生成可追溯的答案。4.4 工具调用方式接入 Agent现代 Agent 通常采用 function calling 模式LLM 决定调用什么工具、传什么参数Agent 执行工具后再把结果返回给 LLM。下面是接入 OpenAI 风格 tool calling 的示例。如果你使用的是其他 LLM比如 Claude、通义千问、DeepSeek调用方式大同小异。# 文件路径agent.py import json from typing import Dict, List from config import OPENAI_API_KEY from search_client import WebSearchClient try: from openai import OpenAI except ImportError: OpenAI None class SearchAgent: def __init__( self, search_client: WebSearchClient, model: str gpt-4o-mini, ): self.search_client search_client self.model model if OpenAI is None: raise RuntimeError(未安装 openai 库请先执行 pip install openai) self.client OpenAI(api_keyOPENAI_API_KEY) property def tools(self) - List[Dict]: 工具描述用于让模型理解如何使用搜索能力。 return [ { type: function, function: { name: web_search, description: 当用户问题涉及实时信息、最新新闻、外部网页内容时使用此工具检索网络资料。, parameters: { type: object, properties: { query: { type: string, description: 用户问题建议使用自然语言完整问题例如 Python 3.13 有哪些新特性, }, max_results: { type: integer, description: 返回结果数量默认 5, minimum: 1, maximum: 10, }, }, required: [query], }, }, } ] def run(self, question: str, max_turns: int 3) - str: 执行一问一答支持最多多轮工具调用。 messages [{role: user, content: question}] turn 0 while turn max_turns: resp self.client.chat.completions.create( modelself.model, messagesmessages, toolsself.tools, tool_choiceauto, ) msg resp.choices[0].message if not msg.tool_calls: # 模型没有调用工具直接返回最终答案 return msg.content or messages.append(msg) # 逐个执行工具调用 for tool_call in msg.tool_calls: if tool_call.function.name ! web_search: continue args json.loads(tool_call.function.arguments) query args.get(query, question) max_results args.get(max_results, 5) print(f[Tool] web_search: {query}) results self.search_client.search( queryquery, max_resultsmax_results, ) context build_context(results) tool_message { role: tool, tool_call_id: tool_call.id, content: context, } messages.append(tool_message) turn 1 # 达到最大轮数仍未结束做一次强制收尾 final_resp self.client.chat.completions.create( modelself.model, messagesmessages, ) return final_resp.choices[0].message.content or 4.5 运行与验证最后写一个入口脚本把整个流程串起来。# 文件路径main.py from config import SEARCH_API_BASE_URL, SEARCH_API_KEY from search_client import WebSearchClient from agent import SearchAgent def main(): search_client WebSearchClient( api_keySEARCH_API_KEY, base_urlSEARCH_API_BASE_URL, ) agent SearchAgent(search_clientsearch_client) question 2025 年值得关注的 AI Agent 开发框架有哪些 answer agent.run(question) print( 最终答案 ) print(answer) if __name__ __main__: main()运行命令python main.py如果一切正常你会先看到工具调用的日志然后看到最终答案。示例输出类似[Tool] web_search: 2025 年值得关注的 AI Agent 开发框架有哪些 最终答案 根据 2025 年的公开资料以下框架值得关注 1. LangGraph - 适合构建有状态的多步骤 Agent 工作流... 2. AutoGen - 微软开源的动态多 Agent 协作框架... 3. CrewAI - 面向任务编排的角色化 Agent 团队框架... ...如果你没有可用的 LLM Key只想先测试搜索客户端可以单独跑一行# 临时测试脚本 from config import SEARCH_API_BASE_URL, SEARCH_API_KEY from search_client import WebSearchClient client WebSearchClient(SEARCH_API_KEY, SEARCH_API_BASE_URL) results client.search(OpenAI o1 推理模型, max_results3) for r in results: print(r[title], r[url])5. 常见问题与排查思路实际接入时你大概率会遇到下面几类问题。我整理了一个排查表问题现象常见原因解决思路请求返回 401 / 403API Key 错误、过期或权限不足检查 Key 是否写对确认账号是否开通搜索服务返回结果为空搜索词太生僻或 API 未指定引擎增加关键词、换通用引擎、检查 time_range 是否过窄Agent 不调用搜索工具工具描述不够清晰或模型不理解改写工具 description补充触发条件示例前端请求超时搜索 API 响应慢或网络问题调大 timeout增加重试机制或切换更近的服务节点返回内容过长Token 超限结果条数太多或单条内容太长限制 max_results在 API 层或代码层做截断回答中出现“幻觉引用”搜索结果拼接不完整模型分不清来源显式要求模型引用 [n] 序号禁止编造链接费用快速上涨每次搜索都走多轮循环Token 消耗大增加缓存、控制 max_turns、搜索前先做意图判断5.1 Agent 不调用工具怎么办这是最常见的集成问题。大多数情况不是代码错了而是工具描述不够“吸引”模型触发。前文tools定义里我把web_search的description写成了“当用户问题涉及实时信息、最新新闻、外部网页内容时”。这个描述已经比较明确但在实际项目中你还可以更具体description: 当用户询问的内容非常有可能依赖最新数据、新闻事件、产品版本、市场行情 或者你确认自己的训练数据中没有相关信息时优先调用 web_search 获取最新资料。另外要把用户问题改写得更完整。很多 Agent 框架会内置一个“query rewriting”步骤把“它会不会下蛋”改写成“企鹅会下蛋吗”从而提升搜索效果。5.2 搜索结果与问题不相关如果 API 返回的内容偏了可能是搜索 API 服务端的关键词抽取能力有限。这时可以人工改写查询词。例如用户问题“Python 比 Java 好在哪里”如果直接传这个句子搜索 API 可能返回大量主观讨论帖。更稳的做法是先让 LLM 拆解成搜索词用户问题Python 比 Java 好在哪里 搜索词1Python vs Java 性能 对比 搜索词2Python Java 适用场景 区别然后分别搜索再合并结果。5.3 上下文过长怎么处理搜索结果动辄几千字如果一次返回 10 条很容易超出上下文窗口。处理策略有三个减少max_results把结果数量从 10 降到 5。对每一条结果再次调用 LLM 做摘要只保留和用户问题相关的句子。在 API 层配置内容长度上限比如只取开头 500 字符。其中第二点效果最好但会消耗额外 Token适合对答案质量要求较高的场景。6. 最佳实践与工程建议6.1 查询改写应该由 LLM 或规则完成搜索 API 虽然能理解自然语言但对“口语化、有歧义、指代不明”的提问效果会打折扣。工程上建议至少做一层查询改写。示例函数def rewrite_query(question: str) - List[str]: 把口语问题拆成搜索关键词。这里演示规则写法生产环境建议用 LLM。 # 简单去问问号和礼貌用语 question question.replace(我想知道, ).replace(帮我查一下, ) question question.strip(。? ) return [question]更复杂的场景建议把“改写”作为一个独立工具交给 LLM 完成。6.2 缓存机制非常重要Agent 场景中模型经常会对同一个问题做多次工具调用或者多个用户问相似问题。如果你的 API 按次计费缓存能省下不少成本。缓存策略建议以“归一化之后的查询词”为 key。缓存时间按关键词类型区分新闻资讯类缓存 10 分钟技术文档类缓存 24 小时常识类缓存 7 天。缓存数据除了搜索结果还要保留抓取时间避免长期不更新。示例使用 Python 内置functools.lru_cache做进程内缓存from functools import lru_cache lru_cache(maxsize256) def cached_search(query: str, max_results: int 5): # 这里调用真实的搜索客户端 return tuple(client.search(query, max_resultsmax_results))注意lru_cache的参数必须是可哈希类型返回结果最好转成不可变结构。生产环境建议使用 Redis。6.3 结果去重与排序同一个话题在不同站点可能被多次转载搜索结果中经常出现“标题不同、正文相似”的内容。设计搜索引擎时一般会做去重如果 API 没做你就需要在代码里处理。去重思路按 URL 去重删除完全一样的链接。按标题相似度去重比如计算字符串相似度。按内容摘要去重取前 100 字符做 hash。推荐使用difflib.SequenceMatcher做简单相似度判断from difflib import SequenceMatcher def is_similar(a: str, b: str, threshold: float 0.85) - bool: return SequenceMatcher(None, a, b).ratio() threshold6.4 安全与合规注意事项Web Search API 承担着 Agent 的“外部信息入口”角色也意味着它同时是“风险入口”。工程上不能只关注能跑通还要考虑安全和合规边界。最小权限原则为 Agent 申请独立 API Key不要与生产主账号共用一个 Key权限只开放给需要的引擎和接口。内容安全搜索结果可能包含恶意链接、钓鱼站点、违法内容。Agent 端可以做链接白名单校验或对搜索结果做敏感词过滤。输出校验模型生成答案时要校验引用的 URL 是否真的出现在搜索结果中不允许模型凭空编造来源。操作边界如果 Agent 后续有写操作、购买操作、修改数据能力必须增加人工确认环节不能完全依赖模型判断。6.5 可观测性日志与追踪Agent 接入搜索 API 之后调试难度会明显上升。你看到“答案不对”但很难直接判断是搜索没搜对、上下文拼坏了还是模型理解错了。建议从第一阶段就加上结构化日志。每次搜索记录原始用户问题改写后的查询词API 返回条数与耗时被拼接进 Prompt 的前几条结果 URL最终答案与前若干字符日志尽量输出为 JSON 格式方便后续接入日志平台。import logging logger logging.getLogger(agent) logger.info( search_finish, extra{ url: url, query: query, result_count: len(results), elapsed_ms: elapsed_ms, }, )7. 总结与下一步学习路线这篇文章从一个真实的天使项目 Keenable 出发聊清楚了面向 AI Agent 的 Web Search API 的几个关键问题它和传统搜索 API 的区别、Agent 搜索流程是怎样的、如何用 Python 封装一个通用搜索客户端、怎么通过 function calling 让模型主动触发搜索以及工程落地时的缓存、安全、日志和排错策略。如果你正在构建自己的 Agent下一步可以按这个顺序深入先用最简代码跑通“搜索 → 拼接上下文 → 模型回答”的最小闭环。再做查询改写与结果裁剪观察答案质量是否提升。然后接入缓存和日志评估成本与响应延迟。最后考虑多工具协同比如让 Agent 同时具备搜索、代码执行、数据库查询能力。实际项目中最优先关注的三个风险点一定是出口真实性与可追溯性、Token 成本控制、以及搜索 API 的鉴权安全。把这三件事做好Agent 能力才能稳定可靠。如果你觉得这套集成思路有帮助建议先收藏备用等真正动手接搜索能力的时候拿出来对照着写。