How to Integrate Tool Calling with DS4's OpenAI-Compatible API Endpoints
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, 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:
// 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:
{
"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.
// 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:
{
"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 originaltool_callsarray (replayed exactly)- A second assistant message with
tool_call_idandtool_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:
// 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
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
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
toolsarray; DS4 parses viaparse_tools_value()intotool_schema_orders - Tool emission: DS4 streams
tool_callsthroughopenai_tool_streamwith full OpenAI-compatible SSE formatting - Result replay: Include
tool_call_idin assistant messages; DS4 matches viachat_msg_add_tool_call_id()and continues generation - State management: The
requeststruct maintainshas_toolsandtool_ordersacross 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →