Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/two-queens-boil.md
Original file line number Diff line number Diff line change
@@ -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`.
1 change: 1 addition & 0 deletions docs/reference/files/generative-ai.filemetadataresponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |

11 changes: 11 additions & 0 deletions docs/reference/files/generative-ai.filemetadataresponse.state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@google/generative-ai](./generative-ai.md) &gt; [FileMetadataResponse](./generative-ai.filemetadataresponse.md) &gt; [state](./generative-ai.filemetadataresponse.state.md)

## FileMetadataResponse.state property

**Signature:**

```typescript
state: FileState;
```
23 changes: 23 additions & 0 deletions docs/reference/files/generative-ai.filestate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@google/generative-ai](./generative-ai.md) &gt; [FileState](./generative-ai.filestate.md)

## FileState enum

Processing state of the `File`<!-- -->.

**Signature:**

```typescript
export declare enum FileState
```

## Enumeration Members

| Member | Value | Description |
| --- | --- | --- |
| ACTIVE | <code>2</code> | |
| FAILED | <code>10</code> | |
| PROCESSING | <code>1</code> | |
| STATE\_UNSPECIFIED | <code>0</code> | |

6 changes: 6 additions & 0 deletions docs/reference/files/generative-ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>File</code>. |

## Interfaces

| Interface | Description |
Expand Down
16 changes: 16 additions & 0 deletions packages/main/src/files/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface FileMetadataResponse {
expirationTime: string;
sha256Hash: string;
uri: string;
state: FileState;
}

/**
Expand All @@ -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,
}
125 changes: 90 additions & 35 deletions packages/main/src/requests/response-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
],
},
},
Expand All @@ -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);
Expand Down
16 changes: 11 additions & 5 deletions packages/main/src/requests/response-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 "";
}
Expand Down