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 6 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/icy-waves-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@google/generative-ai": patch
---

fix: Exclude content with empty parts from chat history
58 changes: 56 additions & 2 deletions src/methods/chat-session-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
* limitations under the License.
*/

import { validateChatHistory } from "./chat-session-helpers";
import { isValidResponse, validateChatHistory } from "./chat-session-helpers";
import { expect } from "chai";
import { Content } from "../../types";
import { Content, GenerateContentResponse } from "../../types";
import { GoogleGenerativeAIError } from "../errors";

describe("chat-session-helpers", () => {
Expand Down Expand Up @@ -167,4 +167,58 @@ describe("chat-session-helpers", () => {
});
});
});
describe("isValidResponse", () => {
const testCases = [
{ name: "default response", response: {}, isValid: false },
{
name: "empty candidates",
response: { candidates: [] } as GenerateContentResponse,
isValid: false,
},
{
name: "default candidates",
response: { candidates: [{}] } as GenerateContentResponse,
isValid: false,
},
{
name: "default content",
response: { candidates: [{ content: {} }] } as GenerateContentResponse,
isValid: false,
},
{
name: "empty parts",
response: {
candidates: [{ content: { parts: [] } }],
} as GenerateContentResponse,
isValid: false,
},
{
name: "default part",
response: {
candidates: [{ content: { parts: [{}] } }],
} as GenerateContentResponse,
isValid: false,
},
{
name: "part with empty text",
response: {
candidates: [{ content: { parts: [{ text: "" }] } }],
} as GenerateContentResponse,
isValid: false,
},
{
name: "part with text",
response: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a case where text is not defined, put a function call or something, not that it doesn't work now, but to prevent future bugs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added more tests

candidates: [{ content: { parts: [{ text: "hello" }] } }],
} as GenerateContentResponse,
isValid: true,
},
];

testCases.forEach((testCase) => {
it(testCase.name, () => {
expect(isValidResponse(testCase.response)).to.eq(testCase.isValid);
});
});
});
});
32 changes: 31 additions & 1 deletion src/methods/chat-session-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
* limitations under the License.
*/

import { Content, POSSIBLE_ROLES, Part } from "../../types";
import {
Content,
GenerateContentResponse,
POSSIBLE_ROLES,
Part,
} from "../../types";
import { GoogleGenerativeAIError } from "../errors";

type Role = (typeof POSSIBLE_ROLES)[number];
Expand Down Expand Up @@ -97,3 +102,28 @@ export function validateChatHistory(history: Content[]): void {
prevContent = true;
}
}

/**
* Returns true if the response is valid (could be appended to the history), flase otherwise.
*/
export function isValidResponse(response: GenerateContentResponse): boolean {
if (response.candidates === undefined || response.candidates.length === 0) {
return false;
}
const content = response.candidates[0]?.content;
if (content === undefined) {
return false;
}
if (content.parts === undefined || content.parts.length === 0) {
return false;
}
for (const part of content.parts) {
if (part === undefined || Object.keys(part).length === 0) {
return false;
}
if (part.text !== undefined && part.text === "") {
return false;
}
}
return true;
}
14 changes: 3 additions & 11 deletions src/methods/chat-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from "../../types";
import { formatNewContent } from "../requests/request-helpers";
import { formatBlockErrorMessage } from "../requests/response-helpers";
import { validateChatHistory } from "./chat-session-helpers";
import { isValidResponse, validateChatHistory } from "./chat-session-helpers";
import { generateContent, generateContentStream } from "./generate-content";

/**
Expand Down Expand Up @@ -108,11 +108,7 @@ export class ChatSession {
),
)
.then((result) => {
if (
result.response.candidates &&
result.response.candidates.length > 0 &&
result.response.candidates[0]?.content !== undefined
) {
if (isValidResponse(result.response)) {
this._history.push(newContent);
const responseContent: Content = {
parts: [],
Expand Down Expand Up @@ -180,11 +176,7 @@ export class ChatSession {
})
.then((streamResult) => streamResult.response)
.then((response) => {
if (
response.candidates &&
response.candidates.length > 0 &&
response.candidates[0]?.content !== undefined
) {
if (isValidResponse(response)) {
this._history.push(newContent);
const responseContent = { ...response.candidates[0].content };
// Response seems to come back without a role set.
Expand Down
Loading