For /v1/responses, see Using the Responses API. The other examples on this page use Chat Completions.
What is tool calling?
Tool calling enables your system to seamlessly interact with external services such as APIs, databases, or custom functions, transforming static models into dynamic, real-world tools. Developers can define custom tools within the tools array, specifying their purpose, inputs, and outputs. The model then dynamically generates tool call signatures in JSON format, unlocking a wide range of capabilities, including:
- API calls: LLMs can call APIs to retrieve real-time data, such as weather updates, stock prices, news, etc.
- Database queries: The model can interact with databases to pull specific data.
- Automation tasks: LLMs can trigger workflows or automation tools (like Zapier or other RPA platforms).
- Code execution: The model can generate code and trigger it using a tool call for immediate execution.
Examples
Example 1: Request for weather data
Request
from openai import OpenAI # openai==1.52.2
import json
client = OpenAI(
api_key="UPSTAGE_API_KEY_INJECT",
base_url="https://api.upstage.ai/v1"
)
# Step 1: Setup and define the function
# This is an example dummy function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
"""Get the current weather in a given location"""
if unit is None:
unit = "fahrenheit"
if "seoul" in location.lower():
return json.dumps({"location": "Seoul", "temperature": "10", "unit": unit})
elif "san francisco" in location.lower():
return json.dumps(
{"location": "San Francisco", "temperature": "72", "unit": unit}
)
elif "paris" in location.lower():
return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
else:
return json.dumps({"location": location, "temperature": "unknown"})
# Step 2: Send the query and available tools to the model
def run_conversation():
messages = [
{
"role": "user",
"content": "What's the weather like in Seoul?",
}
]
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
"additionalProperties": False,
},
},
}
]
# Step 3: Check if the model has requested a tool call
# The model identifies that the query requires external data (e.g., real-time weather) and decides to call a relevant tool, such as a weather API.
response = client.chat.completions.create(
model="solar-pro4",
messages=messages,
tools=tools,
tool_choice="auto",
parallel_tool_calls=False
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
if not tool_calls:
return response
# Step 4: Execute the tool call
# The JSON response from the model may not always be valid, so handle errors appropriately
if tool_calls:
available_functions = {
"get_current_weather": get_current_weather,
} # You can define multiple functions here as needed
messages.append(response_message) # Add the assistant's reply to the conversation history
# Step 5: Process each tool call and provide the results to the model
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions.get(function_name)
if function_to_call is None:
raise ValueError(f"Tool not allowed: {function_name}")
try:
function_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError as exc:
raise ValueError("Tool arguments must be valid JSON") from exc
if not isinstance(function_args, dict):
raise ValueError("Tool arguments must be a JSON object")
unexpected = set(function_args) - {"location", "unit"}
if unexpected:
raise ValueError(f"Unexpected tool arguments: {sorted(unexpected)}")
location = function_args.get("location")
unit = function_args.get("unit", "fahrenheit")
if not isinstance(location, str) or not location.strip():
raise ValueError("location must be a non-empty string")
if unit not in {"celsius", "fahrenheit"}:
raise ValueError("unit must be celsius or fahrenheit")
function_response = function_to_call(
location=location,
unit=unit,
) # Call the function with the provided arguments
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
) # Append the function response to the conversation history
# Step 6: Generate a new response from the model using the updated conversation history
second_response = client.chat.completions.create(
model="solar-pro4",
messages=messages,
)
return second_response # Return the final response from the model
response = run_conversation()
print(response.choices[0].message.content)Response
The current weather in Seoul is 10 degrees Celsius.Example 2: Retrieving information from structured data
Request
import json
import os
from typing import List
from openai import OpenAI # openai==1.52.2
import pandas as pd
client = OpenAI(
api_key="UPSTAGE_API_KEY_INJECT",
base_url="https://api.upstage.ai/v1"
)
# Step 1. Setup and create structured data
# This is a hard coded dummy data to demonstrate the tool call capability
# In production, this could be your internal database
# Create data(MAU and revenue)
mau_data = [
{"Month": 1, "Service": "LLM", "MAU": 100},
{"Month": 1, "Service": "Embedding", "MAU": 50},
{"Month": 2, "Service": "LLM", "MAU": 150},
{"Month": 2, "Service": "Embedding", "MAU": 70},
{"Month": 3, "Service": "LLM", "MAU": 300},
{"Month": 3, "Service": "Embedding", "MAU": 80},
{"Month": 4, "Service": "LLM", "MAU": 350},
{"Month": 4, "Service": "Embedding", "MAU": 150},
]
mau = pd.DataFrame(mau_data)
revenue_data = [
{"Month": 1, "Service": "LLM", "Revenue": 1000},
{"Month": 1, "Service": "Embedding", "Revenue": 500},
{"Month": 2, "Service": "LLM", "Revenue": 1500},
{"Month": 2, "Service": "Embedding", "Revenue": 700},
{"Month": 3, "Service": "LLM", "Revenue": 3000},
{"Month": 3, "Service": "Embedding", "Revenue": 800},
{"Month": 4, "Service": "LLM", "Revenue": 3500},
{"Month": 4, "Service": "Embedding", "Revenue": 1500},
]
revenue = pd.DataFrame(revenue_data)
# Step 2. Define the function
# This function query_data is designed to filter data from either the MAU or Revenue tables based on the months and service specified.
# It returns a filtered DataFrame based on these criteria.
# This function will be later utilized by the LLM as part of the tool call mechanism to dynamically answer queries.
# Example question
question = "Use the query_data tool to find Revenue for service LLM in months 1, 2, and 3."
def query_data(table_name: str, month: List[int], service: str) -> pd.DataFrame:
"""
Query data from the given table based on month and service.
"""
if table_name == "MAU":
data = mau
elif table_name == "Revenue":
data = revenue
else:
raise ValueError(f"Table name {table_name} not found.")
return data[(data["Month"].isin(month)) & (data["Service"] == service)]
# Step 3. Define tools for tool calling
# This block defines a tools array that registers the query_data function as an available option for the LLM.
# It provides the model with information about the available functions and its expected parameters, enabling the model to dynamically call the functions during conversations.
tools = [
{
"type": "function",
"function": {
"name": "query_data",
"description": "Query MAU or Revenue data for specified months and service.",
"parameters": {
"type": "object",
"properties": {
"table_name": {
"type": "string",
"description": "The table name to query.",
"enum": ["MAU", "Revenue"],
},
"month": {
"type": "array",
"items": {"type": "integer"},
"description": "The list of months to query.",
},
"service": {
"type": "string",
"description": "Which service to query.",
"enum": ["LLM", "Embedding"],
},
},
"required": ["table_name", "month", "service"],
"additionalProperties": False,
},
},
}
]
# Step 4. User message and initial LLM response
# The LLM receives a user message inquiring about the revenue in the 1st quarter for LLM. Along with this, the tools list (including query_data) provided, enabling the model to decide whether a tool call is needed.
# If the model determines that the query_data function should be used, it will generate a request, which will be handled in the next step.
messages = [
{
"role": "user",
"content": question,
}
]
response = client.chat.completions.create(
model="solar-pro4",
messages=messages,
tools=tools,
tool_choice={"type": "function", "function": {"name": "query_data"}},
parallel_tool_calls=True, # Enable parallel tool calls
)
# Step 5. Function execution and tool call handling
# The LLM’s response includes a tool call (tool_call), indicating the model has chosen to call the query_data function.
# The tool call contains the function name and arguments, which are extracted and parsed.
# The query_data function is executed using the extracted arguments.
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
if not tool_calls:
raise RuntimeError("The model did not return the required tool call")
tool_call = tool_calls[0]
# Define the available functions
available_functions = {
"query_data": query_data,
}
messages.append(response_message)
# Retrieve the function name and corresponding callable function
function_name = tool_call.function.name
function_to_call = available_functions.get(function_name)
if function_to_call is None:
raise ValueError(f"Tool not allowed: {function_name}")
# Parse the function arguments from the tool call
try:
function_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError as exc:
raise ValueError("Tool arguments must be valid JSON") from exc
if not isinstance(function_args, dict):
raise ValueError("Tool arguments must be a JSON object")
unexpected = set(function_args) - {"table_name", "month", "service"}
if unexpected:
raise ValueError(f"Unexpected tool arguments: {sorted(unexpected)}")
table_name = function_args.get("table_name")
month = function_args.get("month")
service = function_args.get("service")
if table_name not in {"MAU", "Revenue"}:
raise ValueError("table_name must be MAU or Revenue")
if (
not isinstance(month, list)
or not month
or not all(type(value) is int for value in month)
):
raise ValueError("month must be a non-empty array of integers")
if service not in {"LLM", "Embedding"}:
raise ValueError("service must be LLM or Embedding")
# Execute the function with the parsed arguments
function_response = function_to_call(
table_name=table_name,
month=month,
service=service,
)
function_response = json.dumps(function_response.to_dict(orient="records"))
# Step 6. Extend the conversation with function output
# The function’s output is appended to the conversation as a message.
# The model is then prompted to continue the conversation, now informed by the function's results.
# Append the function's response to the conversation history
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
)
# Continue the conversation by sending the updated message history to the LLM
second_response = client.chat.completions.create(
model="solar-pro4",
messages=messages,
)
print(second_response.choices[0].message.content)Response
The revenue for LLM in the 1st quarter was $1000 in January, $1500 in February, and $3000 in March.Parallel tool calls
Parallel tool calling allows the model to generate multiple tool calls in a single response, enabling concurrent execution of independent tool calls. This is especially useful when you need to retrieve data from multiple sources simultaneously.
How to enable parallel tool calls
Set parallel_tool_calls=True in your API request:
response = client.chat.completions.create(
model="solar-pro4",
messages=messages,
tools=tools,
tool_choice="auto",
parallel_tool_calls=True
)When the model determines that multiple independent tool calls are needed (e.g., fetching weather for three different cities), it will return multiple tool_calls in a single response. You should process all tool calls and append their results to the conversation before making the next API call.
Using the Responses API
The Responses API is available on Solar Pro 4 and Solar Mini 4.
Declare a function, execute the returned call in your code, and send its result back as function_call_output. The Python example shows the full round trip; cURL shows the initial request.
import json
from openai import OpenAI
client = OpenAI(
api_key="UPSTAGE_API_KEY",
base_url="https://api.upstage.ai/v1",
)
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
]
first = client.responses.create(
model="solar-pro4",
input="What's the weather in Seoul?",
tools=tools,
)
call = next(item for item in first.output if item.type == "function_call")
result = {"temp_c": 23, "sky": "clear"} # run the tool yourself
# Send the history back with the tool result appended.
history = [{"role": "user", "content": "What's the weather in Seoul?"}]
history += [item.model_dump(exclude_none=True) for item in first.output]
history.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
}
)
second = client.responses.create(model="solar-pro4", input=history, tools=tools)
print(second.output_text)