OpenAI · GPT-6 Astra

GPT-6 Astra Function Calling 实战 2026:让 AI 调用 API、数据库和自定义工具

2026.09.11 · 约 15 分钟阅读

Function Calling 不难,难在工具循环写错、工具权限没有隔离,以及把 Chat Completions 当默认挡死了托管工具。 下文给出 HTTP API 工具 schema、完整工具循环、数据库只读模式、函数工具 vs 托管工具对比、决策矩阵和 7 步验收。

开发者在屏幕前调试 GPT-6 Astra Function Calling 工具调用循环

GPT-6 Astra 的工具调用(Function Calling)已经是 AI Agent 的基础能力:让模型决定调用哪个工具、传什么参数,运行时负责执行副作用,再把结果送回给模型继续推理。这条链路写对了,Astra 能自主查询 API、读数据库、执行自定义逻辑;写错了,要么工具循环死锁,要么工具权限失控。本文不重复「Function Calling 是什么」——那篇见 协议对照;也不重复「怎么拿 Key」——那篇见 API 调用教程。本文只把从「能跑」到「能安全上线」的三个核心工具类型拆开讲清楚。

Responses
工具调用唯一主路径
function_call
output 事件类型
allowlist
数据库工具安全基线
必须用 Responses API
工具调用、托管工具(网页搜索、代码解释器、Computer Use 等)和异步 tool 全部要走 Responses API。把工具焊在 Chat Completions 上,接托管工具时会直接 400。文本探测已经成功的前提下,本文所有代码走 Responses。

第一步:定义工具 schema

工具 schema 是一段 JSON 对象,告诉模型「这个工具叫什么、做什么、接受什么参数」。Astra 支持 type: function,参数用标准 JSON Schema 描述。以一个调用天气 HTTP API 的工具为例:

工具 schema 定义(HTTP API 工具)
import json
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a city via HTTP API.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, e.g. Tokyo, London, Shanghai",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit. Default celsius.",
                },
            },
            "required": ["city"],
            "additionalProperties": False,
        },
    }
]

几条写法建议:description 要说「做什么」而不是「叫什么名字」,模型靠这里决定要不要调用这个工具。required 列出必填字段,可选字段加在 properties 里但不进 required。additionalProperties: false 可以降低模型传入意外字段的概率。多个工具放在同一个 list 里,模型会根据对话决定调用哪个(或同时调用多个,需要你的循环处理并行 tool call)。

第二步:完整工具循环

工具循环的核心是:发请求 → 检查 output 里是否有 function_call → 执行 → 把结果塞回 input → 再次请求,直到没有新的 function_call 为止。循环必须是 while-true,不能假设模型只会调用一次工具。

完整工具循环(Responses API)
import json
import requests
from openai import OpenAI

client = OpenAI()

def call_weather_api(city: str, unit: str = "celsius") -> dict:
    """Replace with your real HTTP endpoint."""
    url = "https://api.example.com/weather"
    r = requests.get(url, params={"city": city, "unit": unit}, timeout=5)
    r.raise_for_status()
    return r.json()  # e.g. {"city": "Tokyo", "temp": 22, "condition": "sunny"}

# Initial request — model may call tools
response = client.responses.create(
    model="gpt-6-astra",
    input="What is the weather in Tokyo right now?",
    tools=tools,
    reasoning={"effort": "low"},
)

# Tool loop: keep going until model stops calling tools
while True:
    tool_calls = [o for o in response.output if o.type == "function_call"]
    if not tool_calls:
        break  # model is done — final answer is in response.output_text

    tool_results = []
    for call in tool_calls:
        args = json.loads(call.arguments)
        if call.name == "get_weather":
            result = call_weather_api(**args)
        else:
            result = {"error": f"Unknown tool: {call.name}"}
        tool_results.append({
            "type": "function_call_output",
            "call_id": call.call_id,
            "output": json.dumps(result, ensure_ascii=False),
        })

    # Append history + results and continue
    response = client.responses.create(
        model="gpt-6-astra",
        input=response.output + tool_results,
        tools=tools,
        reasoning={"effort": "low"},
    )

print(response.output_text)

注意几个细节:第一,response.output 是一个列表,其中可能同时存在 function_callmessage 类型的事件;只过滤 type == "function_call" 的项。第二,提交结果时,input 要传 response.output + tool_results,带上历史,模型才知道自己问了什么、得到了什么。第三,call_id 必须和工具结果的 call_id 对应,平台用这个字段做配对校验。第四,工具返回值用 json.dumps 序列化为字符串,不要直接传 dict 对象。

