# OpenAI function calling：60 行 Python 实现一个 Google 搜索工具

> 给 OpenAI 模型一个 Google 搜索工具：JSON schema、在 SerpCrypto SERP API 上的 Python 工具调用循环，以及供 Claude 使用的相同 schema。无需 KYC。

- URL: https://serpcrypto.com/zh/docs/openai/
- 最后更新: 2026-10-22
- 发布者: SerpCrypto (https://serpcrypto.com)

向模型描述一个 `google_search` 函数，运行它请求的查询，再把结果发回。该 schema 是纯 JSON Schema，因此可以直接用于 Claude 和其他支持工具调用的模型。

**快速回答 — 如何给 OpenAI 模型一个网页搜索工具？**

在 `tools` 中用 JSON Schema 声明一个 `google_search` 函数，然后循环：当模型返回 `tool_calls` 时，在 `https://serpcrypto.com/api/v1/search` 上运行每次查询，并把结果作为 `tool` 消息追加，直到它给出答案。**SerpCrypto** 每次成功查询收取 1 个积分，起价为 US$0.30/千次搜索，用加密货币支付，无需 KYC。

## 工具 schema 是什么样的？

一个 function 工具包含名称、描述和 JSON Schema 格式的参数。模型只能看到这三样东西，因此描述必须说明该工具返回什么以及何时使用它。只有 `query` 是必填的；country 和 language 是可选的，在代码中默认为 US English。

*工具定义（Chat Completions 格式）*

```json
{
  "type": "function",
  "function": {
    "name": "google_search",
    "description": "Search Google for current information. Returns the top results with title, link and snippet. Use it for facts newer than your training data.",
    "parameters": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "The search query, as a person would type it." },
        "gl": { "type": "string", "description": "Two-letter country code of the results, e.g. us, gb, de." },
        "hl": { "type": "string", "description": "Language code, e.g. en, de, pt-BR." }
      },
      "required": ["query"]
    }
  }
}
```

## 工具调用循环是怎么运作的？

用 `tools` 发送问题。如果回复中包含 `tool_calls`，就逐个执行，追加 assistant 消息，并为每次调用追加一条带有对应 `tool_call_id` 的 `tool` 消息，然后再次调用模型。当某条回复没有任何工具调用时，它就是最终答案。

下面的循环在三次查询后停止。如果模型不断查询，上限可以保护你的积分，而三次查询就能回答大多数事实性问题。

*search_agent.py*

```python
"""Tool-calling loop: the model asks for searches, this code runs them on SerpCrypto."""
import json
import os

import requests
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY
MODEL = os.environ["OPENAI_MODEL"]  # any OpenAI chat model with tool calling
API_URL = "https://serpcrypto.com/api/v1/search"

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "google_search",
            "description": "Search Google for current information. Returns the top results with title, link and snippet.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "The search query."},
                    "gl": {"type": "string", "description": "Two-letter country code, e.g. us."},
                    "hl": {"type": "string", "description": "Language code, e.g. en."},
                },
                "required": ["query"],
            },
        },
    }
]


def google_search(query, gl="us", hl="en"):
    r = requests.get(
        API_URL,
        params={"q": query, "gl": gl, "hl": hl},
        headers={"Authorization": "Bearer " + os.environ["SERPCRYPTO_API_KEY"]},
        timeout=90,
    )
    if r.status_code != 200:
        return f"Search failed with HTTP {r.status_code}."
    items = r.json().get("organic_results", [])[:5]
    return "\n\n".join(f"{x['title']}\n{x['link']}\n{x.get('snippet', '')}" for x in items) or "No results."


def ask(question, max_searches=3):
    messages = [{"role": "user", "content": question}]
    for _ in range(max_searches + 1):
        reply = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
        msg = reply.choices[0].message
        if not msg.tool_calls:
            return msg.content
        messages.append(msg)
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            messages.append({"role": "tool", "tool_call_id": call.id, "content": google_search(**args)})
    return "Stopped: search limit reached."


print(ask("Which countries changed their data protection laws in 2026? Cite sources."))
```

## 同一份 schema 也适用于 Claude 和其他模型吗？

适用，只是封装不同。Claude 的 tool use 在 `input_schema` 下接受相同的 JSON Schema，与 `name` 和 `description` 并列。大多数其他支持工具调用的 API 在参数上也接受 JSON Schema，只是外层字段名不同。搜索函数本身保持不变，因为它只调用 SerpCrypto endpoint。

*同一个工具用于 Claude（Messages API）*

```json
{
  "name": "google_search",
  "description": "Search Google for current information. Returns the top results with title, link and snippet.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "The search query." },
      "gl": { "type": "string", "description": "Two-letter country code, e.g. us." },
      "hl": { "type": "string", "description": "Language code, e.g. en." }
    },
    "required": ["query"]
  }
}
```

## 工具应该向模型返回什么？

带链接的简短文本。五条包含标题、链接和摘要的结果通常就足够模型回答和引用。返回完整 JSON 也行，但会消耗更多 token，并给模型更多噪声去读。

如果存在 AI Overview 文本，可以加上，让模型看到 Google 自己的摘要，并保留来源链接，这样答案可以引用页面而不是概述。

## 如何让搜索成本可预测？

统计工具调用：每次成功调用计 1 积分。像循环那样为每个问题限制查询次数，并用查询词记录每次调用，这样你能看到模型在问什么。在 100,000 次搜索 套餐上费率为 US$0.56/千次搜索，并且 预付的搜索积分永不过期：未使用的查询会一直留在你的账户中，直到你使用它们。

出错不扣费：1 积分 = 1 次成功的查询请求，可在任意支持的引擎上返回一页结果。失败的请求不计费。

## Function calling 常见问题

### 可以用 Responses API 代替 Chat Completions 吗？

可以。Responses API 接受带有相同 JSON Schema 的 function 工具，其中 `name`、`description` 和 `parameters` 位于每个工具的顶层。搜索函数和循环的思路都不变。

### 为什么不使用模型提供商内置的网页搜索？

内置搜索工具会自行选择来源和设置。在 SerpCrypto 上使用 function，可以按你选择的国家和语言获取 Google 搜索结果，以可记录的 JSON 返回，每次查询价格已知。

### 模型能选择国家吗？

能。`gl` 是工具的一个 parameter，所以模型可以根据问题来设置它，例如针对关于英国的问题设为 `gb`。对于没有提到国家的问题，在代码中保留默认值。

### 获取搜索 API 密钥需要信用卡吗？

不需要。SerpCrypto 积分用 BTC, USDT, USDC, ETH, SOL, LTC 支付，无需身份证、无需自拍、无需证件。使用账号登录；邮箱为选填。

---

SerpCrypto: SerpCrypto 是面向开发者和 AI Agent 的 SERP API，可将 Google 和 Bing 搜索结果以 JSON 返回，用加密货币支付，无需上传证件。预付费搜索积分自 US$0.30/千次搜索 起永不过期。无需 KYC——无需证件、无需自拍、无需材料。加密货币支付。
