Skip to content

Commit 14b91dc

Browse files
committed
Fix: handle JSON parse error in streaming tool calls
Signed-off-by: Minu Kim <[email protected]>
1 parent 1dd686b commit 14b91dc

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.model.tool;
18+
19+
import java.util.List;
20+
import java.util.function.BiPredicate;
21+
22+
import org.jetbrains.annotations.NotNull;
23+
24+
import org.springframework.ai.chat.model.ChatResponse;
25+
import org.springframework.ai.chat.model.Generation;
26+
import org.springframework.ai.chat.prompt.ChatOptions;
27+
28+
/**
29+
* Executes tools only when the assistant signals completion (finishReason = "tool_calls"
30+
* or "stop").
31+
*/
32+
public final class ToolOnFinishPredicate implements ToolExecutionEligibilityPredicate {
33+
34+
@Override
35+
public boolean isToolExecutionRequired(ChatOptions opts, ChatResponse resp) {
36+
List<Generation> gens = resp.getResults();
37+
if (gens.isEmpty()) {
38+
return false;
39+
}
40+
41+
Generation gen = gens.get(0);
42+
boolean hasToolCalls = !gen.getOutput().getToolCalls().isEmpty();
43+
String finish = String.valueOf(gen.getMetadata().get("finishReason"));
44+
45+
return hasToolCalls && ("tool_calls".equalsIgnoreCase(finish) || "stop".equalsIgnoreCase(finish));
46+
}
47+
48+
@Override
49+
public boolean test(ChatOptions chatOptions, ChatResponse chatResponse) {
50+
return false;
51+
}
52+
53+
@NotNull
54+
@Override
55+
public BiPredicate<ChatOptions, ChatResponse> and(
56+
@NotNull BiPredicate<? super ChatOptions, ? super ChatResponse> other) {
57+
return ToolExecutionEligibilityPredicate.super.and(other);
58+
}
59+
60+
@NotNull
61+
@Override
62+
public BiPredicate<ChatOptions, ChatResponse> negate() {
63+
return ToolExecutionEligibilityPredicate.super.negate();
64+
}
65+
66+
@NotNull
67+
@Override
68+
public BiPredicate<ChatOptions, ChatResponse> or(
69+
@NotNull BiPredicate<? super ChatOptions, ? super ChatResponse> other) {
70+
return ToolExecutionEligibilityPredicate.super.or(other);
71+
}
72+
73+
}

0 commit comments

Comments
 (0)