|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect } from 'vitest'; |
|
|
import { |
|
|
getResponseText, |
|
|
getResponseTextFromParts, |
|
|
getFunctionCalls, |
|
|
getFunctionCallsFromParts, |
|
|
getFunctionCallsAsJson, |
|
|
getFunctionCallsFromPartsAsJson, |
|
|
getStructuredResponse, |
|
|
getStructuredResponseFromParts, |
|
|
} from './generateContentResponseUtilities.js'; |
|
|
import { |
|
|
GenerateContentResponse, |
|
|
Part, |
|
|
FinishReason, |
|
|
SafetyRating, |
|
|
} from '@google/genai'; |
|
|
|
|
|
const mockTextPart = (text: string): Part => ({ text }); |
|
|
const mockFunctionCallPart = ( |
|
|
name: string, |
|
|
args?: Record<string, unknown>, |
|
|
): Part => ({ |
|
|
functionCall: { name, args: args ?? {} }, |
|
|
}); |
|
|
|
|
|
const mockResponse = ( |
|
|
parts: Part[], |
|
|
finishReason: FinishReason = FinishReason.STOP, |
|
|
safetyRatings: SafetyRating[] = [], |
|
|
): GenerateContentResponse => ({ |
|
|
candidates: [ |
|
|
{ |
|
|
content: { |
|
|
parts, |
|
|
role: 'model', |
|
|
}, |
|
|
index: 0, |
|
|
finishReason, |
|
|
safetyRatings, |
|
|
}, |
|
|
], |
|
|
promptFeedback: { |
|
|
safetyRatings: [], |
|
|
}, |
|
|
text: undefined, |
|
|
data: undefined, |
|
|
functionCalls: undefined, |
|
|
executableCode: undefined, |
|
|
codeExecutionResult: undefined, |
|
|
}); |
|
|
|
|
|
const minimalMockResponse = ( |
|
|
candidates: GenerateContentResponse['candidates'], |
|
|
): GenerateContentResponse => ({ |
|
|
candidates, |
|
|
promptFeedback: { safetyRatings: [] }, |
|
|
text: undefined, |
|
|
data: undefined, |
|
|
functionCalls: undefined, |
|
|
executableCode: undefined, |
|
|
codeExecutionResult: undefined, |
|
|
}); |
|
|
|
|
|
describe('generateContentResponseUtilities', () => { |
|
|
describe('getResponseText', () => { |
|
|
it('should return undefined for no candidates', () => { |
|
|
expect(getResponseText(minimalMockResponse(undefined))).toBeUndefined(); |
|
|
}); |
|
|
it('should return undefined for empty candidates array', () => { |
|
|
expect(getResponseText(minimalMockResponse([]))).toBeUndefined(); |
|
|
}); |
|
|
it('should return undefined for no parts', () => { |
|
|
const response = mockResponse([]); |
|
|
expect(getResponseText(response)).toBeUndefined(); |
|
|
}); |
|
|
it('should extract text from a single text part', () => { |
|
|
const response = mockResponse([mockTextPart('Hello')]); |
|
|
expect(getResponseText(response)).toBe('Hello'); |
|
|
}); |
|
|
it('should concatenate text from multiple text parts', () => { |
|
|
const response = mockResponse([ |
|
|
mockTextPart('Hello '), |
|
|
mockTextPart('World'), |
|
|
]); |
|
|
expect(getResponseText(response)).toBe('Hello World'); |
|
|
}); |
|
|
it('should ignore function call parts', () => { |
|
|
const response = mockResponse([ |
|
|
mockTextPart('Hello '), |
|
|
mockFunctionCallPart('testFunc'), |
|
|
mockTextPart('World'), |
|
|
]); |
|
|
expect(getResponseText(response)).toBe('Hello World'); |
|
|
}); |
|
|
it('should return undefined if only function call parts exist', () => { |
|
|
const response = mockResponse([ |
|
|
mockFunctionCallPart('testFunc'), |
|
|
mockFunctionCallPart('anotherFunc'), |
|
|
]); |
|
|
expect(getResponseText(response)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getResponseTextFromParts', () => { |
|
|
it('should return undefined for no parts', () => { |
|
|
expect(getResponseTextFromParts([])).toBeUndefined(); |
|
|
}); |
|
|
it('should extract text from a single text part', () => { |
|
|
expect(getResponseTextFromParts([mockTextPart('Hello')])).toBe('Hello'); |
|
|
}); |
|
|
it('should concatenate text from multiple text parts', () => { |
|
|
expect( |
|
|
getResponseTextFromParts([ |
|
|
mockTextPart('Hello '), |
|
|
mockTextPart('World'), |
|
|
]), |
|
|
).toBe('Hello World'); |
|
|
}); |
|
|
it('should ignore function call parts', () => { |
|
|
expect( |
|
|
getResponseTextFromParts([ |
|
|
mockTextPart('Hello '), |
|
|
mockFunctionCallPart('testFunc'), |
|
|
mockTextPart('World'), |
|
|
]), |
|
|
).toBe('Hello World'); |
|
|
}); |
|
|
it('should return undefined if only function call parts exist', () => { |
|
|
expect( |
|
|
getResponseTextFromParts([ |
|
|
mockFunctionCallPart('testFunc'), |
|
|
mockFunctionCallPart('anotherFunc'), |
|
|
]), |
|
|
).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getFunctionCalls', () => { |
|
|
it('should return undefined for no candidates', () => { |
|
|
expect(getFunctionCalls(minimalMockResponse(undefined))).toBeUndefined(); |
|
|
}); |
|
|
it('should return undefined for empty candidates array', () => { |
|
|
expect(getFunctionCalls(minimalMockResponse([]))).toBeUndefined(); |
|
|
}); |
|
|
it('should return undefined for no parts', () => { |
|
|
const response = mockResponse([]); |
|
|
expect(getFunctionCalls(response)).toBeUndefined(); |
|
|
}); |
|
|
it('should extract a single function call', () => { |
|
|
const func = { name: 'testFunc', args: { a: 1 } }; |
|
|
const response = mockResponse([ |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
]); |
|
|
expect(getFunctionCalls(response)).toEqual([func]); |
|
|
}); |
|
|
it('should extract multiple function calls', () => { |
|
|
const func1 = { name: 'testFunc1', args: { a: 1 } }; |
|
|
const func2 = { name: 'testFunc2', args: { b: 2 } }; |
|
|
const response = mockResponse([ |
|
|
mockFunctionCallPart(func1.name, func1.args), |
|
|
mockFunctionCallPart(func2.name, func2.args), |
|
|
]); |
|
|
expect(getFunctionCalls(response)).toEqual([func1, func2]); |
|
|
}); |
|
|
it('should ignore text parts', () => { |
|
|
const func = { name: 'testFunc', args: { a: 1 } }; |
|
|
const response = mockResponse([ |
|
|
mockTextPart('Some text'), |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
mockTextPart('More text'), |
|
|
]); |
|
|
expect(getFunctionCalls(response)).toEqual([func]); |
|
|
}); |
|
|
it('should return undefined if only text parts exist', () => { |
|
|
const response = mockResponse([ |
|
|
mockTextPart('Some text'), |
|
|
mockTextPart('More text'), |
|
|
]); |
|
|
expect(getFunctionCalls(response)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getFunctionCallsFromParts', () => { |
|
|
it('should return undefined for no parts', () => { |
|
|
expect(getFunctionCallsFromParts([])).toBeUndefined(); |
|
|
}); |
|
|
it('should extract a single function call', () => { |
|
|
const func = { name: 'testFunc', args: { a: 1 } }; |
|
|
expect( |
|
|
getFunctionCallsFromParts([mockFunctionCallPart(func.name, func.args)]), |
|
|
).toEqual([func]); |
|
|
}); |
|
|
it('should extract multiple function calls', () => { |
|
|
const func1 = { name: 'testFunc1', args: { a: 1 } }; |
|
|
const func2 = { name: 'testFunc2', args: { b: 2 } }; |
|
|
expect( |
|
|
getFunctionCallsFromParts([ |
|
|
mockFunctionCallPart(func1.name, func1.args), |
|
|
mockFunctionCallPart(func2.name, func2.args), |
|
|
]), |
|
|
).toEqual([func1, func2]); |
|
|
}); |
|
|
it('should ignore text parts', () => { |
|
|
const func = { name: 'testFunc', args: { a: 1 } }; |
|
|
expect( |
|
|
getFunctionCallsFromParts([ |
|
|
mockTextPart('Some text'), |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
mockTextPart('More text'), |
|
|
]), |
|
|
).toEqual([func]); |
|
|
}); |
|
|
it('should return undefined if only text parts exist', () => { |
|
|
expect( |
|
|
getFunctionCallsFromParts([ |
|
|
mockTextPart('Some text'), |
|
|
mockTextPart('More text'), |
|
|
]), |
|
|
).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getFunctionCallsAsJson', () => { |
|
|
it('should return JSON string of function calls', () => { |
|
|
const func1 = { name: 'testFunc1', args: { a: 1 } }; |
|
|
const func2 = { name: 'testFunc2', args: { b: 2 } }; |
|
|
const response = mockResponse([ |
|
|
mockFunctionCallPart(func1.name, func1.args), |
|
|
mockTextPart('text in between'), |
|
|
mockFunctionCallPart(func2.name, func2.args), |
|
|
]); |
|
|
const expectedJson = JSON.stringify([func1, func2], null, 2); |
|
|
expect(getFunctionCallsAsJson(response)).toBe(expectedJson); |
|
|
}); |
|
|
it('should return undefined if no function calls', () => { |
|
|
const response = mockResponse([mockTextPart('Hello')]); |
|
|
expect(getFunctionCallsAsJson(response)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getFunctionCallsFromPartsAsJson', () => { |
|
|
it('should return JSON string of function calls from parts', () => { |
|
|
const func1 = { name: 'testFunc1', args: { a: 1 } }; |
|
|
const func2 = { name: 'testFunc2', args: { b: 2 } }; |
|
|
const parts = [ |
|
|
mockFunctionCallPart(func1.name, func1.args), |
|
|
mockTextPart('text in between'), |
|
|
mockFunctionCallPart(func2.name, func2.args), |
|
|
]; |
|
|
const expectedJson = JSON.stringify([func1, func2], null, 2); |
|
|
expect(getFunctionCallsFromPartsAsJson(parts)).toBe(expectedJson); |
|
|
}); |
|
|
it('should return undefined if no function calls in parts', () => { |
|
|
const parts = [mockTextPart('Hello')]; |
|
|
expect(getFunctionCallsFromPartsAsJson(parts)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getStructuredResponse', () => { |
|
|
it('should return only text if only text exists', () => { |
|
|
const response = mockResponse([mockTextPart('Hello World')]); |
|
|
expect(getStructuredResponse(response)).toBe('Hello World'); |
|
|
}); |
|
|
it('should return only function call JSON if only function calls exist', () => { |
|
|
const func = { name: 'testFunc', args: { data: 'payload' } }; |
|
|
const response = mockResponse([ |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
]); |
|
|
const expectedJson = JSON.stringify([func], null, 2); |
|
|
expect(getStructuredResponse(response)).toBe(expectedJson); |
|
|
}); |
|
|
it('should return text and function call JSON if both exist', () => { |
|
|
const text = 'Consider this data:'; |
|
|
const func = { name: 'processData', args: { item: 42 } }; |
|
|
const response = mockResponse([ |
|
|
mockTextPart(text), |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
]); |
|
|
const expectedJson = JSON.stringify([func], null, 2); |
|
|
expect(getStructuredResponse(response)).toBe(`${text}\n${expectedJson}`); |
|
|
}); |
|
|
it('should return undefined if neither text nor function calls exist', () => { |
|
|
const response = mockResponse([]); |
|
|
expect(getStructuredResponse(response)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
describe('getStructuredResponseFromParts', () => { |
|
|
it('should return only text if only text exists in parts', () => { |
|
|
const parts = [mockTextPart('Hello World')]; |
|
|
expect(getStructuredResponseFromParts(parts)).toBe('Hello World'); |
|
|
}); |
|
|
it('should return only function call JSON if only function calls exist in parts', () => { |
|
|
const func = { name: 'testFunc', args: { data: 'payload' } }; |
|
|
const parts = [mockFunctionCallPart(func.name, func.args)]; |
|
|
const expectedJson = JSON.stringify([func], null, 2); |
|
|
expect(getStructuredResponseFromParts(parts)).toBe(expectedJson); |
|
|
}); |
|
|
it('should return text and function call JSON if both exist in parts', () => { |
|
|
const text = 'Consider this data:'; |
|
|
const func = { name: 'processData', args: { item: 42 } }; |
|
|
const parts = [ |
|
|
mockTextPart(text), |
|
|
mockFunctionCallPart(func.name, func.args), |
|
|
]; |
|
|
const expectedJson = JSON.stringify([func], null, 2); |
|
|
expect(getStructuredResponseFromParts(parts)).toBe( |
|
|
`${text}\n${expectedJson}`, |
|
|
); |
|
|
}); |
|
|
it('should return undefined if neither text nor function calls exist in parts', () => { |
|
|
const parts: Part[] = []; |
|
|
expect(getStructuredResponseFromParts(parts)).toBeUndefined(); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|