OpenAI · GPT-6 Astra

GPT-6 Astra Function Calling in 2026: Call APIs, Databases, and Custom Tools

2026.09.11 · ~15 min read

Function Calling is not complex — the hard parts are a broken tool loop, insufficient tool isolation, and locking yourself to Chat Completions before you need hosted tools. Below: HTTP API tool schema, full tool loop, read-only database pattern, function tools vs hosted tools, a decision matrix, stacks, and a 7-step checklist.

Developer debugging a GPT-6 Astra Function Calling tool loop on a monitor

GPT-6 Astra function calling is the engine of AI agents: the model decides which tool to invoke and what arguments to pass; your runtime executes the side effect; the result feeds back into the model for continued reasoning. When this loop is correct, Astra can autonomously query APIs, read databases, and execute custom logic. When it is wrong, the loop deadlocks, tool permissions exceed what was intended, or the entire agent is locked to Chat Completions and cannot reach hosted tools. This article does not repeat what Function Calling is — that belongs in the protocol comparison — and does not repeat how to obtain an API key — that is the API tutorial. This article covers the three core tool patterns you need to go from "it runs" to "it is safely deployable."

Responses
Only path for tool calls
function_call
Output event type to check
allowlist
Database tool security baseline
Responses API is mandatory for tool calls
Function tools, hosted tools (web search, code interpreter, computer use), and async tool loops all require the Responses API. Welding tools to Chat Completions will return HTTP 400 the moment you add a hosted tool. All code in this article targets Responses, with a working text path as a prerequisite.

Step 1: Define a tool schema

A tool schema is a JSON object that tells the model what the tool is called, what it does, and what parameters it accepts. Astra supports type: function; parameters use standard JSON Schema. The example below defines a tool that calls a weather HTTP API:

Tool schema definition (HTTP API tool)
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,
        },
    }
]

A few authoring guidelines. The description should say what the tool does, not just what it is named. The model reads this field to decide whether to invoke the tool. List all required fields under required; optional fields go in properties but not in required. Setting additionalProperties: false reduces the chance of the model passing unexpected keys. Multiple tools go in the same list; the model picks one or more based on the conversation. When the model calls multiple tools in a single turn, your loop must handle parallel tool results before continuing.

Step 2: The full tool loop

The tool loop follows a fixed pattern: send request → check output for function_call items → execute → append results to input → send again, repeating until no new function_call items appear. The loop must be a while True. Assuming a single round is a common bug because the model is allowed to call tools across multiple turns before producing a final text answer.

Full tool loop (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)

Several implementation details matter. First, response.output is a list that may contain both function_call items and message items. Filter for type == "function_call" only. Second, when continuing the conversation, pass response.output + tool_results as input. Sending only tool_results loses the conversation history, and the model cannot understand what question it was answering. Third, call_id in each result must match the call_id in the corresponding tool call; the platform validates this pairing. Fourth, serialize tool return values with json.dumps to a string. Do not pass raw Python dicts as the output field.

Security: allowlist tools and isolate execution
What a tool can do is decided by your runtime, not by the model. Validate the name field in model output against a known allowlist before dispatching. Validate argument types. For HTTP tools, add a timeout and a domain allowlist. For database tools, execute only pre-defined SQL statements with parameterized values — never concatenate model output into a query string. Any tool that writes to disk or runs shell commands belongs on a remote, disposable Mac rather than a shared desktop.

Step 3: A read-only database tool

A safe database tool rests on three properties: read-only credentials (no INSERT, UPDATE, DELETE), parameterized queries (no SQL injection), and a SQL allowlist (the model cannot construct arbitrary queries). The example below uses SQLite, but the pattern applies to PostgreSQL and MySQL: store allowed query names and their fixed SQL strings in a dict; the tool executes only what is in that dict and accepts only the parameters the caller provides.

Read-only database tool (allowlist + parameterized queries)
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"],
    },
}

In production you will also need a connection pool (do not connect and close per tool call), a query timeout (either via the database driver's timeout argument or a database-level setting), a hard row-limit baked into every SQL statement (the model should not be able to retrieve 100,000 rows), and masking of sensitive columns (phone numbers and email addresses before they enter the model's context). The tool result sent back to the model should be a summary or a small slice of data, not a raw table dump. Every row returned costs input tokens on the next request.

Function tools versus hosted tools

Astra supports both tool types. The selection logic is different for each, and confusing them leads to either unnecessary development overhead or hitting the wrong execution boundary.

DimensionFunction tools (type: function)Hosted tools (OpenAI-operated)
Execution locationYour runtime (local machine or remote Mac)OpenAI infrastructure
Best forPrivate APIs, internal databases, custom business logicWeb search, file search, code interpreter, shell, computer use
Development effortYou write the schema, the execution function, and the loopName the tool in the tools list; OpenAI handles execution
BillingTokens only (input + output)Tokens plus per-call tool fees (search per call, computer use per hour)
Isolation requirementWrite-capable tools need an isolated execution environmentOpenAI handles sandbox isolation; you still govern data access
Capability boundaryArbitrary custom logic; constrained by runtime permissionsGoverned by OpenAI policy; cannot reach private networks

The two types can be mixed in the same request. You can pass function tool schemas alongside a hosted web search tool in the same tools list; the model decides which to call based on the conversation. When mixing, note that hosted tool results flow back into the conversation automatically. You do not submit function_call_output for them. Your tool loop only needs to handle the function_call items for the function tools you defined.

Decision matrix

