Last verified: July 20, 2026
An OpenAI-compatible tool-calling loop has four application-controlled steps: send tool definitions, inspect message.tool_calls, validate and execute an allowed function, then return each result as a tool message before asking the model to continue. The model proposes a call; your Python code decides whether it is valid and performs the action.
LumeAPI lets you use one OpenAI-shaped client and exact catalog model IDs across supported model families. That simplifies cross-model testing, but tool behavior must still be verified for every chosen model. The public catalog positions models such as GPT-5.4 and Claude Sonnet 4.6 for tool or agent workloads; this article does not claim universal tool support for every listed ID.
Install and configure the client
python -m pip install --upgrade openai jsonschema
export LUMEAPI_KEY='your-lumeapi-key'import json
import os
from typing import Any
from jsonschema import validate
from openai import OpenAI
client = OpenAI(
api_key=os.environ['LUMEAPI_KEY'],
base_url='https://api.lumeapi.site/v1',
timeout=30.0,
max_retries=1,
)
MODEL = os.getenv('LUMEAPI_MODEL', 'gpt-5.4')Use a server-side environment variable. A browser must not receive the key. Keep the model configurable so the same contract tests can run against another supported ID.
Define one narrow tool
This example returns mock weather data so it is safe to run without an external service.
WEATHER_PARAMETERS = {
'type': 'object',
'properties': {
'city': {'type': 'string', 'minLength': 1},
'unit': {'type': 'string', 'enum': ['celsius', 'fahrenheit']},
},
'required': ['city', 'unit'],
'additionalProperties': False,
}
TOOLS = [
{
'type': 'function',
'function': {
'name': 'get_weather',
'description': 'Return the current weather for one city.',
'parameters': WEATHER_PARAMETERS,
},
}
]
def get_weather(city: str, unit: str) -> dict[str, Any]:
return {
'city': city,
'unit': unit,
'temperature': 22 if unit == 'celsius' else 72,
'condition': 'clear',
'source': 'demo-data',
}Descriptions should explain when to use the tool, while the schema constrains arguments. Even with a schema, treat all generated arguments as untrusted input.
Make the first request
messages: list[dict[str, Any]] = [
{
'role': 'system',
'content': 'Use the weather tool when current weather is requested.',
},
{
'role': 'user',
'content': 'What is the weather in Shanghai in Celsius?',
},
]
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
tool_choice='auto',
)
assistant_message = response.choices[0].messagetool_choice='auto' allows either a tool call or a direct answer. A required-tool workflow should use the documented forced/required mode supported by the selected model and test it explicitly; compatible endpoints can differ on advanced options.
Validate and dispatch tool calls
Never call a function by dynamically importing the model-provided name. Dispatch through a fixed allowlist.
TOOL_FUNCTIONS = {'get_weather': get_weather}
TOOL_SCHEMAS = {'get_weather': WEATHER_PARAMETERS}
def execute_tool_call(tool_call: Any) -> dict[str, Any]:
name = tool_call.function.name
if name not in TOOL_FUNCTIONS:
raise ValueError(f'Tool is not allowed: {name}')
arguments = json.loads(tool_call.function.arguments)
validate(instance=arguments, schema=TOOL_SCHEMAS[name])
result = TOOL_FUNCTIONS[name](**arguments)
return {
'role': 'tool',
'tool_call_id': tool_call.id,
'content': json.dumps(result, ensure_ascii=False),
}Validation protects the Python function from missing fields, extra fields and incorrect types. Add authorization checks for user-specific resources; a syntactically valid tool argument is not automatically authorized.
Complete the tool round trip
The assistant message containing tool_calls must be included before the tool result messages.
messages.append(assistant_message.model_dump(exclude_none=True))
for tool_call in assistant_message.tool_calls or []:
messages.append(execute_tool_call(tool_call))
final_response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
)
print(final_response.choices[0].message.content)Return a result for every accepted tool call. If one call fails, send a structured, sanitized failure that the model can reason about, or stop the workflow. Do not include stack traces, secrets or internal database details in tool content.
Build a bounded reusable loop
A production agent needs an iteration cap and a clear outcome.
def run_tool_loop(user_text: str, max_steps: int = 4) -> str:
conversation: list[dict[str, Any]] = [
{'role': 'user', 'content': user_text},
]
for _ in range(max_steps):
response = client.chat.completions.create(
model=MODEL,
messages=conversation,
tools=TOOLS,
tool_choice='auto',
)
message = response.choices[0].message
conversation.append(message.model_dump(exclude_none=True))
calls = message.tool_calls or []
if not calls:
return message.content or ''
for call in calls:
try:
tool_message = execute_tool_call(call)
except Exception as exc:
tool_message = {
'role': 'tool',
'tool_call_id': call.id,
'content': json.dumps({
'ok': False,
'error': type(exc).__name__,
}),
}
conversation.append(tool_message)
raise RuntimeError('Tool loop exceeded its step limit.')
print(run_tool_loop('Check the weather in Shanghai in Celsius.'))For real side effects, do not catch every exception and let the model retry blindly. Emails, payments, orders and writes need durable state and idempotency. Read how to prevent duplicate AI agent tool calls before enabling retries.
Test compatibility across models
Use the same cases with each intended model ID:
| Case | Expected evidence |
|---|---|
| Obvious tool request | Correct tool name and parseable arguments |
| No-tool question | Direct answer without unnecessary call |
| Missing required detail | Clarification or valid default policy |
| Invalid enum pressure | Arguments still pass schema |
| Multiple requests | Correct number and association of calls |
| Tool failure | Bounded recovery, no secret leakage |
| Repeated failure | Loop stops at the configured cap |
| Side-effect retry | Idempotency prevents duplicate action |
Record task success, schema-valid argument rate, unnecessary-call rate, steps, latency and cost. A model accepting the tools field is only the beginning; reliable selection and arguments are the production requirement.
Common OpenAI-compatible tool-calling failures
The model answers instead of calling a tool
Improve the tool description and system instruction, or use a documented forced-choice option. Confirm the selected model supports the requested control.
Arguments are not valid JSON
Catch json.JSONDecodeError, return a controlled failure or retry within a strict budget, and include the case in regression tests. Never repair arbitrary JSON and execute it without validation.
The second request fails
Preserve the assistant message containing the tool-call ID and send the result with the exact matching tool_call_id. Do not convert it into a generic user message.
The tool runs twice
SDK retries, application retries and model loops can repeat work. Use an idempotency key based on the workflow and tool-call intent, not only on an in-memory flag.
One model works and another does not
OpenAI-shaped transport does not make models behaviorally identical. Keep a tested model allowlist for tool workflows, and fall back only to a model that passed the same suite.
Why use LumeAPI for tool workloads
One LumeAPI base URL and key can reduce separate SDK integrations when testing supported GPT, Claude and Gemini models. You still select the exact model transparently. The console's per-call model, token, cost and latency logs help connect an agent failure to the request that produced it, while gateway allowlists restrict keys to supported catalog IDs.
The same account and USD wallet also cover listed image and video models, but media generation is not automatically a chat tool. Wrap each modality behind an application tool only after implementing its documented request, polling and error flow.
Review the OpenAI-compatible API, AI agent API, and current model catalog before choosing a production model.
Sources and limitations
- OpenAI function calling guide for the standard tool definition and result-message lifecycle.
- Official OpenAI Python library for client and Chat Completions types.
- LumeAPI model catalog, Claude Sonnet 4.6 docs, and gateway overview for current product positioning and IDs.
All Python blocks were syntax-checked on July 20, 2026. No billable customer key was available, so the article does not claim observed cross-model tool-call accuracy or universal compatibility. Run the matrix with your own key and exact models.