[object Object]

← back to Exo

send error finish reason on failing to parse a tool call (#1785)

509533d49e678e9b16224ee5a46741a744778c6a · 2026-03-24 17:21:39 +0000 · Evan Quiney

a simplification of #1757 which is now stale

Files touched

Diff

commit 509533d49e678e9b16224ee5a46741a744778c6a
Author: Evan Quiney <evanev7@gmail.com>
Date:   Tue Mar 24 17:21:39 2026 +0000

    send error finish reason on failing to parse a tool call (#1785)
    
    a simplification of #1757 which is now stale
---
 src/exo/worker/engines/mlx/utils_mlx.py                            | 7 ++-----
 src/exo/worker/runner/llm_inference/model_output_parsers.py        | 7 +++++--
 .../worker/tests/unittests/test_runner/test_parse_tool_calls.py    | 1 +
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/exo/worker/engines/mlx/utils_mlx.py b/src/exo/worker/engines/mlx/utils_mlx.py
index 91e6fd60..e34b4a43 100644
--- a/src/exo/worker/engines/mlx/utils_mlx.py
+++ b/src/exo/worker/engines/mlx/utils_mlx.py
@@ -740,12 +740,9 @@ def _parse_kimi_tool_calls(text: str):
         if func_args_match is None:
             raise ValueError("No tool call arguments found.")
         func_args = func_args_match.group(1)
-        try:
-            arg_dct = json.loads(func_args)  # pyright: ignore[reportAny]
-        except Exception:
-            arg_dct = None
+        arg_dct = json.loads(func_args)  # pyright: ignore[reportAny]
 
-        return dict(id=tool_call_id, name=func_name, arguments=arg_dct)
+        return dict(id=tool_call_id, name=func_name, arguments=arg_dct)  # pyright: ignore[reportAny]
 
     tool_matches = _tool_call_split_regex.findall(text)
     if tool_matches:
diff --git a/src/exo/worker/runner/llm_inference/model_output_parsers.py b/src/exo/worker/runner/llm_inference/model_output_parsers.py
index c6c7dd4e..b5729697 100644
--- a/src/exo/worker/runner/llm_inference/model_output_parsers.py
+++ b/src/exo/worker/runner/llm_inference/model_output_parsers.py
@@ -358,8 +358,10 @@ def parse_tool_calls(
 
             if parsed is None:
                 logger.warning(f"tool call parsing failed for text {combined}")
-                yield response.model_copy(update={"text": combined})
-                continue
+                yield response.model_copy(
+                    update={"text": combined, "token": 0, "finish_reason": "error"}
+                )
+                break
 
             yield ToolCallResponse(
                 tool_calls=parsed, usage=response.usage, stats=response.stats
@@ -374,6 +376,7 @@ def parse_tool_calls(
                 update={
                     "text": "".join(tool_call_text_parts),
                     "token": 0,
+                    "finish_reason": "error",
                 }
             )
             yield response
diff --git a/src/exo/worker/tests/unittests/test_runner/test_parse_tool_calls.py b/src/exo/worker/tests/unittests/test_runner/test_parse_tool_calls.py
index 03be01bd..49655f03 100644
--- a/src/exo/worker/tests/unittests/test_runner/test_parse_tool_calls.py
+++ b/src/exo/worker/tests/unittests/test_runner/test_parse_tool_calls.py
@@ -87,6 +87,7 @@ class TestParseToolCalls:
         assert len(results) == 1
         assert isinstance(results[0], GenerationResponse)
         assert results[0].text == "<tool_call>bad content</tool_call>"
+        assert results[0].finish_reason == "error"
 
     def test_tool_schema_coerces_string_arguments_to_expected_types(self):
         """Tool argument values should be coerced using provided JSON schema."""

← b6240a97 Prevent Qwen3.5 looping by using mlx lm fork (#1784)  ·  back to Exo  ·  override macmon in flake (#1747) 7ee88c1f →