Skip to content

Commit 1a04dc1

Browse files
sampottingerDeepSeek V4.1 Flash (via OpenRouter)
andcommitted
fix(assistant): treat malformed tool arguments as failed tool calls
Co-authored-by: DeepSeek V4.1 Flash (via OpenRouter) <noreply@deepseek.com>
1 parent 7c31372 commit 1a04dc1

3 files changed

Lines changed: 21 additions & 19 deletions

File tree

‎engine/src/main/java/org/kigalisim/assistant/model/OpenAiModelProtocolStrategy.java‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,10 @@ private AssistantToolInvocation parseToolCall(JsonObject toolCall) {
173173
}
174174

175175
/**
176-
* Parses tool arguments, requiring them to be a JSON object.
176+
* Parses tool arguments, falling back to an empty object when they are malformed.
177177
*
178178
* @param argumentsRaw The arguments value as received.
179-
* @return The parsed argument object.
180-
* @throws IllegalStateException If the arguments are not a JSON object.
179+
* @return The parsed argument object, or an empty object when malformed.
181180
*/
182181
private JsonObject parseArguments(Object argumentsRaw) {
183182
if (argumentsRaw instanceof JsonObject arguments) {
@@ -187,18 +186,12 @@ private JsonObject parseArguments(Object argumentsRaw) {
187186
Object parsed = Jsoner.deserialize(argumentsString);
188187
if (parsed instanceof JsonObject arguments) {
189188
return arguments;
190-
} else {
191-
throw new IllegalStateException(
192-
"Tool arguments were not a JSON object: " + abbreviate(argumentsString));
193189
}
194190
} catch (JsonException e) {
195-
throw new IllegalStateException(
196-
"Unable to parse tool arguments: " + abbreviate(argumentsString), e);
191+
return new JsonObject();
197192
}
198-
} else {
199-
throw new IllegalStateException(
200-
"Tool arguments were not a JSON object: " + abbreviate(String.valueOf(argumentsRaw)));
201193
}
194+
return new JsonObject();
202195
}
203196

204197
/**

‎engine/src/test/java/org/kigalisim/assistant/UntimedAssistantTurnStrategyTest.java‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,20 +274,24 @@ public void testUnknownToolProducesFailure() {
274274
}
275275

276276
/**
277-
* Test that malformed tool arguments fail the query rather than being silently ignored.
277+
* Test that malformed tool arguments produce a failed tool call without failing the turn.
278278
*/
279279
@Test
280-
public void testMalformedArgumentsFailQuery() {
280+
public void testMalformedArgumentsProduceFailure() {
281281
Harness harness = new Harness(25);
282282
JsonArray firstCalls = new JsonArray();
283283
firstCalls.add(toolCall("call_bad", "message_user", "{\"message\":"));
284284
harness.wire.enqueuePayload(buildResponse(assistantMessage(firstCalls, null), "tool_calls"));
285285

286+
JsonArray secondCalls = new JsonArray();
287+
secondCalls.add(toolCall("call_end", "end_turn", "{}"));
288+
harness.wire.enqueuePayload(buildResponse(assistantMessage(secondCalls, null), "tool_calls"));
289+
286290
harness.strategy.runTurn(harness.toolbelt, "chat", "hi", harness.callback);
287291

288292
List<AssistantResult> actions = harness.callback.actions;
289-
assertEquals(1, actions.size(), "Malformed arguments should fail the model query");
290-
assertFalse(actions.get(0).getWasSuccessful(), "Malformed arguments should fail");
293+
assertFalse(actions.get(1).getWasSuccessful(), "Malformed arguments should fail");
294+
assertTrue(actions.get(1).getToolInvocation().orElseThrow().getCall().isEmpty());
291295
assertEquals(1, harness.callback.completions.size());
292296
}
293297

‎engine/src/test/java/org/kigalisim/assistant/model/OpenAiModelProtocolStrategyTest.java‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,20 @@ public void testParseMultipleToolCallsInOrder() throws IOException {
164164
}
165165

166166
/**
167-
* Test that malformed arguments cause parsing to fail.
167+
* Test that malformed arguments become an empty object without failing the parse.
168168
*
169169
* @throws IOException If the fixture cannot be read.
170170
*/
171171
@Test
172-
public void testParseMalformedArgumentsThrows() throws IOException {
172+
public void testParseMalformedArguments() throws IOException {
173173
OpenAiModelProtocolStrategy strategy = new OpenAiModelProtocolStrategy("test-model");
174-
String raw = readFixture("openai_response_malformed_arguments.json");
175-
assertThrows(IllegalStateException.class, () -> strategy.parseResponse(raw));
174+
AssistantModelResponse response =
175+
strategy.parseResponse(readFixture("openai_response_malformed_arguments.json"));
176+
177+
assertEquals(2, response.getToolCalls().size(), "Both calls should still parse");
178+
assertTrue(response.getToolCalls().get(0).getCall().isEmpty(),
179+
"Malformed arguments should become an empty object");
180+
assertEquals("Good", response.getToolCalls().get(1).getCall().get("message"));
176181
}
177182

178183
/**

0 commit comments

Comments
 (0)