If you need toUse thisWhy
Call an internal REST APIFunction toolPrivate networks are not reachable from OpenAI infrastructure
Query an internal database (read-only)Function tool + SQL allowlistPrivate data; you control access credentials and row-level permissions
Search the public web for live informationHosted web_search_previewOpenAI crawls and ranks; no infrastructure maintenance on your side
Let the model run Python codeHosted code_interpreterExecutes in an OpenAI sandbox; does not consume your compute
Let the model drive a desktop or browserHosted computer_use + isolated MacComputer Use needs a visible desktop; isolation prevents accidental writes
Execute arbitrary shell commands (writes disk)Function tool + remote Mac isolationWrite permissions must live on a disposable host, not a shared desktop
Mix web search with private API callsHosted web_search + function tool combinedBoth types can coexist in a single request's tools list

A — Solo developer or prototype: local machine, function tools targeting HTTP APIs (no disk writes), schema written and loop verified with call_id logging. Enable hosted tools (web_search is the most common first addition) only after the function tool loop runs reliably. Add a second tool only after the single-tool loop is stable. Do not attempt parallel tool calls until the sequential loop is correct.

B — Small team or product: function tools mounted behind internal services with token authentication; database tools using read-only credentials, a connection pool, and query timeouts; hosted tools enabled per task type (web_search for research, code_interpreter for data analysis). Tool execution logic covered by unit tests; tool return values schema-validated before feeding the model. Write-capable scenarios isolated on a remote Mac node.

C — Enterprise or high-security deployment: all function tool calls proxied through an API gateway with audit logging; database tools pointing to a read-only replica with row-level security enforced at the database layer; computer use sessions on disposable isolated Mac nodes that are destroyed at session end; hosted tool data-retention policy matched to account agreement. Account and delivery details live in the help center; monthly node cost is on the Mac mini pricing page.

Common pitfalls

  1. Running the tool loop only once: using if instead of while means the model's first tool call ends the loop. The final text answer never arrives.
  2. Submitting results without the conversation history: passing only tool_results as input instead of response.output + tool_results loses context. The model does not know what it was asked, and usually errors or answers something unrelated.
  3. Passing model output directly into SQL or shell: even when the model is well-behaved, prompt injection through user data can construct dangerous statements. An allowlist with parameterized queries eliminates the class of attack.
  4. No timeout on tool HTTP calls: a downstream service that hangs will freeze the entire agent session indefinitely. Every external call needs a timeout and the loop needs a maximum iteration count.
  5. Returning large payloads from tools: tool results enter the model context. Returning 10,000 rows or a full file dumps into the prompt and can trigger the 272K whole-request billing uplift. Aggregate and truncate at the tool layer; send a summary, not a raw dump.

Action plan: 7 steps

  1. Confirm the plain-text Responses path works first (see the API tutorial). Tools build on this; skipping it adds confusion.
  2. Write the first function tool schema. Make the description a verb phrase that says what the tool does. List required fields in required. Set additionalProperties: false.
  3. Implement the tool execution function. Add a timeout for HTTP tools. For database tools, use the allowlist and parameterized query pattern from this article.
  4. Send a Responses request with the tools parameter. Print response.output to confirm a function_call item appears.
  5. Implement the while True loop. Submit results as function_call_output items with matching call_ids, passing full history as input. Exit when no new function_call items appear.
  6. Decide whether to add hosted tools (web_search, code_interpreter). If so, include them in the same tools list. Remember that hosted tool results are injected automatically; you do not need to submit them.
  7. Move any tool that writes to disk, executes shell commands, or drives a browser to an isolated remote Mac. Destroy the workspace when the session ends.

FAQ

Do function tools require the Responses API?

Yes. Tool calls, hosted tools, and async tool loops require the Responses API. Chat Completions is not the primary path for Astra function calling; a hosted tool on a Completions request returns HTTP 400.

How many tools can I define per request?

There is no strict small limit, but more tools consume more prompt tokens. Pass only the tools relevant to the current task rather than loading the entire tool library on every request.

How do I prevent SQL injection in a database tool?

Combine parameterized queries using ? placeholders with a fixed SQL allowlist. The tool executes only pre-defined statements; the caller supplies the parameter values. Never concatenate model output directly into a SQL string.

When should I use a function tool versus a hosted tool?

Private APIs, internal databases, and custom logic use function tools. Web search, code interpretation, and computer use are hosted tools operated by OpenAI. Both types can coexist in the same request's tools list.

Can I stream the final answer after tool calls?

Yes. Tool call events are structured; after submitting results the final text answer can be streamed by setting stream=True. Verify the non-streaming loop works correctly before enabling streaming.

Conclusion

GPT-6 Astra Function Calling has three foundations: a schema with a clear description and a complete parameters block; a while loop that submits full history with each tool result; and a database tool locked to a SQL allowlist with parameterized queries, with write-capable tools on an isolated Mac. Choosing the right tool type — function tool for private systems, hosted tool for OpenAI-operated capabilities — moves an agent from prototype to production-ready. Remote Mac nodes are listed on the rental page and pricing page; account issues go to the help center.

Further reading

Tools touching real APIs? Run them on an isolated Cloud Mac

Calling a public HTTP API locally is fine. Anything that writes disk, edits a repo, or opens a browser should not share your daily laptop. Remote Mac nodes isolate a workspace per session so Astra function tools can go from working to safely deployable.

Order now · See pricing

GPT-6 Astra Function Calling

Tools touching real APIs? Run them on an isolated Cloud Mac

Cloud Mac · isolated agent runtime

Order now
Mac Order now