Skip to content

Commit 364370e

Browse files
committed
Add logic to account for thought and thoughtSignature in chat history
1 parent 3f62f8a commit 364370e

File tree

5 files changed

+143
-7
lines changed

5 files changed

+143
-7
lines changed

common/api-review/ai.api.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ export interface FileDataPart {
242242
text?: never;
243243
// (undocumented)
244244
thought?: boolean;
245+
// @internal (undocumented)
246+
thoughtSignature?: never;
245247
}
246248

247249
// @public
@@ -298,6 +300,8 @@ export interface FunctionCallPart {
298300
text?: never;
299301
// (undocumented)
300302
thought?: boolean;
303+
// @internal (undocumented)
304+
thoughtSignature?: never;
301305
}
302306

303307
// @public
@@ -332,6 +336,8 @@ export interface FunctionResponsePart {
332336
text?: never;
333337
// (undocumented)
334338
thought?: boolean;
339+
// @internal (undocumented)
340+
thoughtSignature?: never;
335341
}
336342

337343
// @public
@@ -699,6 +705,8 @@ export interface InlineDataPart {
699705
text?: never;
700706
// (undocumented)
701707
thought?: boolean;
708+
// @internal (undocumented)
709+
thoughtSignature?: never;
702710
videoMetadata?: VideoMetadata;
703711
}
704712

@@ -967,6 +975,8 @@ export interface TextPart {
967975
text: string;
968976
// (undocumented)
969977
thought?: boolean;
978+
// @internal (undocumented)
979+
thoughtSignature?: string;
970980
}
971981

972982
// @public

packages/ai/src/methods/chat-session-helpers.test.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import { FirebaseError } from '@firebase/util';
2222

