Skip to content

Commit 0cdac07

Browse files
committed
Gemma4 reasoning fix
1 parent 35d2c6f commit 0cdac07

5 files changed

Lines changed: 77 additions & 53 deletions

File tree

‎com.chabicht.code-intelligence/src/com/chabicht/code_intelligence/apiclient/AnthropicApiClient.java‎

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
181181
try {
182182
if (response.statusCode() >= 200 && response.statusCode() < 300) {
183183
AtomicReference<String> currentEvent = new AtomicReference<>("");
184-
AtomicBoolean thinkingStarted = new AtomicBoolean(false);
185184

186185
response.body().forEach(line -> {
187186
if (line == null || line.isEmpty()) {
@@ -217,23 +216,15 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
217216
String deltaType = delta.get("type").getAsString();
218217

219218
if (deltaType.equals("text_delta")) {
220-
if (thinkingStarted.get()) {
221-
assistantMessage
222-
.setContent(assistantMessage.getContent() + "\n</think>\n");
223-
thinkingStarted.set(false);
219+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
220+
assistantMessage.setThinkingComplete(true);
224221
}
225222
String text = delta.get("text").getAsString();
226223
assistantMessage.setContent(assistantMessage.getContent() + text);
227224
chat.notifyMessageUpdated(assistantMessage);
228225
} else if (deltaType.equals("thinking_delta")) {
229-
if (!thinkingStarted.get()) {
230-
assistantMessage
231-
.setContent(assistantMessage.getContent() + "\n<think>\n");
232-
thinkingStarted.set(true);
233-
}
234226
// Handle thinking delta
235227
String thinking = delta.get("thinking").getAsString();
236-
assistantMessage.setContent(assistantMessage.getContent() + thinking);
237228
assistantMessage.setThinkingContent(
238229
(assistantMessage.getThinkingContent() == null ? ""
239230
: assistantMessage.getThinkingContent()) + thinking);
@@ -272,11 +263,7 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
272263
}
273264
activeToolUses.put(index, new ToolUseInfo(id, name, initialInputJson));
274265
} else if (blockType.equals("thinking")) {
275-
if (!thinkingStarted.get()) {
276-
assistantMessage
277-
.setContent(assistantMessage.getContent() + "\n<think>\n");
278-
thinkingStarted.set(true);
279-
}
266+
// Thinking block started
280267
}
281268
}
282269
break;
@@ -657,6 +644,9 @@ private void finalizeToolUses(Map<Integer, ToolUseInfo> activeToolUses, ChatMess
657644
private void finalizeAssistantMessage(ChatMessage assistantMessage, ChatConversation chat,
658645
AtomicBoolean responseFinished) {
659646
if (assistantMessage != null && !responseFinished.get()) {
647+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
648+
assistantMessage.setThinkingComplete(true);
649+
}
660650
if (assistantMessage.getFunctionCallBatch().isPresent()) {
661651
chat.notifyFunctionCalled(assistantMessage);
662652
}

‎com.chabicht.code-intelligence/src/com/chabicht/code_intelligence/apiclient/GeminiApiClient.java‎

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,8 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
121121
HttpRequest request = buildHttpRequest(modelName + ":streamGenerateContent?alt=sse&", requestBody);
122122

123123
AtomicBoolean responseFinished = new AtomicBoolean(false);
124-
AtomicBoolean thinkingStarted = new AtomicBoolean(false);
125124
AtomicBoolean functionCallEventSent = new AtomicBoolean(false);
126125
FunctionCallBatch functionCallBatch = new FunctionCallBatch();
127-
128126
asyncRequest = HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofLines())
129127
.thenAccept(response -> {
130128
if (response.statusCode() >= 200 && response.statusCode() < 300) {
@@ -140,17 +138,16 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
140138
JsonArray candidates = jsonChunk.getAsJsonArray("candidates");
141139
if (candidates != null && !candidates.isEmpty()) {
142140
JsonObject candidate = candidates.get(0).getAsJsonObject();
143-
parseCandidateContentParts(chat, assistantMessage, candidate, thinkingStarted,
141+
parseCandidateContentParts(chat, assistantMessage, candidate,
144142
functionCallBatch);
145143

146144
if (candidate.has("finishReason")) {
147145
String reason = candidate.get("finishReason").getAsString();
148146
if ("MALFORMED_FUNCTION_CALL".equals(reason)) {
149147
Activator.logError("Error " + reason + " in API response.\n");
150148
}
151-
if (thinkingStarted.get()) {
152-
assistantMessage.setContent(assistantMessage.getContent() + "\n</think>\n");
153-
thinkingStarted.set(false);
149+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
150+
assistantMessage.setThinkingComplete(true);
154151
}
155152
int parsedCallCount = countBatchCalls(functionCallBatch);
156153
if (parsedCallCount > 0) {
@@ -161,6 +158,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
161158
functionCallEventSent.set(true);
162159
}
163160
}
161+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
162+
assistantMessage.setThinkingComplete(true);
163+
}
164164
chat.notifyChatResponseFinished(assistantMessage);
165165
responseFinished.set(true);
166166
asyncRequest = null;
@@ -170,6 +170,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
170170
Activator.logError("Exception processing streaming chat chunk: " + data, e);
171171
if (asyncRequest != null) {
172172
if (!responseFinished.get()) {
173+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
174+
assistantMessage.setThinkingComplete(true);
175+
}
173176
chat.notifyChatResponseFinished(assistantMessage);
174177
responseFinished.set(true);
175178
}
@@ -184,6 +187,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
184187
+ "\n\nRequest JSON:\n" + requestBody);
185188
if (asyncRequest != null) {
186189
if (!responseFinished.get()) {
190+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
191+
assistantMessage.setThinkingComplete(true);
192+
}
187193
chat.notifyChatResponseFinished(assistantMessage);
188194
responseFinished.set(true);
189195
}
@@ -194,6 +200,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
194200
Activator.logError("Exception during streaming chat request", e);
195201
if (asyncRequest != null) {
196202
if (!responseFinished.get()) {
203+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
204+
assistantMessage.setThinkingComplete(true);
205+
}
197206
chat.notifyChatResponseFinished(assistantMessage);
198207
responseFinished.set(true);
199208
}
@@ -433,7 +442,7 @@ private boolean isDebugToolBatchLoggingEnabled() {
433442
}
434443

435444
private void parseCandidateContentParts(ChatConversation chat, ChatMessage assistantMessage, JsonObject candidate,
436-
AtomicBoolean thinkingStarted, FunctionCallBatch functionCallBatch) {
445+
FunctionCallBatch functionCallBatch) {
437446
JsonObject content = candidate.getAsJsonObject("content");
438447
if (content == null || !content.has("parts")) {
439448
return;
@@ -454,18 +463,14 @@ private void parseCandidateContentParts(ChatConversation chat, ChatMessage assis
454463
String chunk = part.get("text").getAsString();
455464
boolean isThoughtPart = part.has("thought") && part.get("thought").getAsBoolean();
456465
if (isThoughtPart) {
457-
if (!thinkingStarted.get()) {
458-
assistantMessage.setContent(assistantMessage.getContent() + "\n<think>\n");
459-
thinkingStarted.set(true);
460-
}
461466
assistantMessage.setThinkingContent(
462467
(assistantMessage.getThinkingContent() == null ? "" : assistantMessage.getThinkingContent()) + chunk);
463-
} else if (thinkingStarted.get()) {
464-
assistantMessage.setContent(assistantMessage.getContent() + "\n</think>\n");
465-
thinkingStarted.set(false);
468+
} else {
469+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
470+
assistantMessage.setThinkingComplete(true);
471+
}
472+
assistantMessage.setContent(assistantMessage.getContent() + chunk);
466473
}
467-
468-
assistantMessage.setContent(assistantMessage.getContent() + chunk);
469474
if (chat != null) {
470475
chat.notifyMessageUpdated(assistantMessage);
471476
}

‎com.chabicht.code-intelligence/src/com/chabicht/code_intelligence/apiclient/OllamaApiClient.java‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
import com.chabicht.code_intelligence.chat.tools.ToolDefinitions;
3131
import com.chabicht.code_intelligence.chat.tools.ToolProfile;
3232
import com.chabicht.code_intelligence.model.ChatConversation;
33-
import com.chabicht.code_intelligence.model.ChatConversation.ChatOption;
3433
import com.chabicht.code_intelligence.model.ChatConversation.ChatMessage;
34+
import com.chabicht.code_intelligence.model.ChatConversation.ChatOption;
3535
import com.chabicht.code_intelligence.model.ChatConversation.FunctionCall;
3636
import com.chabicht.code_intelligence.model.ChatConversation.FunctionCallBatch;
3737
import com.chabicht.code_intelligence.model.ChatConversation.FunctionCallBatch.FunctionCallItem;
@@ -206,13 +206,13 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
206206
chat.addMessage(assistantMessage, true);
207207

208208
String requestBody = gson.toJson(req);
209+
Activator.logInfo("Client: " + requestBody);
209210
HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
210211
.connectTimeout(Duration.ofSeconds(5)).followRedirects(HttpClient.Redirect.ALWAYS).build();
211212
HttpRequest request = createRequestBuilder("api/chat").POST(HttpRequest.BodyPublishers.ofString(requestBody))
212213
.header("Content-Type", "application/json").build();
213214

214215
final AtomicBoolean responseFinished = new AtomicBoolean(false);
215-
final AtomicBoolean thinkingStarted = new AtomicBoolean(false);
216216
final Map<Integer, FunctionCall> pendingToolCalls = new TreeMap<>();
217217

218218
asyncRequest = client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()).thenAccept(response -> {
@@ -221,6 +221,7 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
221221
response.body().forEach(line -> {
222222
if (line != null && !line.trim().isEmpty()) {
223223
try {
224+
Activator.logInfo("Ollama: " + line);
224225
JsonObject jsonChunk = JsonParser.parseString(line).getAsJsonObject();
225226
if (jsonChunk.has("message")) {
226227
JsonObject messageObj = jsonChunk.getAsJsonObject("message");
@@ -234,12 +235,7 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
234235
// Handle thinking content
235236
if (messageObj.has("thinking")
236237
&& !StringUtils.isEmpty(messageObj.get("thinking").getAsString())) {
237-
if (!thinkingStarted.get()) {
238-
assistantMessage.setContent(assistantMessage.getContent() + "\n<think>\n");
239-
thinkingStarted.set(true);
240-
}
241238
String thinking = messageObj.get("thinking").getAsString();
242-
assistantMessage.setContent(assistantMessage.getContent() + thinking);
243239
assistantMessage
244240
.setThinkingContent((assistantMessage.getThinkingContent() == null ? ""
245241
: assistantMessage.getThinkingContent()) + thinking);
@@ -249,9 +245,8 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
249245
// Handle content
250246
if (messageObj.has("content")
251247
&& !StringUtils.isEmpty(messageObj.get("content").getAsString())) {
252-
if (thinkingStarted.get()) {
253-
assistantMessage.setContent(assistantMessage.getContent() + "\n</think>\n");
254-
thinkingStarted.set(false);
248+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
249+
assistantMessage.setThinkingComplete(true);
255250
}
256251
String chunk = messageObj.get("content").getAsString();
257252
assistantMessage.setContent(assistantMessage.getContent() + chunk);
@@ -448,6 +443,9 @@ private JsonArray buildMessagesJson(ChatConversation chat) {
448443
JsonObject jsonMsg = new JsonObject();
449444
jsonMsg.addProperty("role", message.getRole().toString().toLowerCase());
450445
jsonMsg.addProperty("content", compileMessageContent(message));
446+
if (StringUtils.isNotBlank(message.getThinkingContent())) {
447+
jsonMsg.addProperty("thinking", message.getThinkingContent());
448+
}
451449
appendAssistantToolCalls(jsonMsg, message);
452450
messagesJson.add(jsonMsg);
453451
appendToolResultMessages(messagesJson, message);
@@ -644,6 +642,9 @@ private JsonElement parseJsonElementOrEmptyObject(String json) {
644642
private void finalizeAssistantMessage(ChatMessage assistantMessage, ChatConversation chat,
645643
AtomicBoolean responseFinished) {
646644
if (assistantMessage != null && !responseFinished.get()) {
645+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
646+
assistantMessage.setThinkingComplete(true);
647+
}
647648
if (assistantMessage.getFunctionCallBatch().isPresent()) {
648649
chat.notifyFunctionCalled(assistantMessage);
649650
}

‎com.chabicht.code-intelligence/src/com/chabicht/code_intelligence/apiclient/OpenAiApiClient.java‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
245245
// line-by-line.
246246
asyncRequest = client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()).thenAccept(response -> {
247247
try {
248-
AtomicBoolean reasoningStarted = new AtomicBoolean(false);
249248
if (response.statusCode() >= 200 && response.statusCode() < 300) {
250249
response.body().forEach(line -> {
251250
// Each chunk from the API is prefixed with "data: ".
@@ -268,17 +267,14 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
268267
// content.
269268
if (delta.has("content") && !delta.get("content").isJsonNull()) {
270269
chunk = delta.get("content").getAsString();
271-
if (reasoningStarted.get()) {
272-
chunk = "</think>\n" + chunk;
273-
reasoningStarted.set(false);
270+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
271+
assistantMessage.setThinkingComplete(true);
274272
}
275273
} else if (delta.has("reasoning_content")
276274
&& !delta.get("reasoning_content").isJsonNull()) {
277-
chunk = delta.get("reasoning_content").getAsString();
278-
if (!reasoningStarted.get()) {
279-
chunk = "<think>\n" + chunk;
280-
reasoningStarted.set(true);
281-
}
275+
String thinkingChunk = delta.get("reasoning_content").getAsString();
276+
assistantMessage.setThinkingContent((assistantMessage.getThinkingContent() == null ? "" : assistantMessage.getThinkingContent()) + thinkingChunk);
277+
chat.notifyMessageUpdated(assistantMessage);
282278
} else {
283279
chunk = "";
284280
}
@@ -330,6 +326,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
330326
finalizeToolCalls(activeToolCalls, assistantMessage, chat);
331327
}
332328

329+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
330+
assistantMessage.setThinkingComplete(true);
331+
}
333332
chat.notifyChatResponseFinished(assistantMessage);
334333
asyncRequest = null;
335334
}
@@ -341,6 +340,9 @@ public void performChat(String modelName, ChatConversation chat, int maxResponse
341340
finalizeToolCalls(activeToolCalls, assistantMessage, chat);
342341
}
343342

343+
if (assistantMessage.getThinkingContent() != null && !assistantMessage.isThinkingComplete()) {
344+
assistantMessage.setThinkingComplete(true);
345+
}
344346
chat.notifyChatResponseFinished(assistantMessage);
345347
asyncRequest = null;
346348
return null;

‎com.chabicht.code-intelligence/src/com/chabicht/code_intelligence/chat/ChatView.java‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ private String messageContentToHtml(MessageRenderSnapshot message) {
262262
if (message.getRole() == Role.TOOL_SUMMARY) { // Add this new block
263263
return toolSummaryToHtml(message);
264264
}
265-
MessageContentWithReasoning thoughtsAndMessage = splitThoughtsFromMessage(message.getContent());
265+
MessageContentWithReasoning thoughtsAndMessage = splitThoughtsFromMessage(message);
266+
266267

267268
String thinkHtml = "";
268269
if (StringUtils.isNotBlank(thoughtsAndMessage.getThoughts())) {
@@ -532,6 +533,7 @@ private MessageRenderSnapshot createMessageRenderSnapshot(ChatMessage message, b
532533
}
533534

534535
return new MessageRenderSnapshot(message.getId(), message.getRole(), StringUtils.defaultString(message.getContent()),
536+
message.getThinkingContent(), message.isThinkingComplete(),
535537
copyFunctionCallBatch(message.getFunctionCallBatch()), attachments);
536538
}
537539

@@ -662,14 +664,18 @@ private static final class MessageRenderSnapshot {
662664
private final UUID id;
663665
private final Role role;
664666
private final String content;
667+
private final String thinkingContent;
668+
private final boolean isThinkingComplete;
665669
private final Optional<FunctionCallBatch> functionCallBatch;
666670
private final List<AttachmentRenderSnapshot> attachments;
667671

668-
private MessageRenderSnapshot(UUID id, Role role, String content, Optional<FunctionCallBatch> functionCallBatch,
672+
private MessageRenderSnapshot(UUID id, Role role, String content, String thinkingContent, boolean isThinkingComplete, Optional<FunctionCallBatch> functionCallBatch,
669673
List<AttachmentRenderSnapshot> attachments) {
670674
this.id = id;
671675
this.role = role;
672676
this.content = content;
677+
this.thinkingContent = thinkingContent;
678+
this.isThinkingComplete = isThinkingComplete;
673679
this.functionCallBatch = functionCallBatch == null ? Optional.empty() : functionCallBatch;
674680
this.attachments = attachments == null ? new ArrayList<>() : attachments;
675681
}
@@ -686,6 +692,14 @@ public String getContent() {
686692
return content;
687693
}
688694

695+
public String getThinkingContent() {
696+
return thinkingContent;
697+
}
698+
699+
public boolean isThinkingComplete() {
700+
return isThinkingComplete;
701+
}
702+
689703
public Optional<FunctionCallBatch> getFunctionCallBatch() {
690704
return functionCallBatch;
691705
}
@@ -2111,9 +2125,21 @@ private void formatCode(IDocument document, int offset, int length) {
21112125
}
21122126

21132127
private MessageContentWithReasoning splitThoughtsFromMessage(ChatMessage message) {
2128+
if (StringUtils.isNotBlank(message.getThinkingContent())) {
2129+
return new MessageContentWithReasoning(message.getThinkingContent(), StringUtils.defaultString(message.getContent()), message.isThinkingComplete());
2130+
}
21142131
return splitThoughtsFromMessage(message.getContent());
21152132
}
21162133

2134+
private MessageContentWithReasoning splitThoughtsFromMessage(MessageRenderSnapshot message) {
2135+
if (StringUtils.isNotBlank(message.getThinkingContent())) {
2136+
return new MessageContentWithReasoning(message.getThinkingContent(), StringUtils.defaultString(message.getContent()), message.isThinkingComplete());
2137+
}
2138+
return splitThoughtsFromMessage(message.getContent());
2139+
}
2140+
2141+
2142+
21172143
private MessageContentWithReasoning splitThoughtsFromMessage(String content) {
21182144
content = StringUtils.stripToEmpty(content);
21192145
Matcher thinkStartMatcher = PATTERN_THINK_START.matcher(content);

0 commit comments

Comments
 (0)