Skip to content

Commit 9e95663

Browse files
authoredFeb 28, 2025··
Fix chat history to exclude content with empty parts (#362)
Fixes #217
1 parent be83756 commit 9e95663

File tree

4 files changed

+130
-14
lines changed

4 files changed

+130
-14
lines changed
 

‎.changeset/icy-waves-attend.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@google/generative-ai": patch
3+
---
4+
5+
fix: Exclude content with empty parts from chat history

‎src/methods/chat-session-helpers.test.ts

+91-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { validateChatHistory } from "./chat-session-helpers";
18+
import { isValidResponse, validateChatHistory } from "./chat-session-helpers";
1919
import { expect } from "chai";
20-
import { Content } from "../../types";
20+
import { Content, GenerateContentResponse } from "../../types";
2121
import { GoogleGenerativeAIError } from "../errors";
2222

2323
describe("chat-session-helpers", () => {
@@ -167,4 +167,93 @@ describe("chat-session-helpers", () => {
167167
});
168168
});
169169
});
170+
describe("isValidResponse", () => {
171+
const testCases = [
172+
{ name: "default response", response: {}, isValid: false },
173+
{
174+
name: "empty candidates",
175+
response: { candidates: [] } as GenerateContentResponse,
176+
isValid: false,
177+
},
178+
{
179+
name: "default candidates",
180+
response: { candidates: [{}] } as GenerateContentResponse,
181+
isValid: false,
182+
},
183+
{
184+
name: "default content",
185+
response: { candidates: [{ content: {} }] } as GenerateContentResponse,
186+
isValid: false,
187+
},
188+
{
189+
name: "empty parts",
190+
response: {
191+
candidates: [{ content: { parts: [] } }],
192+
} as GenerateContentResponse,
193+
isValid: false,
194+
},
195+
{
196+
name: "default part",
197+
response: {
198+
candidates: [{ content: { parts: [{}] } }],
199+
} as GenerateContentResponse,
200+
isValid: false,
201+
},
202+
{
203+
name: "part with empty text",
204+
response: {
205+
candidates: [{ content: { parts: [{ text: "" }] } }],
206+
} as GenerateContentResponse,
207+
isValid: false,
208+
},
209+
{
210+
name: "part with text",
211+
response: {
212+
candidates: [{ content: { parts: [{ text: "hello" }] } }],
213+
} as GenerateContentResponse,
214+
isValid: true,
215+
},
216+
{
217+
name: "part with function call",
218+
response: {
219+
candidates: [
220+
{
221+
content: {
222+
parts: [
223+
{ functionCall: { name: "foo", args: { input: "bar" } } },
224+
],
225+
},
226+
},
227+
],
228+
} as GenerateContentResponse,
229+
isValid: true,
230+
},
231+
{
232+
name: "part with function response",
233+
response: {
234+
candidates: [
235+
{
236+
content: {
237+
parts: [
238+
{
239+
functionResponse: {
240+
name: "foo",
241+
response: { output: "bar" },
242+
},
243+
},
244+
],
245+
},
246+
},
247+
],
248+
} as GenerateContentResponse,
249+
isValid: true,
250+
},
251+
];
252+
253+
testCases.forEach((testCase) => {
254+
it(testCase.name, () => {
255+
expect(isValidResponse(testCase.response)).to.eq(testCase.isValid);
256+
});
257+
});
258+
});
170259
});

‎src/methods/chat-session-helpers.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Content, POSSIBLE_ROLES, Part } from "../../types";
18+
import {
19+
Content,
20+
GenerateContentResponse,
21+
POSSIBLE_ROLES,
22+
Part,
23+
} from "../../types";
1924
import { GoogleGenerativeAIError } from "../errors";
2025

2126
type Role = (typeof POSSIBLE_ROLES)[number];
@@ -97,3 +102,28 @@ export function validateChatHistory(history: Content[]): void {
97102
prevContent = true;
98103
}
99104
}
105+
106+
/**
107+
* Returns true if the response is valid (could be appended to the history), flase otherwise.
108+
*/
109+
export function isValidResponse(response: GenerateContentResponse): boolean {
110+
if (response.candidates === undefined || response.candidates.length === 0) {
111+
return false;
112+
}
113+
const content = response.candidates[0]?.content;
114+
if (content === undefined) {
115+
return false;
116+
}
117+
if (content.parts === undefined || content.parts.length === 0) {
118+
return false;
119+
}
120+
for (const part of content.parts) {
121+
if (part === undefined || Object.keys(part).length === 0) {
122+
return false;
123+
}
124+
if (part.text !== undefined && part.text === "") {
125+
return false;
126+
}
127+
}
128+
return true;
129+
}

‎src/methods/chat-session.ts

+3-11
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from "../../types";
2828
import { formatNewContent } from "../requests/request-helpers";
2929
import { formatBlockErrorMessage } from "../requests/response-helpers";
30-
import { validateChatHistory } from "./chat-session-helpers";
30+
import { isValidResponse, validateChatHistory } from "./chat-session-helpers";
3131
import { generateContent, generateContentStream } from "./generate-content";
3232

3333
/**
@@ -108,11 +108,7 @@ export class ChatSession {
108108
),
109109
)
110110
.then((result) => {
111-
if (
112-
result.response.candidates &&
113-
result.response.candidates.length > 0 &&
114-
result.response.candidates[0]?.content !== undefined
115-
) {
111+
if (isValidResponse(result.response)) {
116112
this._history.push(newContent);
117113
const responseContent: Content = {
118114
parts: [],
@@ -180,11 +176,7 @@ export class ChatSession {
180176
})
181177
.then((streamResult) => streamResult.response)
182178
.then((response) => {
183-
if (
184-
response.candidates &&
185-
response.candidates.length > 0 &&
186-
response.candidates[0]?.content !== undefined
187-
) {
179+
if (isValidResponse(response)) {
188180
this._history.push(newContent);
189181
const responseContent = { ...response.candidates[0].content };
190182
// Response seems to come back without a role set.

0 commit comments

Comments
 (0)
Please sign in to comment.