2323
describe('chat-session-helpers', () => {
2424
describe('validateChatHistory', () => {
25-
const TCS: Array<{ history: Content[]; isValid: boolean }> = [
25+
const TCS: Array<{
26+
history: Content[];
27+
isValid: boolean;
28+
errorShouldInclude?: string;
29+
}> = [
2630
{
2731
history: [{ role: 'user', parts: [{ text: 'hi' }] }],
2832
isValid: true
@@ -99,19 +103,23 @@ describe('chat-session-helpers', () => {
99103
{
100104
//@ts-expect-error
101105
history: [{ role: 'user', parts: '' }],
106+
errorShouldInclude: `array of Parts`,
102107
isValid: false
103108
},
104109
{
105110
//@ts-expect-error
106111
history: [{ role: 'user' }],
112+
errorShouldInclude: `array of Parts`,
107113
isValid: false
108114
},
109115
{
110116
history: [{ role: 'user', parts: [] }],
117+
errorShouldInclude: `at least one part`,
111118
isValid: false
112119
},
113120
{
114121
history: [{ role: 'model', parts: [{ text: 'hi' }] }],
122+
errorShouldInclude: `model`,
115123
isValid: false
116124
},
117125
{
@@ -125,13 +133,15 @@ describe('chat-session-helpers', () => {
125133
]
126134
}
127135
],
136+
errorShouldInclude: `function`,
128137
isValid: false
129138
},
130139
{
131140
history: [
132141
{ role: 'user', parts: [{ text: 'hi' }] },
133142
{ role: 'user', parts: [{ text: 'hi' }] }
134143
],
144+
errorShouldInclude: `can't follow 'user'`,
135145
isValid: false
136146
},
137147
{
@@ -140,6 +150,45 @@ describe('chat-session-helpers', () => {
140150
{ role: 'model', parts: [{ text: 'hi' }] },
141151
{ role: 'model', parts: [{ text: 'hi' }] }
142152
],
153+
errorShouldInclude: `can't follow 'model'`,
154+
isValid: false
155+
},
156+
{
157+
history: [
158+
{ role: 'user', parts: [{ text: 'hi' }] },
159+
{
160+
role: 'model',
161+
parts: [
162+
{ text: 'hi' },
163+
{
164+
text: 'thought about hi',
165+
thought: true,
166+
thoughtSignature: 'thought signature'
167+
}
168+
]
169+
}
170+
],
171+
isValid: true
172+
},
173+
{
174+
history: [
175+
{
176+
role: 'user',
177+
parts: [{ text: 'hi', thought: true, thoughtSignature: 'sig' }]
178+
},
179+
{
180+
role: 'model',
181+
parts: [
182+
{ text: 'hi' },
183+
{
184+
text: 'thought about hi',
185+
thought: true,
186+
thoughtSignature: 'thought signature'
187+
}
188+
]
189+
}
190+
],
191+
errorShouldInclude: 'thought',
143192
isValid: false
144193
}
145194
];
@@ -149,7 +198,14 @@ describe('chat-session-helpers', () => {
149198
if (tc.isValid) {
150199
expect(fn).to.not.throw();
151200
} else {
152-
expect(fn).to.throw(FirebaseError);
201+
try {
202+
fn();
203+
} catch (e) {
204+
expect(e).to.be.instanceOf(FirebaseError);
205+
if (e instanceof FirebaseError && tc.errorShouldInclude) {
206+
expect(e.message).to.include(tc.errorShouldInclude);
207+
}
208+
}
153209
}
154210
});
155211
});

packages/ai/src/methods/chat-session-helpers.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ const VALID_PART_FIELDS: Array<keyof Part> = [
2424
'text',
2525
'inlineData',
2626
'functionCall',
27-
'functionResponse'
27+
'functionResponse',
28+
'thought',
29+
'thoughtSignature'
2830
];
2931

3032
const VALID_PARTS_PER_ROLE: { [key in Role]: Array<keyof Part> } = {
3133
user: ['text', 'inlineData'],
3234
function: ['functionResponse'],
33-
model: ['text', 'functionCall'],
35+
model: ['text', 'functionCall', 'thought', 'thoughtSignature'],
3436
// System instructions shouldn't be in history anyway.
3537
system: ['text']
3638
};
@@ -65,7 +67,7 @@ export function validateChatHistory(history: Content[]): void {
6567
if (!Array.isArray(parts)) {
6668
throw new AIError(
6769
AIErrorCode.INVALID_CONTENT,
68-
`Content should have 'parts' but property with an array of Parts`
70+
`Content should have 'parts' property with an array of Parts`
6971
);
7072
}
7173

@@ -81,7 +83,8 @@ export function validateChatHistory(history: Content[]): void {
8183
inlineData: 0,
8284
functionCall: 0,
8385
functionResponse: 0,
84-
thought: 0
86+
thought: 0,
87+
thoughtSignature: 0
8588
};
8689

8790
for (const part of parts) {

packages/ai/src/methods/chat-session.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { match, restore, stub, useFakeTimers } from 'sinon';
2020
import sinonChai from 'sinon-chai';
2121
import chaiAsPromised from 'chai-as-promised';
2222
import * as generateContentMethods from './generate-content';
23-
import { GenerateContentStreamResult } from '../types';
23+
import { Content, GenerateContentStreamResult } from '../types';
2424
import { ChatSession } from './chat-session';
2525
import { ApiSettings } from '../types/internal';
2626
import { VertexAIBackend } from '../backend';
@@ -54,6 +54,53 @@ describe('ChatSession', () => {
5454
match.any
5555
);
5656
});
57+
it('adds message and response to history', async () => {
58+
const fakeContent: Content = {
59+
role: 'model',
60+
parts: [
61+
{ text: 'hi' },
62+
{
63+
text: 'thought about hi',
64+
thoughtSignature: 'thought signature'
65+
}
66+
]
67+
};
68+
const fakeResponse = {
69+
candidates: [
70+
{
71+
index: 1,
72+
content: fakeContent
73+
}
74+
]
75+
};
76+
const generateContentStub = stub(
77+
generateContentMethods,
78+
'generateContent'
79+
).resolves({
80+
// @ts-ignore
81+
response: fakeResponse
82+
});
83+
const chatSession = new ChatSession(fakeApiSettings, 'a-model');
84+
const result = await chatSession.sendMessage('hello');
85+
// @ts-ignore
86+
expect(result.response).to.equal(fakeResponse);
87+
// Test: stores history correctly?
88+
const history = await chatSession.getHistory();
89+
expect(history[0].role).to.equal('user');
90+
expect(history[0].parts[0].text).to.equal('hello');
91+
expect(history[1]).to.deep.equal(fakeResponse.candidates[0].content);
92+
// Test: sends history correctly?
93+
await chatSession.sendMessage('hello 2');
94+
expect(generateContentStub.args[1][2].contents[0].parts[0].text).to.equal(
95+
'hello'
96+
);
97+
expect(generateContentStub.args[1][2].contents[1]).to.deep.equal(
98+
fakeResponse.candidates[0].content
99+
);
100+
expect(generateContentStub.args[1][2].contents[2].parts[0].text).to.equal(
101+
'hello 2'
102+
);
103+
});
57104
});
58105
describe('sendMessageStream()', () => {
59106
it('generateContentStream errors should be catchable', async () => {

packages/ai/src/types/content.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export interface TextPart {
4848
functionCall?: never;
4949
functionResponse?: never;
5050
thought?: boolean;
51+
/**
52+
* @internal
53+
*/
54+
thoughtSignature?: string;
5155
}
5256

5357
/**
@@ -64,6 +68,10 @@ export interface InlineDataPart {
6468
*/
6569
videoMetadata?: VideoMetadata;
6670
thought?: boolean;
71+
/**
72+
* @internal
73+
*/
74+
thoughtSignature?: never;
6775
}
6876

6977
/**
@@ -93,6 +101,10 @@ export interface FunctionCallPart {
93101
functionCall: FunctionCall;
94102
functionResponse?: never;
95103
thought?: boolean;
104+
/**
105+
* @internal
106+
*/
107+
thoughtSignature?: never;
96108
}
97109

98110
/**
@@ -105,6 +117,10 @@ export interface FunctionResponsePart {
105117
functionCall?: never;
106118
functionResponse: FunctionResponse;
107119
thought?: boolean;
120+
/**
121+
* @internal
122+
*/
123+
thoughtSignature?: never;
108124
}
109125

110126
/**
@@ -118,6 +134,10 @@ export interface FileDataPart {
118134
functionResponse?: never;
119135
fileData: FileData;
120136
thought?: boolean;
137+
/**
138+
* @internal
139+
*/
140+
thoughtSignature?: never;
121141
}
122142

123143
/**

0 commit comments

Comments
 (0)