安全:allowlist 工具,隔离执行
工具能做什么,运行时说了算,不是模型说了算。在模型输出的 name 字段上做白名单过滤;参数用类型校验;对 HTTP 工具加超时和域名白名单;对数据库工具只执行预设 SQL,不接受用户或模型拼的 SQL 字符串。会写磁盘或跑命令的工具,放到远程 Mac 上隔离执行。

第三步:数据库只读工具

数据库工具的安全底线是三件事:只读(不给 INSERT / UPDATE / DELETE 权限)、参数化查询(防注入)、SQL 白名单(防模型构造恶意查询)。下面示例用 SQLite,但模式对 PostgreSQL / MySQL 同样适用:把允许的查询名和 SQL 存在 dict 里,工具只执行 dict 里有的语句,参数由调用方传入。

数据库只读工具(allowlist + 参数化查询)
import sqlite3
from typing import Any

# Only these parameterized queries can be executed — no raw user SQL
ALLOWED_QUERIES: dict[str, str] = {
    "get_order":   "SELECT id, status, total, created_at FROM orders WHERE id = ?",
    "list_orders": "SELECT id, status, total FROM orders WHERE user_id = ? LIMIT 20",
}

def query_db(query_name: str, params: list[Any]) -> list[dict]:
    """Execute an allowlisted, parameterized read-only query.
    Never accepts raw SQL strings from the model.
    """
    sql = ALLOWED_QUERIES.get(query_name)
    if sql is None:
        raise ValueError(f"Query '{query_name}' not in allowlist")
    con = sqlite3.connect("orders.db", check_same_thread=False)
    con.row_factory = sqlite3.Row
    try:
        rows = con.execute(sql, params).fetchall()
        return [dict(r) for r in rows]
    finally:
        con.close()

# Schema exposed to the model
db_tool = {
    "type": "function",
    "name": "query_db",
    "description": "Query the orders DB. Only read-only allowlisted queries.",
    "parameters": {
        "type": "object",
        "properties": {
            "query_name": {
                "type": "string",
                "enum": list(ALLOWED_QUERIES.keys()),
                "description": "Name of the allowlisted query",
            },
            "params": {
                "type": "array",
                "items": {"type": ["string", "number"]},
                "description": "Positional parameters for the query",
            },
        },
        "required": ["query_name", "params"],
    },
}

实际生产中还需要:连接池(不要每次 tool call 都 connect/close)、查询超时(timeout 参数或数据库层配置)、结果行数硬限制(LIMIT N 写进 SQL,不让模型传 999999)、敏感字段脱敏(手机号、邮箱在返回前 mask)。工具返回给模型的应该是摘要,不是整张表的 raw dump。

函数工具 vs 托管工具

Astra 同时支持两类工具,选择逻辑不同,不能混淆。

