Skip to content

Commit 1633e80

Browse files
committed
clean up and add tests
1 parent 9f31324 commit 1633e80

File tree

5 files changed

+54
-19
lines changed

5 files changed

+54
-19
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"src/index.node.ts",
1818
"--timeout",
1919
"5000",
20-
"src/**/*.test.ts"
20+
"src/**/!(*-browser)*.test.ts"
2121
],
2222
"env": {
2323
"TS_NODE_COMPILER_OPTIONS": "{\"module\":\"commonjs\"}"

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,34 @@ describe('ChatSession', () => {
220220
);
221221
clock.restore();
222222
});
223+
it('error from stream promise should not be logged', async () => {
224+
const consoleStub = stub(console, 'error');
225+
stub(generateContentMethods, 'generateContentStream').rejects('foo');
226+
const chatSession = new ChatSession(
227+
fakeApiSettings,
228+
'a-model',
229+
fakeChromeAdapter
230+
);
231+
try {
232+
// This will throw since generateContentStream will reject immediately.
233+
await chatSession.sendMessageStream('hello');
234+
} catch (_) {}
235+
236+
expect(consoleStub).to.not.have.been.called;
237+
});
238+
it('error from final response promise should not be logged', async () => {
239+
const consoleStub = stub(console, 'error');
240+
stub(generateContentMethods, 'generateContentStream').resolves({
241+
response: new Promise((_, reject) => reject(new Error()))
242+
} as unknown as GenerateContentStreamResult);
243+
const chatSession = new ChatSession(
244+
fakeApiSettings,
245+
'a-model',
246+
fakeChromeAdapter
247+
);
248+
await chatSession.sendMessageStream('hello');
249+
expect(consoleStub).to.not.have.been.called;
250+
});
223251
it('singleRequestOptions overrides requestOptions', async () => {
224252
const generateContentStub = stub(
225253
generateContentMethods,

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ export class ChatSession {
156156
this._apiSettings,
157157
this.model,
158158
generateContentRequest,
159-
// Merge requestOptions
160159
this.chromeAdapter,
160+
// Merge requestOptions
161161
{
162162
...this.requestOptions,
163163
...singleRequestOptions
@@ -167,12 +167,20 @@ export class ChatSession {
167167
// Add onto the chain.
168168
this._sendPromise = this._sendPromise
169169
.then(() => streamPromise)
170-
// This must be handled to avoid unhandled rejection, but jump
171-
// to the final catch block with a label to not log this error.
170+
.then(streamResult => streamResult.response)
172171
.catch(_ignored => {
173172
throw new Error(SILENT_ERROR);
174173
})
175-
.then(streamResult => streamResult.response)
174+
// We want to log errors that the user cannot catch.
175+
// The user can catch all errors that are thrown from the `streamPromise` and the
176+
// `streamResult.response`, since these are returned to the user in the `GenerateContentResult`.
177+
// The user cannot catch errors that are thrown in the following `then` block, which appends
178+
// the model's response to the chat history.
179+
//
180+
// To prevent us from logging errors that the user *can* catch, we re-throw them as
181+
// SILENT_ERROR, then in the final `catch` block below, we only log errors that are not
182+
// SILENT_ERROR. There is currently no way for these errors to be propagated to the user,
183+
// so we log them to try to make up for this.
176184
.then(response => {
177185
if (response.candidates && response.candidates.length > 0) {
178186
this._history.push(newContent);
@@ -192,16 +200,7 @@ export class ChatSession {
192200
}
193201
})
194202
.catch(e => {
195-
// Errors in streamPromise are already catchable by the user as
196-
// streamPromise is returned.
197-
// Avoid duplicating the error message in logs.
198-
// AbortErrors are thrown after the initial streamPromise resolves, since the request
199-
// may be aborted once streaming has begun. Since these errors won't be wrapped in a SILENT_ERROR,
200-
// we have to explicitly check for them. The user will be able to catch these AbortErrors when
201-
// awaiting the resolution of the result.response.
202-
if (e.message !== SILENT_ERROR && e.name !== 'AbortError') {
203-
// Users do not have access to _sendPromise to catch errors
204-
// downstream from streamPromise, so they should not throw.
203+
if (e.message !== SILENT_ERROR) {
205204
logger.error(e);
206205
}
207206
});

packages/ai/src/methods/chrome-adapter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,8 @@ export function chromeAdapterFactory(
400400
// Do not initialize a ChromeAdapter if we are not in hybrid mode.
401401
if (typeof window !== 'undefined' && mode) {
402402
return new ChromeAdapterImpl(
403-
(window as Window).LanguageModel as LanguageModel,
403+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
404+
(window as unknown as any).LanguageModel as LanguageModel,
404405
mode,
405406
params
406407
);

packages/ai/src/models/generative-model.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,9 @@ describe('GenerativeModel', () => {
407407
{
408408
model: 'my-model',
409409
tools: [
410-
{ functionDeclarations: [{ name: 'myfunc', description: 'mydesc' }] }
410+
{ functionDeclarations: [{ name: 'myfunc', description: 'mydesc' }] },
411+
{ googleSearch: {} },
412+
{ urlContext: {} }
411413
],
412414
toolConfig: {
413415
functionCallingConfig: { mode: FunctionCallingMode.NONE }
@@ -420,7 +422,7 @@ describe('GenerativeModel', () => {
420422
{},
421423
fakeChromeAdapter
422424
);
423-
expect(genModel.tools?.length).to.equal(1);
425+
expect(genModel.tools?.length).to.equal(3);
424426
expect(genModel.toolConfig?.functionCallingConfig?.mode).to.equal(
425427
FunctionCallingMode.NONE
426428
);
@@ -539,7 +541,12 @@ describe('GenerativeModel', () => {
539541
restore();
540542
});
541543
it('calls countTokens', async () => {
542-
const genModel = new GenerativeModel(fakeAI, { model: 'my-model' });
544+
const genModel = new GenerativeModel(
545+
fakeAI,
546+
{ model: 'my-model' },
547+
{},
548+
fakeChromeAdapter
549+
);
543550
const mockResponse = getMockResponse(
544551
'vertexAI',
545552
'unary-success-total-tokens.json'

0 commit comments

Comments
 (0)