Skip to content

Commit 1bb7ccb

Browse files
committed
feat(ollama): enhance JSON response handling and fallback mechanism in generateJson method
1 parent 29964f1 commit 1bb7ccb

File tree

1 file changed

+49
-27
lines changed

1 file changed

+49
-27
lines changed

packages/core/src/core/ollamaContentGenerator.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,36 @@ export class OllamaContentGenerator implements ContentGenerator {
136136
const prompt = this.convertContentsToPrompt(contents);
137137
const jsonPrompt = `${prompt}\n\nPlease respond with valid JSON that matches this schema: ${JSON.stringify(schema)}`;
138138

139+
const targetModel = model || 'gpt-oss:20b';
140+
139141
try {
140-
const response = await fetch(`${this.baseUrl}/api/generate`, {
142+
// First try with JSON format
143+
let response = await fetch(`${this.baseUrl}/api/generate`, {
141144
method: 'POST',
142145
headers: { 'Content-Type': 'application/json' },
143146
body: JSON.stringify({
144-
model: model || 'llama3.2:3b',
147+
model: targetModel,
145148
prompt: jsonPrompt,
146149
stream: false,
147150
format: 'json',
148151
}),
149152
signal: abortSignal,
150153
});
151154

155+
// If JSON format fails, try without format constraint
156+
if (!response.ok && response.status === 400) {
157+
response = await fetch(`${this.baseUrl}/api/generate`, {
158+
method: 'POST',
159+
headers: { 'Content-Type': 'application/json' },
160+
body: JSON.stringify({
161+
model: targetModel,
162+
prompt: jsonPrompt,
163+
stream: false,
164+
}),
165+
signal: abortSignal,
166+
});
167+
}
168+
152169
if (!response.ok) {
153170
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
154171
}
@@ -258,33 +275,38 @@ export class OllamaContentGenerator implements ContentGenerator {
258275
if (line.trim()) {
259276
try {
260277
const chunk = JSON.parse(line);
261-
const streamResponse = {
262-
candidates: [
263-
{
264-
content: {
265-
role: 'model',
266-
parts: [{ text: chunk.response || '' }],
278+
279+
// Only yield chunks that have actual response content or are done
280+
// Skip chunks with only thinking content
281+
if (chunk.response || chunk.done) {
282+
const streamResponse = {
283+
candidates: [
284+
{
285+
content: {
286+
role: 'model',
287+
parts: [{ text: chunk.response || '' }],
288+
},
289+
finishReason: chunk.done ? FinishReason.STOP : undefined,
267290
},
268-
finishReason: chunk.done ? FinishReason.STOP : undefined,
291+
],
292+
usageMetadata: chunk.done ? {
293+
promptTokenCount: chunk.prompt_eval_count || 0,
294+
candidatesTokenCount: chunk.eval_count || 0,
295+
totalTokenCount: (chunk.prompt_eval_count || 0) + (chunk.eval_count || 0),
296+
} : undefined,
297+
get text() {
298+
return chunk.response || '';
269299
},
270-
],
271-
usageMetadata: chunk.done ? {
272-
promptTokenCount: chunk.prompt_eval_count || 0,
273-
candidatesTokenCount: chunk.eval_count || 0,
274-
totalTokenCount: (chunk.prompt_eval_count || 0) + (chunk.eval_count || 0),
275-
} : undefined,
276-
get text() {
277-
return chunk.response || '';
278-
},
279-
get data() {
280-
return chunk.response || '';
281-
},
282-
functionCalls: [],
283-
executableCode: null,
284-
codeExecutionResult: null,
285-
} as any;
286-
287-
yield streamResponse;
300+
get data() {
301+
return chunk.response || '';
302+
},
303+
functionCalls: [],
304+
executableCode: null,
305+
codeExecutionResult: null,
306+
} as any;
307+
308+
yield streamResponse;
309+
}
288310
} catch (e) {
289311
// Skip malformed JSON lines
290312
continue;

0 commit comments

Comments
 (0)