# How to Integrate Tool Calling with DS4's OpenAI-Compatible API Endpoints

> Integrate tool calling with DS4's OpenAI-compatible API. Send tool definitions, get function calls, and return results seamlessly. Self-host your AI with DS4.

- Repository: [Salvatore Sanfilippo/ds4](https://github.com/antirez/ds4)
- Tags: how-to-guide
- Published: 2026-08-04

---

**Use DS4's self-hosted OpenAI-compatible server to send tool definitions, receive streaming function calls, and return results by including `tool_call_id` in follow-up requests.**

DS4 (DeepSeek 4) implements a fully self-hosted OpenAI-compatible server that supports **tool calling**—the same function-calling feature available in OpenAI's Chat Completion API. This integration allows you to define custom tools, receive structured function calls from the model, and feed results back into the conversation. Understanding how to integrate tool calling with DS4's OpenAI-compatible API endpoints unlocks agentic workflows without relying on external APIs.

## Parsing Tool Definitions from Client Requests

When your HTTP request includes a `tools` array, DS4 extracts and validates each function schema before passing them to the model. The parsing logic resides in **[`ds4_server.c`](https://github.com/antirez/ds4/blob/main/ds4_server.c)**, specifically within `parse_tools_value()`.

The function iterates over the raw JSON array, unwraps each OpenAI-style "function" wrapper via `openai_function_schema_from_tool()`, and stores the result in an internal `tool_schema_orders` structure:

```c
// ds4_server.c lines 1608-1625
// parse_tools_value() processes the "tools" JSON array
// Each entry becomes a tool_schema_orders entry with name, description, and parameters

```

Your tool definitions must follow the standard OpenAI format:

```json
{
  "type": "function",
  "function": {
    "name": "weather_lookup",
    "description": "Get current weather for a location",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {"type": "string", "description": "City name"},
        "units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
      },
      "required": ["city"]
    }
  }
}

```

DS4 stores these schemas in the **`request` struct** field `tool_orders` and sets `has_tools = true`, signaling the inference engine to include tool instructions in the system prompt.

## Receiving Streaming Tool Calls from DS4

DS4 streams responses using the `openai_stream` structure. When the model emits a tool call, the server captures it through **`openai_tool_stream`** objects at lines 1196-1210 of [`ds4_server.c`](https://github.com/antirez/ds4/blob/main/ds4_server.c).

```c
// ds4_server.c lines 1190-1212
// openai_tool_stream_init() prepares incremental JSON output
// Emits call_id, function name, and arguments as partial SSE chunks

```

The streaming SSE payload includes a `tool_calls` array matching OpenAI's format:

```json
{
  "choices": [{
    "delta": {
      "tool_calls": [{
        "index": 0,
        "id": "call_abc123xyz",
        "type": "function",
        "function": {
          "name": "weather_lookup",
          "arguments": "{\"city\":\"Berlin\",\"units\":\"celsius\"}"
        }
      }]
    }
  }]
}

```

DS4 handles fragmented arguments across multiple SSE chunks, buffering until the complete JSON is available.

## Returning Tool Results to Continue Generation

After executing the requested function, your client must send a follow-up request containing:

- **`role: "assistant"`** with the original `tool_calls` array (replayed exactly)
- A second **assistant message** with `tool_call_id` and `tool_result`

DS4 matches the ID via `chat_msg_add_tool_call_id()` and appends the result text through `append_tool_result_text()` at lines 889-903:

```c
// ds4_server.c lines 889-903
// parse_messages() locates matching tool_call_id
// chat_msg structure stores result for prompt replay

```

The **`chat_msg` struct** maintains the full conversation state, enabling DS4 to resume generation with tool outputs available as context.

## Complete Tool Calling Example

### Step 1: Request with Tool Definition

```bash
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "messages": [{"role":"user","content":"What is the weather in Tokyo?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "weather_lookup",
        "description": "Get current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {"type": "string"},
            "units": {"type": "string", "enum": ["celsius","fahrenheit"]}
          },
          "required": ["city"]
        }
      }
    }],
    "tool_choice": "auto",
    "stream": true
  }'

```

### Step 2: Receive and Parse Tool Call

Extract from the SSE stream:
- `tool_calls[0].id` → `"call_weather_001"`
- `tool_calls[0].function.name` → `"weather_lookup"`
- `tool_calls[0].function.arguments` → parse as JSON

### Step 3: Return Tool Result

```bash
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "messages": [
      {"role":"user","content":"What is the weather in Tokyo?"},
      {
        "role":"assistant",
        "tool_calls": [{
          "id":"call_weather_001",
          "type":"function",
          "function":{"name":"weather_lookup","arguments":"{\"city\":\"Tokyo\",\"units\":\"celsius\"}"}
        }]
      },
      {
        "role":"assistant",
        "tool_call_id":"call_weather_001",
        "tool_result":"{\"temperature\":22,\"condition\":\"partly cloudy\",\"humidity\":65}"
      }
    ],
    "stream": false
  }'

```

DS4 continues generation, now able to formulate a natural language response using the weather data.

## Key Architecture Components

| Component | Location | Purpose |
|-----------|----------|---------|
| `request` struct | `ds4_server.c:1064-1086` | Holds `tool_orders`, `has_tools` flag, and replay state |
| `tool_schema_orders` | `ds4_server.c:1069-1079` | Dynamic collection of parsed tool schemas |
| `openai_stream` / `openai_tool_stream` | `ds4_server.c:1190-1212` | Incremental JSON output for text and tool fragments |
| `tool_calls` / `tool_call` | `ds4_server.c:3347-3372` | Representation of single invocation; freed post-request |
| `parse_tools_value()` | `ds4_server.c:1608-1625` | Main entry point for tool JSON parsing |
| `chat_msg_add_tool_call_id()` | `ds4_server.c:889-903` | Re-attaches results to conversation |

## Summary

- **Tool definition**: Send OpenAI-standard `tools` array; DS4 parses via `parse_tools_value()` into `tool_schema_orders`
- **Tool emission**: DS4 streams `tool_calls` through `openai_tool_stream` with full OpenAI-compatible SSE formatting
- **Result replay**: Include `tool_call_id` in assistant messages; DS4 matches via `chat_msg_add_tool_call_id()` and continues generation
- **State management**: The `request` struct maintains `has_tools` and `tool_orders` across the full exchange

## Frequently Asked Questions

### Does DS4 validate tool call arguments against the JSON schema?

DS4 extracts tool schemas for prompt construction but does not enforce JSON Schema validation on returned arguments. The model generates arguments as JSON strings; your client should validate before execution. Schema validation occurs implicitly during parsing—malformed tool definitions are rejected at `openai_function_schema_from_tool()`.

### Can I force a specific tool with `tool_choice`?

Yes. Set `"tool_choice": {"type": "function", "function": {"name": "specific_tool"}}` to force that tool, or `"tool_choice": "auto"` for model discretion, or `"none"` to disable. DS4 passes this through the `request` struct to influence sampling.

### What happens if I send a `tool_call_id` that doesn't exist?

DS4's `chat_msg_add_tool_call_id()` attempts to locate the matching ID. If not found, the message is treated as a standard assistant message without tool context. The conversation continues but may produce inconsistent results since the model lacks expected grounding.