【从0搭建AI智能体·7】给 Agent 接搜索:实时联网问答,突破知识截止日期

📚 本文是《从 0 搭建你的 AI 智能体》专栏第 7 篇。
上一篇:《多轮对话状态管理与并发安全》

标签:联网搜索 AI Agent Function Calling 网页抓取 大模型 RAG

📌 前言:模型的「记忆」停在了过去

你问大模型「今天有什么新闻」「最新版的 Python 是多少」「某公司昨天的股价」,它要么回答一个过时的答案,要么老实说「我的知识截止到某年某月」。

这是所有大模型的天生局限:它的知识冻结在训练数据的截止日期,之后发生的一切它一无所知。对于新闻、股价、天气、最新文档、实时数据这类问题,光靠模型自己,注定答不好。

解法就是给它接上「眼睛」——实时联网搜索。当模型判断需要最新信息时,让它调用搜索引擎,把搜到的内容读进来,再基于真实的实时资料作答。这其实是第 3 篇 Function Calling + 第 2 篇 RAG 思路的一次实战组合:用工具调用触发搜索,用检索到的内容增强生成。

这一篇,我们从零搭一个能联网的问答 Agent,并处理好「搜索→抓取→喂给模型→标注来源」这条完整链路里的每个坑。

💡 本文适合谁:已掌握 Function Calling 基础、想让 Agent 回答实时问题的开发者。阅读约 13 分钟。

目录

  1. 联网问答的完整链路
  2. 第一步:选一个搜索 API
  3. 第二步:封装搜索工具
  4. 第三步:抓取网页正文(可选但推荐)
  5. 第四步:用 Function Calling 让模型自己决定搜不搜
  6. 第五步:把结果喂回模型 + 标注来源
  7. 完整可跑 Demo
  8. 进阶:搜索质量优化
  9. 常见坑与排查
  10. FAQ
  11. 总结

① 联网问答的完整链路

联网问答不是「模型直接上网」,而是一条你搭的流水线:

  用户提问
     │
     ▼
  模型判断:需要实时信息吗?
     ├─ 否 → 直接用自身知识回答
     └─ 是 → 调用 search 工具
              │
              ▼
        搜索 API 返回 Top-N 结果(标题+摘要+链接)
              │
              ▼
        (可选)抓取网页正文,拿到更完整内容
              │
              ▼
        把搜索结果作为「参考资料」回传给模型
              │
              ▼
        模型基于实时资料组织答案 + 标注来源链接
              │
              ▼
        有据可查的实时回答

看出来了吗——这就是第 3 篇的工具调用(模型决定搜不搜)+ 第 2 篇的 RAG(把资料喂回去增强生成)的合体。学过前面,这篇就是水到渠成。


② 第一步:选一个搜索 API

模型不能直接爬 Google,你得给它一个能用 API 调的搜索服务。常见选择:

搜索 API特点适用
Tavily专为 LLM 设计,直接返回干净摘要,免爬网页AI 应用首选,最省事
SerpAPI封装 Google 结果,功能全要 Google 原始结果
Bing Web Search微软官方,稳定企业级
DuckDuckGo(免费库)duckduckgo_search 库,免 Key快速原型、练手

本文用 Tavily 演示(专为 LLM 优化、返回结果干净),思路对其他 API 通用。

pip install tavily-python openai python-dotenv

.env 加上搜索 Key:

AGENT_KEY=sk-xxx
API_BASE_URL=https://api.example.com/v1
TAVILY_API_KEY=tvly-xxx

💡 为什么推荐 Tavily 这类「AI 原生」搜索:传统搜索 API 返回一堆链接,你还得自己爬网页、清洗正文。Tavily 直接返回提炼过的摘要,省掉第 ④ 步的大量麻烦。原型阶段能省一半功夫。


③ 第二步:封装搜索工具

先把搜索封装成一个普通函数,返回「标题 + 摘要 + 链接」的结构化结果:

import os
from tavily import TavilyClient
from dotenv import load_dotenv

load_dotenv()
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))


def web_search(query, max_results=5):
    """联网搜索,返回结构化结果列表"""
    resp = tavily.search(query=query, max_results=max_results)
    results = []
    for item in resp.get("results", []):
        results.append({
            "title": item.get("title", ""),
            "content": item.get("content", ""),   # Tavily 已提炼的摘要
            "url": item.get("url", ""),
        })
    return results


if __name__ == "__main__":
    for r in web_search("2026 年 Python 最新稳定版本"):
        print(f"- {r['title']}\n  {r['content'][:80]}...\n  {r['url']}\n")

