mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
Fix: Process all parts in response chunks when thought is first (#13539)
This commit is contained in:
committed by
Tommaso Sciortino
parent
982fd1fc29
commit
02e68e4554
@@ -754,6 +754,108 @@ describe('Turn', () => {
|
|||||||
|
|
||||||
expect(events).toEqual([expectedEvent]);
|
expect(events).toEqual([expectedEvent]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should process all parts when thought is first part in chunk', async () => {
|
||||||
|
const mockResponseStream = (async function* () {
|
||||||
|
yield {
|
||||||
|
type: StreamEventType.CHUNK,
|
||||||
|
value: {
|
||||||
|
candidates: [
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
parts: [
|
||||||
|
{ text: '**Planning** the solution', thought: 'planning' },
|
||||||
|
{ text: 'I will help you with that.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
citationMetadata: {
|
||||||
|
citations: [{ uri: 'https://example.com', title: 'Source' }],
|
||||||
|
},
|
||||||
|
finishReason: 'STOP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
functionCalls: [
|
||||||
|
{
|
||||||
|
id: 'fc1',
|
||||||
|
name: 'ReadFile',
|
||||||
|
args: { path: 'file.txt' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responseId: 'trace-789',
|
||||||
|
} as unknown as GenerateContentResponse,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
for await (const event of turn.run(
|
||||||
|
{ model: 'gemini' },
|
||||||
|
[{ text: 'Test mixed content' }],
|
||||||
|
new AbortController().signal,
|
||||||
|
)) {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should yield:
|
||||||
|
// 1. Thought event (from first part)
|
||||||
|
// 2. Content event (from second part)
|
||||||
|
// 3. ToolCallRequest event (from functionCalls)
|
||||||
|
// 4. Citation event (from citationMetadata, emitted with finishReason)
|
||||||
|
// 5. Finished event (from finishReason)
|
||||||
|
|
||||||
|
expect(events.length).toBe(5);
|
||||||
|
|
||||||
|
const thoughtEvent = events.find(
|
||||||
|
(e) => e.type === GeminiEventType.Thought,
|
||||||
|
);
|
||||||
|
expect(thoughtEvent).toBeDefined();
|
||||||
|
expect(thoughtEvent).toMatchObject({
|
||||||
|
type: GeminiEventType.Thought,
|
||||||
|
value: { subject: 'Planning', description: 'the solution' },
|
||||||
|
traceId: 'trace-789',
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentEvent = events.find(
|
||||||
|
(e) => e.type === GeminiEventType.Content,
|
||||||
|
);
|
||||||
|
expect(contentEvent).toBeDefined();
|
||||||
|
expect(contentEvent).toMatchObject({
|
||||||
|
type: GeminiEventType.Content,
|
||||||
|
value: 'I will help you with that.',
|
||||||
|
traceId: 'trace-789',
|
||||||
|
});
|
||||||
|
|
||||||
|
const toolCallEvent = events.find(
|
||||||
|
(e) => e.type === GeminiEventType.ToolCallRequest,
|
||||||
|
);
|
||||||
|
expect(toolCallEvent).toBeDefined();
|
||||||
|
expect(toolCallEvent).toMatchObject({
|
||||||
|
type: GeminiEventType.ToolCallRequest,
|
||||||
|
value: expect.objectContaining({
|
||||||
|
callId: 'fc1',
|
||||||
|
name: 'ReadFile',
|
||||||
|
args: { path: 'file.txt' },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const citationEvent = events.find(
|
||||||
|
(e) => e.type === GeminiEventType.Citation,
|
||||||
|
);
|
||||||
|
expect(citationEvent).toBeDefined();
|
||||||
|
expect(citationEvent).toMatchObject({
|
||||||
|
type: GeminiEventType.Citation,
|
||||||
|
value: expect.stringContaining('https://example.com'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const finishedEvent = events.find(
|
||||||
|
(e) => e.type === GeminiEventType.Finished,
|
||||||
|
);
|
||||||
|
expect(finishedEvent).toBeDefined();
|
||||||
|
expect(finishedEvent).toMatchObject({
|
||||||
|
type: GeminiEventType.Finished,
|
||||||
|
value: { reason: 'STOP' },
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDebugResponses', () => {
|
describe('getDebugResponses', () => {
|
||||||
|
|||||||
@@ -290,15 +290,16 @@ export class Turn {
|
|||||||
|
|
||||||
const traceId = resp.responseId;
|
const traceId = resp.responseId;
|
||||||
|
|
||||||
const thoughtPart = resp.candidates?.[0]?.content?.parts?.[0];
|
const parts = resp.candidates?.[0]?.content?.parts ?? [];
|
||||||
if (thoughtPart?.thought) {
|
for (const part of parts) {
|
||||||
const thought = parseThought(thoughtPart.text ?? '');
|
if (part.thought) {
|
||||||
yield {
|
const thought = parseThought(part.text ?? '');
|
||||||
type: GeminiEventType.Thought,
|
yield {
|
||||||
value: thought,
|
type: GeminiEventType.Thought,
|
||||||
traceId,
|
value: thought,
|
||||||
};
|
traceId,
|
||||||
continue;
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = getResponseText(resp);
|
const text = getResponseText(resp);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function getResponseText(
|
|||||||
candidate.content.parts.length > 0
|
candidate.content.parts.length > 0
|
||||||
) {
|
) {
|
||||||
return candidate.content.parts
|
return candidate.content.parts
|
||||||
.filter((part) => part.text)
|
.filter((part) => part.text && !part.thought)
|
||||||
.map((part) => part.text)
|
.map((part) => part.text)
|
||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user