diff --git a/.changeset/two-queens-boil.md b/.changeset/two-queens-boil.md new file mode 100644 index 000000000..82fc08a28 --- /dev/null +++ b/.changeset/two-queens-boil.md @@ -0,0 +1,5 @@ +--- +"@google/generative-ai": patch +--- + +Fixed a bug where `text()` did not handle multiple `TextPart`s in a single candidate. Added `state` field to `FileMetadataResponse`. diff --git a/docs/reference/files/generative-ai.filemetadataresponse.md b/docs/reference/files/generative-ai.filemetadataresponse.md index 686250698..337f5ae1a 100644 --- a/docs/reference/files/generative-ai.filemetadataresponse.md +++ b/docs/reference/files/generative-ai.filemetadataresponse.md @@ -23,6 +23,7 @@ export interface FileMetadataResponse | [name](./generative-ai.filemetadataresponse.name.md) | | string | | | [sha256Hash](./generative-ai.filemetadataresponse.sha256hash.md) | | string | | | [sizeBytes](./generative-ai.filemetadataresponse.sizebytes.md) | | string | | +| [state](./generative-ai.filemetadataresponse.state.md) | | [FileState](./generative-ai.filestate.md) | | | [updateTime](./generative-ai.filemetadataresponse.updatetime.md) | | string | | | [uri](./generative-ai.filemetadataresponse.uri.md) | | string | | diff --git a/docs/reference/files/generative-ai.filemetadataresponse.state.md b/docs/reference/files/generative-ai.filemetadataresponse.state.md new file mode 100644 index 000000000..4d92e589e --- /dev/null +++ b/docs/reference/files/generative-ai.filemetadataresponse.state.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@google/generative-ai](./generative-ai.md) > [FileMetadataResponse](./generative-ai.filemetadataresponse.md) > [state](./generative-ai.filemetadataresponse.state.md) + +## FileMetadataResponse.state property + +**Signature:** + +```typescript +state: FileState; +``` diff --git a/docs/reference/files/generative-ai.filestate.md b/docs/reference/files/generative-ai.filestate.md new file mode 100644 index 000000000..b82429520 --- /dev/null +++ b/docs/reference/files/generative-ai.filestate.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@google/generative-ai](./generative-ai.md) > [FileState](./generative-ai.filestate.md) + +## FileState enum + +Processing state of the `File`. + +**Signature:** + +```typescript +export declare enum FileState +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| ACTIVE | 2 | | +| FAILED | 10 | | +| PROCESSING | 1 | | +| STATE\_UNSPECIFIED | 0 | | + diff --git a/docs/reference/files/generative-ai.md b/docs/reference/files/generative-ai.md index 0daf824f5..d23363cb3 100644 --- a/docs/reference/files/generative-ai.md +++ b/docs/reference/files/generative-ai.md @@ -10,6 +10,12 @@ | --- | --- | | [GoogleAIFileManager](./generative-ai.googleaifilemanager.md) | Class for managing GoogleAI file uploads. | +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [FileState](./generative-ai.filestate.md) | Processing state of the File. | + ## Interfaces | Interface | Description | diff --git a/packages/main/src/files/types.ts b/packages/main/src/files/types.ts index 5378c11bc..14d83987f 100644 --- a/packages/main/src/files/types.ts +++ b/packages/main/src/files/types.ts @@ -48,6 +48,7 @@ export interface FileMetadataResponse { expirationTime: string; sha256Hash: string; uri: string; + state: FileState; } /** @@ -66,3 +67,18 @@ export interface ListFilesResponse { export interface UploadFileResponse { file: FileMetadataResponse; } + +/** + * Processing state of the `File`. + * @public + */ +export enum FileState { + // The default value. This value is used if the state is omitted. + STATE_UNSPECIFIED = 0, + // File is being processed and cannot be used for inference yet. + PROCESSING = 1, + // File is processed and available for inference. + ACTIVE = 2, + // File failed processing. + FAILED = 10, +} diff --git a/packages/main/src/requests/response-helpers.test.ts b/packages/main/src/requests/response-helpers.test.ts index ec7a430dc..69bf5fc72 100644 --- a/packages/main/src/requests/response-helpers.test.ts +++ b/packages/main/src/requests/response-helpers.test.ts @@ -40,54 +40,85 @@ const fakeResponseText: GenerateContentResponse = { ], }; +const functionCallPart1 = { + functionCall: { + name: "find_theaters", + args: { + location: "Mountain View, CA", + movie: "Barbie", + }, + }, +}; + +const functionCallPart2 = { + functionCall: { + name: "find_times", + args: { + location: "Mountain View, CA", + movie: "Barbie", + time: "20:00", + }, + }, +}; + const fakeResponseFunctionCall: GenerateContentResponse = { candidates: [ { index: 0, content: { role: "model", - parts: [ - { - functionCall: { - name: "find_theaters", - args: { - location: "Mountain View, CA", - movie: "Barbie", - }, - }, - }, - ], + parts: [functionCallPart1], }, }, ], }; const fakeResponseFunctionCalls: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: "model", + parts: [functionCallPart1, functionCallPart2], + }, + }, + ], +}; + +const fakeResponseMixed1: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: "model", + parts: [{ text: "some text" }, functionCallPart2], + }, + }, + ], +}; + +const fakeResponseMixed2: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: "model", + parts: [functionCallPart1, { text: "some text" }], + }, + }, + ], +}; + +const fakeResponseMixed3: GenerateContentResponse = { candidates: [ { index: 0, content: { role: "model", parts: [ - { - functionCall: { - name: "find_theaters", - args: { - location: "Mountain View, CA", - movie: "Barbie", - }, - }, - }, - { - functionCall: { - name: "find_times", - args: { - location: "Mountain View, CA", - movie: "Barbie", - time: "20:00", - }, - }, - }, + { text: "some text" }, + functionCallPart1, + { text: " and more text" }, ], }, }, @@ -109,19 +140,43 @@ describe("response-helpers methods", () => { it("good response text", async () => { const enhancedResponse = addHelpers(fakeResponseText); expect(enhancedResponse.text()).to.equal("Some text and some more text"); + expect(enhancedResponse.functionCalls()).to.be.undefined; }); it("good response functionCall", async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCall); - expect(enhancedResponse.functionCall()).to.deep.equal( - fakeResponseFunctionCall.candidates[0].content.parts[0].functionCall, - ); + expect(enhancedResponse.text()).to.equal(""); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall, + ]); }); it("good response functionCalls", async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCalls); + expect(enhancedResponse.text()).to.equal(""); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall, + functionCallPart2.functionCall, + ]); + }); + it("good response text/functionCall", async () => { + const enhancedResponse = addHelpers(fakeResponseMixed1); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart2.functionCall, + ]); + expect(enhancedResponse.text()).to.equal("some text"); + }); + it("good response functionCall/text", async () => { + const enhancedResponse = addHelpers(fakeResponseMixed2); + expect(enhancedResponse.functionCalls()).to.deep.equal([ + functionCallPart1.functionCall, + ]); + expect(enhancedResponse.text()).to.equal("some text"); + }); + it("good response text/functionCall/text", async () => { + const enhancedResponse = addHelpers(fakeResponseMixed3); expect(enhancedResponse.functionCalls()).to.deep.equal([ - fakeResponseFunctionCalls.candidates[0].content.parts[0].functionCall, - fakeResponseFunctionCalls.candidates[0].content.parts[1].functionCall, + functionCallPart1.functionCall, ]); + expect(enhancedResponse.text()).to.equal("some text and more text"); }); it("bad response safety", async () => { const enhancedResponse = addHelpers(badFakeResponse); diff --git a/packages/main/src/requests/response-helpers.ts b/packages/main/src/requests/response-helpers.ts index 3621d0226..ef3877ec8 100644 --- a/packages/main/src/requests/response-helpers.ts +++ b/packages/main/src/requests/response-helpers.ts @@ -114,13 +114,19 @@ export function addHelpers( } /** - * Returns text of first candidate. + * Returns all text found in all parts of first candidate. */ export function getText(response: GenerateContentResponse): string { - if (response.candidates?.[0].content?.parts?.[0]?.text) { - return response.candidates[0].content.parts - .map(({ text }) => text) - .join(""); + const textStrings = []; + if (response.candidates?.[0].content?.parts) { + for (const part of response.candidates?.[0].content?.parts) { + if (part.text) { + textStrings.push(part.text); + } + } + } + if (textStrings.length > 0) { + return textStrings.join(""); } else { return ""; }