⚠️ 避坑:搜索结果可能很长,全部塞给模型会爆 Token。上面只取摘要(content),并可在下一步限制总长度。别把几万字原文整坨喂给模型


④ 第三步:抓取网页正文(可选但推荐)

如果搜索 API 只给了摘要、而你需要更完整的内容(比如读一篇长文),就得抓网页正文。用 requests + readability 提取核心内容,去掉导航、广告等噪声:

pip install requests readability-lxml lxml_html_clean
import requests
from readability import Document


def fetch_page_text(url, max_chars=3000):
    """抓取网页并提取正文,去除导航/广告等噪声"""
    try:
        html = requests.get(url, timeout=10, headers={
            "User-Agent": "Mozilla/5.0"        # 伪装浏览器,避免被拒
        }).text
        doc = Document(html)
        # readability 提取主体内容
        from lxml import html as lxml_html
        content = lxml_html.fromstring(doc.summary()).text_content()
        return content.strip()[:max_chars]     # 截断,控制长度
    except Exception as e:
        return f"[抓取失败: {e}]"

🎯 要不要抓正文,看场景

  • 问「最新版本号」这种事实 → 摘要就够,不用抓正文
  • 问「详细解读某篇长文」→ 需要抓正文拿完整内容。

抓正文慢(每个 URL 一次 HTTP + 解析),能不抓就不抓。Tavily 这类已经给了不错的摘要,多数场景直接用摘要即可。


⑤ 第四步:用 Function Calling 让模型自己决定搜不搜

关键一步:不是每个问题都要联网。「1+1 等于几」「帮我写首诗」根本不需要搜。让模型用 Function Calling 自己判断该不该搜(复习第 3 篇):

import json
from openai import OpenAI

client = OpenAI(api_key=os.getenv("AGENT_KEY"), base_url=os.getenv("API_BASE_URL"))
MODEL = "standard-agent-v1"

SEARCH_TOOL = [{
    "type": "function",
    "function": {
        "name": "web_search",
        "description": "联网搜索实时信息。当问题涉及最新新闻、实时数据、"
                       "近期事件,或超出你知识截止日期的内容时使用。",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "搜索关键词"}
            },
            "required": ["query"],
        },
    },
}]

🎯 description 决定成败:这里明确写「当涉及最新信息、超出知识截止日期时使用」,模型才能准确判断。写得含糊,它要么该搜不搜、要么啥都搜(浪费钱和时间)。


⑥ 第五步:把结果喂回模型 + 标注来源

搜到结果后,作为「参考资料」回传给模型,并要求它标注来源链接(这是联网问答区别于瞎编的关键——答案可溯源):

def answer_with_search(question):
    messages = [
        {"role": "system", "content":
            "你是能联网的助手。当使用搜索结果回答时,必须在末尾以「参考来源:」"
            "列出用到的链接,确保答案可追溯。若搜索结果不足以回答,如实说明。"},
        {"role": "user", "content": question},
    ]

    # 第 1 轮:模型判断是否要搜
    resp = client.chat.completions.create(
        model=MODEL, messages=messages, tools=SEARCH_TOOL, tool_choice="auto")
    msg = resp.choices[0].message

    if not msg.tool_calls:
        return msg.content                       # 不需要搜,直接回答

    # 执行搜索
    messages.append(msg)
    for tc in msg.tool_calls:
        query = json.loads(tc.function.arguments)["query"]
        results = web_search(query)
        # 把结果整理成带链接的文本
        context = "\n".join(
            f"[{i+1}] {r['title']}\n{r['content']}\n来源: {r['url']}"
            for i, r in enumerate(results))
        messages.append({
            "role": "tool", "tool_call_id": tc.id, "content": context})

    # 第 2 轮:模型基于搜索结果作答
    final = client.chat.completions.create(model=MODEL, messages=messages)
    return final.choices[0].message.content

🎯 强制标注来源的意义:联网问答最大的价值是「可信」。要求模型列出参考链接,用户能自己点进去核实,这比一个无从考证的答案可信得多——也是和「模型瞎编」的本质区别。


⑦ 完整可跑 Demo

整合成一个能联网的问答脚本:

import os, json
from openai import OpenAI
from tavily import TavilyClient
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("AGENT_KEY"), base_url=os.getenv("API_BASE_URL"))
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
MODEL = "standard-agent-v1"

TOOLS = [{"type": "function", "function": {
    "name": "web_search",
    "description": "联网搜索实时信息。涉及最新新闻、实时数据、超出知识截止日期时使用。",
    "parameters": {"type": "object",
                   "properties": {"query": {"type": "string"}}, "required": ["query"]}}}]


