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 "";
}