OpenAI models can call functions you define. Define one function that calls ODEN, and your model gains real-time web search that returns a synthesized answer plus citations — no scraping, no extra infrastructure.
Define the tool#
tools = [{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the live web for current information and return a synthesized answer with citations.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
},
}]
Run the loop#
Call the model; when it asks for the tool, call ODEN and feed the result back:
import os, json, requests
from openai import OpenAI
client = OpenAI()
def oden_search(query: str) -> str:
r = requests.post(
"https://api.oden-api.com/search",
headers={"Authorization": f"Bearer {os.environ['ODEN_KEY']}"},
json={"query": query, "include_snippets": True},
timeout=30,
)
r.raise_for_status()
d = r.json()["results"]
return json.dumps({
"answer": d.get("answer"),
"citations": [{"title": c["title"], "url": c["url"]} for c in d["citations"]],
})
messages = [{"role": "user", "content": "What are the latest EU AI Act deadlines?"}]
first = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = oden_search(args["query"])
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
final = client.chat.completions.create(model="gpt-4o", messages=messages)
print(final.choices[0].message.content)
Handling parallel tool calls#
Frontier models such as GPT-4o frequently generate multiple tool calls concurrently when a prompt requires comparing distinct entities or dates:
if msg.tool_calls:
messages.append(msg)
# Process parallel tool executions concurrently
for call in msg.tool_calls:
if call.function.name == "web_search":
args = json.loads(call.function.arguments)
tool_output = oden_search(args["query"])
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": tool_output
})
# Send combined tool results back for final generation
final = client.chat.completions.create(model="gpt-4o", messages=messages)
Strict mode schema definition#
Ensure deterministic JSON schema compliance by defining explicit schemas with strict: true and additionalProperties: false:
strict_web_tool = {
"type": "function",
"function": {
"name": "web_search",
"description": "Execute live web search for temporal facts, news, and technical documentation.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The concise factual search query"},
"recency_days": {"type": "integer", "description": "Optional maximum age in days"}
},
"required": ["query", "recency_days"],
"additionalProperties": False
}
}
}
Notes#
- Returning the citations as JSON lets the model quote titles and URLs back to the user. Ask it in your system prompt to cite inline.
- One tool is usually enough; the model decides when a query needs the live web.
- For agent frameworks that manage the loop for you, the same function definition drops straight in.
FAQ#
Which OpenAI models support function calling?#
The current GPT-4o and GPT-4.1 families and their mini variants all support tool/function calling. The snippet uses the Chat Completions API; the Responses API works the same way with one tool.
Can the model call ODEN multiple times?#
Yes. If the model emits several tool_calls, call ODEN for each and append each result before the final completion.
Does this work with Azure OpenAI?#
Yes — the function-calling contract is the same. Only the client setup differs.