维度函数工具(type: function托管工具(OpenAI 运营)
执行位置你的运行时(本机 / 远程 Mac)OpenAI 基础设施
适用场景私有 API、内部数据库、自定义业务逻辑网页搜索、文件搜索、代码解释器、shell、Computer Use
开发成本需要自己写 schema + 执行逻辑 + 工具循环直接在 tools 里写工具名,OpenAI 负责执行
计费仅 token(输入 + 输出)token + 工具调用附加费(如搜索按次、Computer Use 按时)
隔离要求写操作需要隔离执行环境OpenAI 负责隔离,但仍需考虑数据安全
能力边界任意自定义;受运行时权限约束受 OpenAI 策略约束;不支持调用私有内网

两类工具可以混用:同一次请求既可以传函数工具又可以开启托管工具(如 {"type": "web_search_preview"})。模型会根据 description 和对话上下文决定调用哪个。混用时注意:托管工具的返回会自动进入对话历史,不需要你手动提交结果;函数工具的结果必须手动通过 function_call_output 提交。

决策矩阵

如果你需要用这个理由
调用公司内网 REST API函数工具内网不对 OpenAI 基础设施开放
查询内部数据库(只读)函数工具 + allowlist SQL私有数据,需自己控制访问权限
搜索公开网页最新信息托管 web_search_previewOpenAI 负责爬取和排名,无需维护
让模型运行 Python 代码托管 code_interpreter在 OpenAI 沙箱里执行,不消耗本机资源
让模型操作桌面 / 浏览器托管 computer_use + 隔离 MacComputer Use 需要可见桌面;隔离执行避免误操作
执行任意 shell 命令(写磁盘)函数工具 + 远程 Mac 隔离写权限必须在可销毁环境里,不上日常桌面
既要搜索又要调私有 API混用:托管 web_search + 函数工具同一次请求可以组合两类工具

A — 个人开发者 / 原型: 本机 + 函数工具(HTTP API,不写磁盘)。工具 schema 写好,循环跑通,日志打出 call_id 和工具返回。托管工具按需开启(web_search 最常用)。先把单工具循环跑稳,再接多工具或并行 tool call。

B — 小团队 / 产品: 函数工具挂内部服务,接口用 token 鉴权;数据库工具只读,加连接池和查询超时;托管工具(web_search、code_interpreter)按任务需要开启。工具执行逻辑单测覆盖,工具返回值做 schema 校验再送模型。会写磁盘的场景放远程 Mac。

C — 企业 / 高安全: 所有函数工具经 API 网关代理,工具名和参数做审计日志;数据库只读 replica,工具层再加 Row-Level Security;Computer Use 放到可销毁的隔离 Mac 节点,会话结束自动销毁。托管工具的数据安全合规(零数据保留)按账户约定。账户和交付边界见 帮助中心,节点月费见 Mac mini 定价

常见陷阱

  1. 工具循环只跑一轮: 用 if 而不是 while,模型第一次调工具后就退出,拿不到最终文本回答。
  2. 提交结果时不带历史: input 只传 tool_results 而不传 response.output + tool_results,模型不知道上下文,大概率报错或答非所问。
  3. 把模型输出直接拼进 SQL / shell: 即使模型通常不会恶意构造,数据质量或 prompt injection 攻击仍然可以触发危险语句。始终用 allowlist + 参数化查询。
  4. 工具超时没设: HTTP 工具没有超时,下游服务卡住后整个 Agent 会话挂死。所有外部调用加 timeout,工具循环加最大轮数上限。
  5. 工具返回整张表 / 大文件: 工具结果会进模型上下文,返回 10,000 行 CSV 会直接触发 272K 升档费率。工具层做聚合和截断,只返回模型需要的摘要。

落地步骤:7 步

  1. 确认文本通路已经成功(见 API 调用教程);工具基于文本通路,不要跳步。
  2. 写第一个函数工具 schema,description 清晰,parameters 有 required,additionalProperties false。
  3. 实现工具执行函数,HTTP 工具加超时,DB 工具用 allowlist + 参数化查询。
  4. 用 Responses API 发带 tools 参数的请求,打印 response.output,确认出现 function_call 事件。
  5. 实现 while 循环,正确提交 function_call_output,call_id 一一对应,循环直到无新 tool call。
  6. 按工具类型决定是否引入托管工具(web_search、code_interpreter),混用时注意托管工具结果无需手动提交。
  7. 写磁盘、跑命令、开浏览器的工具移到远程 Mac 隔离执行,会话结束销毁节点。

FAQ

Function Calling 必须用 Responses API 吗?

是的。工具调用、托管工具和异步 tool 必须走 Responses API。Chat Completions 在 Astra 上不是函数调用的主路径,一接托管工具就会 400。

一次调用可以定义多少个工具?

没有硬性小数字上限,但工具越多,提示 token 越贵。建议按任务只传入相关工具,避免把整个工具库塞进每次请求。

数据库工具怎么防止 SQL 注入?

使用参数化查询(? 占位符),配合预设 SQL 白名单:工具只能执行白名单里的语句,参数由调用方传入。绝不把模型输出直接拼接成 SQL。

函数工具和托管工具怎么选?

需要调外部私有 API 或内部数据库,用函数工具;需要网页搜索、代码执行、Computer Use 这类 OpenAI 运营的能力,走托管工具。两类可以在同一次请求里混用。

工具调用结果可以流式输出吗?

工具调用本身是结构化事件,不需要流式;工具结果提交后的最终回答可以加 stream=True。建议先验证非流式工具循环成功,再叠加流式。

总结

GPT-6 Astra Function Calling 的三个核心:工具 schema 写清楚 description 和 parameters;工具循环用 while,提交结果时带上历史;数据库工具加 allowlist 和参数化查询,写权限的工具放到远程 Mac 隔离。选对了工具类型(函数工具 vs 托管工具),Agent 能力就能从「跑通」快速推进到「安全上线」。远程 Mac 节点从 租用页定价页 查看,账户问题走 帮助中心

延伸阅读

工具要调真实 API?放到隔离的 Cloud Mac 上再跑

本机调 HTTP API 可以;一旦工具要写磁盘、改仓库或开浏览器,执行环境不该和日常桌面共享。远程 Mac 按会话隔离工作区,适合把 Astra Function Calling 从「能跑」推进到「能安全上线」。

立即订购 · 查看定价

GPT-6 Astra Function Calling

工具要调真实 API?放到隔离的 Cloud Mac 上再跑

Cloud Mac · isolated agent runtime

立即订购
Mac 立即订购