DEVUP Docs
Back to Dashboard
OpenAI-compatible

Tool Calling

DEVUP AI supports OpenAI-compatible tool calling for models that expose this capability. By defining external tools (like APIs or database queries), you can allow the model to intelligently choose when and how to call them. Tool-call quality, feature support, and parameter compliance may vary by model.

Tool-calling flow

1

User request

Send messages and available tool definitions.

2

Model requests tool

Receive the requested tool name and generated arguments.

3

Validate & execute

Validate the tool name and arguments, then run the application-owned function.

4

Return tool result

Append a role: "tool" message using the matching tool_call_id.

5

Final answer

Send the updated conversation and receive the model’s final response.

Setup

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DEVUP_API_KEY"],
    base_url="https://api.devupai.com/v1",
)
1

Define a local function

First, define the function in your application that the model can call. This is a local demonstration function that returns static data. The application fully owns this logic.

import json

def get_current_weather(location):
    """Get the current weather in a given location"""
    if "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "60"})
    return json.dumps({"location": location, "temperature": "unknown"})
2

Send tools to the model

Provide the tool definition in the request using JSON Schema. The model will determine if it needs to call the tool based on the user's message.

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"
                }
            },
            "required": ["location"]
        },
    }
}]

messages = [{"role": "user", "content": "What is the weather in San Francisco?"}]

response = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Pro",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

assistant_message = response.choices[0].message
tool_calls = assistant_message.tool_calls

if tool_calls:
    for tool_call in tool_calls:
        print(tool_call.model_dump())
Illustrative response
json
{
  "id": "call_abc123",
  "type": "function",
  "function": {
    "name": "get_current_weather",
    "arguments": "{\"location\": \"San Francisco, CA\"}"
  }
}
3

Execute and return results

After validating the tool name and parsing the JSON arguments, execute your local function. Append the assistant's message, followed by the tool result containing the matching tool_call_id.

# Safely append the assistant message using model_dump()
messages.append(assistant_message.model_dump(exclude_unset=True))

for tool_call in tool_calls:
    function_name = tool_call.function.name
    if function_name == "get_current_weather":
        function_args = json.loads(tool_call.function.arguments)
        function_response = get_current_weather(location=function_args.get("location"))

    # Append the tool result back to the conversation
    messages.append({
        "tool_call_id": tool_call.id,
        "role": "tool",
        "content": function_response,
    })

# Get the final answer from the model
second_response = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Pro",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

print(second_response.choices[0].message.content)
Illustrative final response

The current temperature in San Francisco, CA is 60 degrees Fahrenheit.

Parallel tool calls

One reply can carry several entries in message.tool_calls. Run each call, then append one role: "tool" message per call, each with the tool_call_id of its call, before you send the next request. The loop in step 3 does this.

Set parallel_tool_calls: false to ask the model for at most one tool call per reply. Send parallel_tool_calls together with tools: on some models, parallel_tool_calls without tools is left out, with the notice parameter_not_supported.

tool_calls.parallel in GET /v1/models is true for models that can return several tool calls in one reply.

Streaming tool calls

With stream: true, tool calls arrive in choices[0].delta.tool_calls. Each entry has an index that identifies its call. The first entry of a call carries its id, type: "function" and function.name; the entries that follow with the same index carry the next parts of function.arguments.

Join the argument parts of each index in the order they arrive, and parse them after the stream ends. A choice whose reply calls tools ends with finish_reason: "tool_calls". Treat each id as an opaque string. As in every chat stream, when the model completes its reply the last chunk has an empty choices array and carries usage; see Streaming.

stream = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Pro",
    messages=messages,
    tools=tools,
    stream=True,
)

calls = {}
for chunk in stream:
    if not chunk.choices:
        continue  # the usage chunk has no choices
    for part in chunk.choices[0].delta.tool_calls or []:
        call = calls.setdefault(part.index, {"id": None, "name": None, "arguments": ""})
        if part.id:
            call["id"] = part.id
        if part.function and part.function.name:
            call["name"] = part.function.name
        if part.function and part.function.arguments:
            call["arguments"] += part.function.arguments

for index in sorted(calls):
    call = calls[index]
    print(index, call["id"], call["name"], json.loads(call["arguments"] or "{}"))

Tool execution safety

Security Best Practices

  • Treat model-generated arguments as untrusted user input.
  • Strictly allowlist executable tool names.
  • Validate arguments rigorously against the JSON schema.
  • Never execute arbitrary code or shell commands.
  • Apply strict timeouts to prevent hanging executions.
  • Handle tool failures gracefully and safely.
  • Never expose secrets or credentials in the tool results.
  • Limit result size to prevent context overflow.

Best Practices

Descriptions

Write clear and specific tool names and descriptions. The model's decision to invoke a tool depends heavily on them.

Validation

Use strict, minimal JSON schemas. Always validate all arguments on your application side before running any logic.

Focus

Keep the list of provided tools focused. Providing too many unrelated tools can degrade model reasoning.

Predictability

Check the selected model's robust capabilities. Use deterministic settings like a lower temperature when appropriate.

Which models take tools

Each model in GET /v1/models shows what it supports for tool calling. A key that is not shown is not known yet.

  • supports.tools is true for models that take tools.
  • tool_choice shows, for each tool_choice value whose handling is known, whether the model enforces it. A request with a value marked false is answered with the notice tool_choice_not_enforced.
  • tool_calls.chat_completions is false when tool calls for the model are served on /v1/responses: a POST /v1/chat/completions request with tools then returns HTTP 400 use_responses_api. It is true when they are served on /v1/chat/completions.
  • tool_calls.parallel is true when the model can return several tool calls in one reply (see Parallel tool calls).

A tool_choice of "required" or a named function without tools returns HTTP 400 missing_tools.

Supported capabilities

Single tool calls
Model can request a single tool execution.Model-dependent
Parallel tool calls
Model can request multiple independent tools at once. tool_calls.parallel in GET /v1/models shows which models can.Model-dependent
tool_choice: "auto"
Model decides whether to use a tool or reply directly.Supported
tool_choice: "none"
Forces the model to skip tools and reply directly.Supported
Explicit selection
Force the model to call a specific tool.Model-dependent
Streaming
Receive tool call arguments incrementally via SSE. See Streaming tool calls.Model-dependent

What's Next