codecamp

Ollama 网络搜索(Web search)

模型的参数里没有今天的新闻,也不知道上周发布的版本号。这类"时效性"问题,最省事的补法不是微调、不是自己维护爬虫,而是给它一个搜索接口,需要时自己去查。

Ollama 官方直接提供了两个现成接口:web_search 负责搜,web_fetch 负责读具体网页。两者都是云端 REST API,配好就能用,本地模型也能调用它们。

版本基线 v0.40.12,官方 docs.ollama.com/capabilities/web-search。

一、先拿到 API Key

这两个接口走 Ollama 云端,需要认证:

  1. 注册一个免费 Ollama 账号
  2. 到 ollama.com/settings/keys 创建 API Key
  3. 写进环境变量 OLLAMA_API_KEY

## 所有调用都会读取这个变量
export OLLAMA_API_KEY="your_api_key"

注意这是独立于本地模型运行的能力——你的模型跑在 localhost:11434,搜索请求发往 ollama.com。所以离线环境下这部分会失败,代码里要有降级路径。

二、web_search:一次查询,返回若干结果

POST https://ollama.com/api/web_search
参数 类型 必填 说明
query string 是 搜索查询字符串
max_results integer 否 返回结果数上限,默认 5,最大 10

curl https://ollama.com/api/web_search \
  --header "Authorization: Bearer $OLLAMA_API_KEY" \
  -d '{
    "query": "what is ollama?"
  }'

返回体是一个对象,results 数组里每条包含三项:

{
  "results": [
    {
      "title": "Ollama",
      "url": "https://ollama.com/",
      "content": "Cloud models are now available..."
    },
    {
      "title": "What is Ollama? Introduction to the AI model management tool",
      "url": "https://www.hostinger.com/tutorials/what-is-ollama",
      "content": "Ariffud M. 6min Read..."
    }
  ]
}

content 是截取的相关片段,不是整页正文。这对控制上下文很友好——但如果你需要完整内容,得再调 web_fetch。

结果数不要贪多。默认 5 条通常够用,调到 10 条往往只是把噪声一起塞进上下文。

三、web_fetch:读某个具体的网页

POST https://ollama.com/api/web_fetch

只有 url 一个必填参数:

curl --request POST \
  --url https://ollama.com/api/web_fetch \
  --header "Authorization: Bearer $OLLAMA_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
      "url": "ollama.com"
  }'

返回三样东西:

{
  "title": "Ollama",
  "content": "[Cloud models](https://ollama.com/blog/cloud-models) are now available in Ollama...",
  "links": [
    "http://ollama.com/",
    "http://ollama.com/models",
    "https://github.com/ollama/ollama"
  ]
}

content 是页面的主体内容,已经做过提取(去掉了导航、页脚这些噪声),但保留了 Markdown 标记——你会看到 [文字](链接) 这样的形式。links 是页面内提取出的链接列表,可以拿来做二级抓取。

url 写 ollama.com 这种不带协议的形式也能工作,服务端会补全。

四、SDK 里就是一行

Python:

## pip install ollama
import ollama

## 搜索
response = ollama.web_search("What is Ollama?")
print(response)

from ollama import web_fetch

## 抓取单页
result = web_fetch('https://ollama.com')
print(result)
## WebFetchResponse(title='Ollama', content='...', links=['https://ollama.com/', ...])

JavaScript:

import { Ollama } from "ollama";

const client = new Ollama();

// 搜索
const results = await client.webSearch("what is ollama?");
console.log(JSON.stringify(results, null, 2));

// 抓取单页
const fetchResult = await client.webFetch("https://ollama.com");
console.log(JSON.stringify(fetchResult, null, 2));

五、核心玩法:把搜索当工具挂给模型

上面这些调用都需要你自己发起。真正有价值的是把它们变成模型可自主调用的工具——模型判断需要联网,就自己搜;搜到 URL 后觉得摘要不够,再自己去抓全文。

Python SDK 的 web_search 和 web_fetch 本身就是函数,直接放进 tools 列表即可:

## 用一个小模型就够,4B 参数足够做工具选择
ollama pull qwen3:4b

from ollama import chat, web_fetch, web_search

## 两个函数直接作为工具
available_tools = {'web_search': web_search, 'web_fetch': web_fetch}

messages = [{'role': 'user', 'content': "what is ollama's new engine"}]

