[object Object]

← back to Exo

Normalise Responses API tool call format (#1704)

f221a6c85c427757df547cce71f92fdf0861b5f5 · 2026-03-11 18:10:12 +0000 · rltakashige

## Motivation

The responses API often does not provide tool args nested under a
"function" field. Since we follow the chat completions format of tools
in the backend (for MLX chat templates), we need to normalise to this
format.

## Test Plan

### Manual Testing
Works on n8n!
<img width="3442" height="2076" alt="image"
src="https://github.com/user-attachments/assets/9e11d679-0102-4d83-9a8e-b0a7a5898708"
/>

Files touched

Diff

commit f221a6c85c427757df547cce71f92fdf0861b5f5
Author: rltakashige <rl.takashige@gmail.com>
Date:   Wed Mar 11 18:10:12 2026 +0000

    Normalise Responses API tool call format (#1704)
    
    ## Motivation
    
    The responses API often does not provide tool args nested under a
    "function" field. Since we follow the chat completions format of tools
    in the backend (for MLX chat templates), we need to normalise to this
    format.
    
    ## Test Plan
    
    ### Manual Testing
    Works on n8n!
    <img width="3442" height="2076" alt="image"
    src="https://github.com/user-attachments/assets/9e11d679-0102-4d83-9a8e-b0a7a5898708"
    />
---
 src/exo/master/adapters/responses.py | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/exo/master/adapters/responses.py b/src/exo/master/adapters/responses.py
index 0a40c016..c92c4e37 100644
--- a/src/exo/master/adapters/responses.py
+++ b/src/exo/master/adapters/responses.py
@@ -128,6 +128,28 @@ def responses_request_to_text_generation(
         effort_from_reasoning, request.enable_thinking
     )
 
+    # The responses API often does not provide tool args nested under a "function" field.
+    # Since we follow the chat completions format of tools in the backend (for MLX chat templates)
+    # we need to normalise to this format.
+    normalised_tools: list[dict[str, Any]] | None = None
+    if request.tools:
+        normalised_tools = []
+        for tool in request.tools:
+            if "function" in tool:
+                normalised_tools.append(tool)
+            else:
+                normalised_tools.append(
+                    {
+                        "type": "function",
+                        "function": {
+                            "name": tool.get("name", ""),
+                            "description": tool.get("description", ""),
+                            "parameters": tool.get("parameters", {}),
+                            **({"strict": tool["strict"]} if "strict" in tool else {}),
+                        },
+                    }
+                )
+
     return TextGenerationTaskParams(
         model=request.model,
         input=input_value,
@@ -136,7 +158,7 @@ def responses_request_to_text_generation(
         temperature=request.temperature,
         top_p=request.top_p,
         stream=request.stream,
-        tools=request.tools,
+        tools=normalised_tools,
         top_k=request.top_k,
         stop=request.stop,
         seed=request.seed,

← 2994b410 fix: validate num_key_value_heads in tensor sharding placeme  ·  back to Exo  ·  fix: show partial download progress on initial dashboard loa 0782d90e →