def web_search(query):
    resp = tavily.search(query=query, max_results=5)
    return "\n".join(
        f"[{i+1}] {r['title']}\n{r['content']}\n来源: {r['url']}"
        for i, r in enumerate(resp.get("results", [])))


def ask(question):
    messages = [
        {"role": "system", "content":
            "你是能联网的助手。用搜索结果回答时,末尾以「参考来源:」列出链接。"},
        {"role": "user", "content": question},
    ]
    msg = client.chat.completions.create(
        model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto"
    ).choices[0].message
    if not msg.tool_calls:
        return msg.content
    messages.append(msg)
    for tc in msg.tool_calls:
        q = json.loads(tc.function.arguments)["query"]
        print(f"[联网搜索] {q}")
        messages.append({"role": "tool", "tool_call_id": tc.id, "content": web_search(q)})
    return client.chat.completions.create(model=MODEL, messages=messages).choices[0].message.content


if __name__ == "__main__":
    print(ask("2026 年有什么值得关注的 AI 大事件?"))   # 会触发搜索
    print("---")
    print(ask("帮我写一句关于春天的诗"))                # 不会搜,直接答

⑧ 进阶:搜索质量优化

搜出来的东西质量参差,几个提升点:

优化项说明
查询改写用户口语化提问(「那个新出的模型咋样」)先让模型改写成精准搜索词
结果去重/筛选多个来源内容重复时去重;明显低质的站点过滤掉
时效性优先新闻类问题,优先取近期结果(部分 API 支持按时间排序)
多轮搜索一次搜不够,允许模型基于首轮结果再搜(配合第 3 篇的循环)
控制总长度所有结果拼接后做长度上限,避免爆 Token

💡 查询改写是性价比最高的一招:用户的话往往不适合直接当搜索词。让模型先「翻译」成一个好的搜索 query,召回质量立竿见影。


⑨ 常见坑与排查

现象原因解决
该搜的时候不搜工具 description 没写清时机明确「涉及最新信息时使用」
啥都搜(浪费钱)同上,或 tool_choice 设成了 requiredauto,让模型自己判断
Token 超限报错搜索结果太长整坨塞入取摘要 + 截断 + 限制条数
网页抓取失败/超时反爬、超时加 UA、设超时、失败兜底不影响主流程
答案不标来源System Prompt 没要求明确要求「列出参考链接」
搜索结果过时未按时效排序用支持时间过滤的 API 参数

🔍 调试技巧:把模型每次生成的 query 打印出来(Demo 里已加)。如果发现它搜的关键词很差,问题多半在「查询改写」或 description 上。


⑩ FAQ

Q1:一定要用付费搜索 API 吗?
原型练手可用免费的 duckduckgo_search 库(免 Key)。但免费方案稳定性、结果质量一般,生产建议用 Tavily/Bing 等。

Q2:联网了模型还会幻觉吗?
会减少但不能根除。关键是 System Prompt 约束「只基于搜索结果、标注来源」,并在结果不足时让它说「找不到」,而非硬编。

Q3:搜索 + RAG 知识库能一起用吗?
能,而且常一起用:私有知识走 RAG(第2篇),公开实时信息走联网搜索,模型按需选择工具。

Q4:怎么控制搜索成本?
① 让模型判断该不该搜(别每次都搜);② 限制 max_results;③ 对相同查询做缓存。

Q5:抓网页会有法律/合规风险吗?
遵守目标站点的 robots.txt 和使用条款,控制频率,不抓取受限内容。商用前建议用正规搜索 API 而非自行大规模爬取。


⑪ 总结

这一篇给 Agent 装上了「实时的眼睛」:

环节关键点
搜索源选 AI 原生搜索(Tavily)最省事
触发用 Function Calling 让模型自己判断该不该搜
内容摘要够用就别抓正文;结果要截断控长
生成System Prompt 强制标注来源,可追溯
优化查询改写性价比最高

本质上,联网问答 = 工具调用(第3篇)+ RAG 增强(第2篇) 的组合应用。你会发现,越往后走,越是在把前面的积木拼成更强大的东西——这正是「搭建智能体」的乐趣所在。

下一篇我们进入 Agent 的「大脑」层面:ReAct vs Plan-Execute,两种主流的智能体编排模式对比,让 Agent 学会规划多步骤复杂任务。


🔜 下一篇预告:《ReAct vs Plan-Execute:Agent 编排模式对比》——让智能体学会拆解和规划复杂任务。
👍 如果本文帮到你,点赞 / 收藏 / 关注,追更不迷路。

更多推荐