while True:
    response = chat(
        model='qwen3:4b',
        messages=messages,
        tools=[web_search, web_fetch],
        think=True,
    )

    if response.message.thinking:
        print('Thinking: ', response.message.thinking)
    if response.message.content:
        print('Content: ', response.message.content)

    messages.append(response.message)

    if response.message.tool_calls:
        for tool_call in response.message.tool_calls:
            function_to_call = available_tools.get(tool_call.function.name)
            if function_to_call:
                args = tool_call.function.arguments
                result = function_to_call(**args)
                # 关键:结果必须截断,否则一次搜索就能把上下文撑爆
                messages.append({
                    'role': 'tool',
                    'content': str(result)[:2000 * 4],
                    'tool_name': tool_call.function.name,
                })
            else:
                messages.append({
                    'role': 'tool',
                    'content': f'Tool {tool_call.function.name} not found',
                    'tool_name': tool_call.function.name,
                })
    else:
        # 没有工具调用了,模型已经给出最终答案
        break

跑起来会看到典型的搜索 Agent 轨迹:先思考"用户问的可能是引擎更新",然后调 web_search 搜 Ollama new engine,拿到结果后继续思考,最后输出带要点的答案。

*`str(result)[:2000 4]` 这个截断别去掉。** 一次搜索返回 5 条结果,每条几百字,轻松上千 token;再叠几轮,小模型的上下文立刻溢出。按需截断是搜索 Agent 能跑通的前提。

六、上下文长度必须调大

官方明确指出:搜索结果可能返回数千 token,建议把模型上下文长度提到至少约 32000 token,搜索 Agent 在完整上下文下效果最好。

Ollama 默认上下文长度通常比这小得多。设置方式有两种:

  • 创建模型时通过 Modelfile 设 PARAMETER num_ctx 32768(详见 5.1 Ollama Modelfile 参考)
  • 请求时在 options 里传 num_ctx

如果不调,会遇到一个很迷惑的现象:搜索结果明明拿到了,模型的回答却像没看见。原因不是你代码错了,而是前面的消息被挤出了上下文窗口。

顺带一提,Ollama 云端模型默认跑在完整上下文长度上,这也是云端在长上下文场景更省心的原因。

七、接进现成工具:MCP

不想自己写 Agent 循环的话,官方提供了一个 Python MCP server,把它挂到支持 MCP 的客户端里,搜索能力立刻可用。

Cline:Manage MCP Servers → Configure MCP Servers,加入:

{
  "mcpServers": {
    "web_search_and_fetch": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "path/to/web-search-mcp.py"],
      "env": { "OLLAMA_API_KEY": "your_api_key_here" }
    }
  }
}

Codex:写进 ~/.codex/config.toml:

[mcp_servers.web_search]
command = "uv"
args = ["run", "path/to/web-search-mcp.py"]
env = { "OLLAMA_API_KEY" = "your_api_key_here" }

Goose 则通过它的 MCP 功能界面配置,字段同上。

这个 server 脚本在 ollama-python 仓库的 examples/web-search-mcp.py,克隆下来指向本地路径就行。

八、和 RAG 怎么选

两者都能"补充模型不知道的知识",分工其实很清楚:

网络搜索 自建 RAG(嵌入检索)
数据来源 公开互联网 你自己的文档库
时效性 实时 取决于你的库何时更新
隐私 内容出网,不适合内部资料 完全本地
依赖 需要 API Key + 联网 只需本地嵌入模型
适合 新闻、版本号、通用知识 内部规范、产品文档、客户资料

实际项目里经常同时用:内部知识查 RAG,外部实时信息查搜索,让模型根据问题自己选工具。这正好是上一节 3.8 Ollama 工具调用(Tool calling) 讲的多工具调度;嵌入检索的建法见 3.6 Ollama 嵌入向量(Embeddings)。

至此模型能力一章走完。想回到命令本身,看 4.1 Ollama CLI 参考;想调参数与模板,看 5.1 Ollama Modelfile 参考。

Ollama 工具调用(Tool calling)
Ollama CLI 参考
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Ollama Cloud

Ollama CLI 命令行参考

Ollama Modelfile 参考

Ollama 上下文长度

Ollama 导入自定义模型

Ollama 常见问题

Ollama 硬件支持

Ollama 故障排查

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }