@firebase/ai 2.8.0 → 2.9.0-20260224183151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-public.d.ts +18 -0
- package/dist/ai.d.ts +36 -0
- package/dist/esm/index.esm.js +237 -50
- package/dist/esm/index.esm.js.map +1 -1
- package/dist/esm/src/methods/chat-session.d.ts +22 -1
- package/dist/esm/src/methods/generate-content.d.ts +4 -2
- package/dist/esm/src/requests/response-helpers.d.ts +1 -1
- package/dist/esm/src/requests/stream-reader.d.ts +3 -1
- package/dist/esm/src/types/content.d.ts +1 -0
- package/dist/esm/src/types/requests.d.ts +14 -0
- package/dist/index.cjs.js +237 -50
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.node.cjs.js +237 -50
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +237 -50
- package/dist/index.node.mjs.map +1 -1
- package/dist/src/methods/chat-session.d.ts +22 -1
- package/dist/src/methods/generate-content.d.ts +4 -2
- package/dist/src/requests/response-helpers.d.ts +1 -1
- package/dist/src/requests/stream-reader.d.ts +3 -1
- package/dist/src/types/content.d.ts +1 -0
- package/dist/src/types/requests.d.ts +14 -0
- package/package.json +5 -5
package/dist/index.node.cjs.js
CHANGED
|
@@ -8,7 +8,7 @@ var util = require('@firebase/util');
|
|
|
8
8
|
var logger$1 = require('@firebase/logger');
|
|
9
9
|
|
|
10
10
|
var name = "@firebase/ai";
|
|
11
|
-
var version = "2.
|
|
11
|
+
var version = "2.9.0-20260224183151";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -1472,6 +1472,9 @@ function getText(response, partFilter) {
|
|
|
1472
1472
|
* Returns every {@link FunctionCall} associated with first candidate.
|
|
1473
1473
|
*/
|
|
1474
1474
|
function getFunctionCalls(response) {
|
|
1475
|
+
if (!response) {
|
|
1476
|
+
return undefined;
|
|
1477
|
+
}
|
|
1475
1478
|
const functionCalls = [];
|
|
1476
1479
|
if (response.candidates?.[0].content?.parts) {
|
|
1477
1480
|
for (const part of response.candidates?.[0].content?.parts) {
|
|
@@ -1767,15 +1770,44 @@ const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
|
|
|
1767
1770
|
*
|
|
1768
1771
|
* @param response - Response from a fetch call
|
|
1769
1772
|
*/
|
|
1770
|
-
function processStream(response, apiSettings, inferenceSource) {
|
|
1773
|
+
async function processStream(response, apiSettings, inferenceSource) {
|
|
1771
1774
|
const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
|
|
1772
1775
|
const responseStream = getResponseStream(inputStream);
|
|
1773
1776
|
// We split the stream so the user can iterate over partial results (stream1)
|
|
1774
1777
|
// while we aggregate the full result for history/final response (stream2).
|
|
1775
1778
|
const [stream1, stream2] = responseStream.tee();
|
|
1779
|
+
const { response: internalResponse, firstValue } = await processStreamInternal(stream2, apiSettings, inferenceSource);
|
|
1776
1780
|
return {
|
|
1777
1781
|
stream: generateResponseSequence(stream1, apiSettings, inferenceSource),
|
|
1778
|
-
response:
|
|
1782
|
+
response: internalResponse,
|
|
1783
|
+
firstValue
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Consumes streams teed from the input stream for internal needs.
|
|
1788
|
+
* The streams need to be teed because each stream can only be consumed
|
|
1789
|
+
* by one reader.
|
|
1790
|
+
*
|
|
1791
|
+
* "streamForPeek"
|
|
1792
|
+
* This tee is used to peek at the first value for relevant information
|
|
1793
|
+
* that we need to evaluate before returning the stream handle to the
|
|
1794
|
+
* client. For example, we need to check if the response is a function
|
|
1795
|
+
* call that may need to be handled by automatic function calling before
|
|
1796
|
+
* returning a response to the client.
|
|
1797
|
+
*
|
|
1798
|
+
* "streamForAggregation"
|
|
1799
|
+
* We iterate through this tee independently from the user and aggregate
|
|
1800
|
+
* it into a single response when the stream is complete. We need this
|
|
1801
|
+
* aggregate object to add to chat history when using ChatSession. It's
|
|
1802
|
+
* also provided to the user if they want it.
|
|
1803
|
+
*/
|
|
1804
|
+
async function processStreamInternal(stream, apiSettings, inferenceSource) {
|
|
1805
|
+
const [streamForPeek, streamForAggregation] = stream.tee();
|
|
1806
|
+
const reader = streamForPeek.getReader();
|
|
1807
|
+
const { value } = await reader.read();
|
|
1808
|
+
return {
|
|
1809
|
+
firstValue: value,
|
|
1810
|
+
response: getResponsePromise(streamForAggregation, apiSettings, inferenceSource)
|
|
1779
1811
|
};
|
|
1780
1812
|
}
|
|
1781
1813
|
async function getResponsePromise(stream, apiSettings, inferenceSource) {
|
|
@@ -2346,6 +2378,11 @@ function validateChatHistory(history) {
|
|
|
2346
2378
|
* by the user, preventing duplicate console logs.
|
|
2347
2379
|
*/
|
|
2348
2380
|
const SILENT_ERROR = 'SILENT_ERROR';
|
|
2381
|
+
/**
|
|
2382
|
+
* Prevent infinite loop if the model continues to request sequential
|
|
2383
|
+
* function calls during automatic function calling.
|
|
2384
|
+
*/
|
|
2385
|
+
const DEFAULT_MAX_SEQUENTIAL_FUNCTION_CALLS = 10;
|
|
2349
2386
|
/**
|
|
2350
2387
|
* ChatSession class that enables sending chat messages and stores
|
|
2351
2388
|
* history of sent and received messages so far.
|
|
@@ -2380,48 +2417,89 @@ class ChatSession {
|
|
|
2380
2417
|
return this._history;
|
|
2381
2418
|
}
|
|
2382
2419
|
/**
|
|
2383
|
-
*
|
|
2384
|
-
*
|
|
2420
|
+
* Format Content into a request for generateContent or
|
|
2421
|
+
* generateContentStream.
|
|
2422
|
+
* @internal
|
|
2385
2423
|
*/
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
const newContent = formatNewContent(request);
|
|
2389
|
-
const generateContentRequest = {
|
|
2424
|
+
_formatRequest(incomingContent, tempHistory) {
|
|
2425
|
+
return {
|
|
2390
2426
|
safetySettings: this.params?.safetySettings,
|
|
2391
2427
|
generationConfig: this.params?.generationConfig,
|
|
2392
2428
|
tools: this.params?.tools,
|
|
2393
2429
|
toolConfig: this.params?.toolConfig,
|
|
2394
2430
|
systemInstruction: this.params?.systemInstruction,
|
|
2395
|
-
contents: [...this._history,
|
|
2431
|
+
contents: [...this._history, ...tempHistory, incomingContent]
|
|
2396
2432
|
};
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Sends a chat message and receives a non-streaming
|
|
2436
|
+
* {@link GenerateContentResult}
|
|
2437
|
+
*/
|
|
2438
|
+
async sendMessage(request, singleRequestOptions) {
|
|
2397
2439
|
let finalResult = {};
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
if (blockErrorMessage) {
|
|
2419
|
-
logger.warn(`sendMessage() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);
|
|
2440
|
+
await this._sendPromise;
|
|
2441
|
+
/**
|
|
2442
|
+
* Temporarily store multiple turns for cases like automatic function
|
|
2443
|
+
* calling, only writing them to official history when the entire
|
|
2444
|
+
* sequence has completed successfully.
|
|
2445
|
+
*/
|
|
2446
|
+
const tempHistory = [];
|
|
2447
|
+
this._sendPromise = this._sendPromise.then(async () => {
|
|
2448
|
+
let functionCalls;
|
|
2449
|
+
let functionCallTurnCount = 0;
|
|
2450
|
+
const functionCallMaxTurns = this.requestOptions?.maxSequentalFunctionCalls ??
|
|
2451
|
+
DEFAULT_MAX_SEQUENTIAL_FUNCTION_CALLS;
|
|
2452
|
+
// Repeats until model returns a response with no function calls
|
|
2453
|
+
// or until `functionCallMaxTurns` is met or exceeded.
|
|
2454
|
+
do {
|
|
2455
|
+
let formattedContent;
|
|
2456
|
+
if (functionCalls) {
|
|
2457
|
+
functionCallTurnCount++;
|
|
2458
|
+
const functionResponseParts = await this._callFunctionsAsNeeded(functionCalls);
|
|
2459
|
+
formattedContent = formatNewContent(functionResponseParts);
|
|
2420
2460
|
}
|
|
2461
|
+
else {
|
|
2462
|
+
formattedContent = formatNewContent(request);
|
|
2463
|
+
}
|
|
2464
|
+
const formattedRequest = this._formatRequest(formattedContent, tempHistory);
|
|
2465
|
+
tempHistory.push(formattedContent);
|
|
2466
|
+
const result = await generateContent(this._apiSettings, this.model, formattedRequest, this.chromeAdapter, {
|
|
2467
|
+
...this.requestOptions,
|
|
2468
|
+
...singleRequestOptions
|
|
2469
|
+
});
|
|
2470
|
+
if (result) {
|
|
2471
|
+
finalResult = result;
|
|
2472
|
+
functionCalls = this._getCallableFunctionCalls(result.response);
|
|
2473
|
+
if (result.response.candidates &&
|
|
2474
|
+
result.response.candidates.length > 0) {
|
|
2475
|
+
// TODO: Make this update atomic. If creating `responseContent` throws,
|
|
2476
|
+
// history will contain the user message but not the response, causing
|
|
2477
|
+
// validation errors on the next request.
|
|
2478
|
+
const responseContent = {
|
|
2479
|
+
parts: result.response.candidates?.[0].content.parts || [],
|
|
2480
|
+
// Response seems to come back without a role set.
|
|
2481
|
+
role: result.response.candidates?.[0].content.role || 'model'
|
|
2482
|
+
};
|
|
2483
|
+
tempHistory.push(responseContent);
|
|
2484
|
+
}
|
|
2485
|
+
else {
|
|
2486
|
+
const blockErrorMessage = formatBlockErrorMessage(result.response);
|
|
2487
|
+
if (blockErrorMessage) {
|
|
2488
|
+
logger.warn(`sendMessage() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
else {
|
|
2493
|
+
functionCalls = undefined;
|
|
2494
|
+
}
|
|
2495
|
+
} while (functionCalls && functionCallTurnCount < functionCallMaxTurns);
|
|
2496
|
+
if (functionCalls && functionCallTurnCount >= functionCallMaxTurns) {
|
|
2497
|
+
logger.warn(`Automatic function calling exceeded the limit of` +
|
|
2498
|
+
` ${functionCallMaxTurns} function calls. Returning last model response.`);
|
|
2421
2499
|
}
|
|
2422
|
-
finalResult = result;
|
|
2423
2500
|
});
|
|
2424
2501
|
await this._sendPromise;
|
|
2502
|
+
this._history = this._history.concat(tempHistory);
|
|
2425
2503
|
return finalResult;
|
|
2426
2504
|
}
|
|
2427
2505
|
/**
|
|
@@ -2431,23 +2509,62 @@ class ChatSession {
|
|
|
2431
2509
|
*/
|
|
2432
2510
|
async sendMessageStream(request, singleRequestOptions) {
|
|
2433
2511
|
await this._sendPromise;
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2512
|
+
/**
|
|
2513
|
+
* Temporarily store multiple turns for cases like automatic function
|
|
2514
|
+
* calling, only writing them to official history when the entire
|
|
2515
|
+
* sequence has completed successfully.
|
|
2516
|
+
*/
|
|
2517
|
+
const tempHistory = [];
|
|
2518
|
+
const callGenerateContentStream = async () => {
|
|
2519
|
+
let functionCalls;
|
|
2520
|
+
let functionCallTurnCount = 0;
|
|
2521
|
+
const functionCallMaxTurns = this.requestOptions?.maxSequentalFunctionCalls ??
|
|
2522
|
+
DEFAULT_MAX_SEQUENTIAL_FUNCTION_CALLS;
|
|
2523
|
+
let result;
|
|
2524
|
+
// Repeats until model returns a response with no function calls
|
|
2525
|
+
// or until `functionCallMaxTurns` is met or exceeded.
|
|
2526
|
+
do {
|
|
2527
|
+
let formattedContent;
|
|
2528
|
+
if (functionCalls) {
|
|
2529
|
+
functionCallTurnCount++;
|
|
2530
|
+
const functionResponseParts = await this._callFunctionsAsNeeded(functionCalls);
|
|
2531
|
+
formattedContent = formatNewContent(functionResponseParts);
|
|
2532
|
+
}
|
|
2533
|
+
else {
|
|
2534
|
+
formattedContent = formatNewContent(request);
|
|
2535
|
+
}
|
|
2536
|
+
tempHistory.push(formattedContent);
|
|
2537
|
+
const formattedRequest = this._formatRequest(formattedContent, tempHistory);
|
|
2538
|
+
result = await generateContentStream(this._apiSettings, this.model, formattedRequest, this.chromeAdapter, {
|
|
2539
|
+
...this.requestOptions,
|
|
2540
|
+
...singleRequestOptions
|
|
2541
|
+
});
|
|
2542
|
+
functionCalls = this._getCallableFunctionCalls(result.firstValue);
|
|
2543
|
+
if (functionCalls &&
|
|
2544
|
+
result.firstValue &&
|
|
2545
|
+
result.firstValue.candidates &&
|
|
2546
|
+
result.firstValue.candidates.length > 0) {
|
|
2547
|
+
const responseContent = {
|
|
2548
|
+
...result.firstValue.candidates[0].content
|
|
2549
|
+
};
|
|
2550
|
+
if (!responseContent.role) {
|
|
2551
|
+
responseContent.role = 'model';
|
|
2552
|
+
}
|
|
2553
|
+
tempHistory.push(responseContent);
|
|
2554
|
+
}
|
|
2555
|
+
} while (functionCalls && functionCallTurnCount < functionCallMaxTurns);
|
|
2556
|
+
if (functionCalls && functionCallTurnCount >= functionCallMaxTurns) {
|
|
2557
|
+
logger.warn(`Automatic function calling exceeded the limit of` +
|
|
2558
|
+
` ${functionCallMaxTurns} function calls. Returning last model response.`);
|
|
2559
|
+
}
|
|
2560
|
+
return { stream: result.stream, response: result.response };
|
|
2442
2561
|
};
|
|
2443
|
-
const streamPromise =
|
|
2444
|
-
|
|
2445
|
-
...singleRequestOptions
|
|
2446
|
-
});
|
|
2447
|
-
// We hook into the chain to update history, but we don't block the
|
|
2448
|
-
// return of `streamPromise` to the user.
|
|
2562
|
+
const streamPromise = callGenerateContentStream();
|
|
2563
|
+
// Add onto the chain.
|
|
2449
2564
|
this._sendPromise = this._sendPromise
|
|
2450
|
-
.then(() => streamPromise)
|
|
2565
|
+
.then(async () => streamPromise)
|
|
2566
|
+
// This must be handled to avoid unhandled rejection, but jump
|
|
2567
|
+
// to the final catch block with a label to not log this error.
|
|
2451
2568
|
.catch(_ignored => {
|
|
2452
2569
|
// If the initial fetch fails, the user's `streamPromise` rejects.
|
|
2453
2570
|
// We swallow the error here to prevent double logging in the final catch.
|
|
@@ -2460,7 +2577,7 @@ class ChatSession {
|
|
|
2460
2577
|
// TODO: Move response validation logic upstream to `stream-reader` so
|
|
2461
2578
|
// errors propagate to the user's `result.response` promise.
|
|
2462
2579
|
if (response.candidates && response.candidates.length > 0) {
|
|
2463
|
-
this._history.
|
|
2580
|
+
this._history = this._history.concat(tempHistory);
|
|
2464
2581
|
// TODO: Validate that `response.candidates[0].content` is not null.
|
|
2465
2582
|
const responseContent = { ...response.candidates[0].content };
|
|
2466
2583
|
if (!responseContent.role) {
|
|
@@ -2483,6 +2600,75 @@ class ChatSession {
|
|
|
2483
2600
|
});
|
|
2484
2601
|
return streamPromise;
|
|
2485
2602
|
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Get function calls that the SDK has references to actually call.
|
|
2605
|
+
* This is all-or-nothing. If the model is requesting multiple
|
|
2606
|
+
* function calls, all of them must have references in order for
|
|
2607
|
+
* automatic function calling to work.
|
|
2608
|
+
*
|
|
2609
|
+
* @internal
|
|
2610
|
+
*/
|
|
2611
|
+
_getCallableFunctionCalls(response) {
|
|
2612
|
+
const functionDeclarationsTool = this.params?.tools?.find(tool => tool.functionDeclarations);
|
|
2613
|
+
if (!functionDeclarationsTool?.functionDeclarations) {
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const functionCalls = getFunctionCalls(response);
|
|
2617
|
+
if (!functionCalls) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
for (const functionCall of functionCalls) {
|
|
2621
|
+
const hasFunctionReference = functionDeclarationsTool.functionDeclarations?.some(declaration => declaration.name === functionCall.name &&
|
|
2622
|
+
typeof declaration.functionReference === 'function');
|
|
2623
|
+
if (!hasFunctionReference) {
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return functionCalls;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Call user-defined functions if requested by the model, and return
|
|
2631
|
+
* the response that should be sent to the model.
|
|
2632
|
+
* @internal
|
|
2633
|
+
*/
|
|
2634
|
+
async _callFunctionsAsNeeded(functionCalls) {
|
|
2635
|
+
const activeCallList = new Map();
|
|
2636
|
+
const promiseList = [];
|
|
2637
|
+
const functionDeclarationsTool = this.params?.tools?.find(tool => tool.functionDeclarations);
|
|
2638
|
+
if (functionDeclarationsTool &&
|
|
2639
|
+
functionDeclarationsTool.functionDeclarations) {
|
|
2640
|
+
for (const functionCall of functionCalls) {
|
|
2641
|
+
const functionDeclaration = functionDeclarationsTool.functionDeclarations.find(declaration => declaration.name === functionCall.name);
|
|
2642
|
+
if (functionDeclaration?.functionReference) {
|
|
2643
|
+
const results = Promise.resolve(functionDeclaration.functionReference(functionCall.args)).catch(e => {
|
|
2644
|
+
const wrappedError = new AIError(AIErrorCode.ERROR, `Error in user-defined function "${functionDeclaration.name}": ${e.message}`);
|
|
2645
|
+
wrappedError.stack = e.stack;
|
|
2646
|
+
throw wrappedError;
|
|
2647
|
+
});
|
|
2648
|
+
activeCallList.set(functionCall.name, {
|
|
2649
|
+
id: functionCall.id,
|
|
2650
|
+
results
|
|
2651
|
+
});
|
|
2652
|
+
promiseList.push(results);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
// Wait for promises to finish.
|
|
2656
|
+
await Promise.all(promiseList);
|
|
2657
|
+
const functionResponseParts = [];
|
|
2658
|
+
for (const [name, callData] of activeCallList) {
|
|
2659
|
+
functionResponseParts.push({
|
|
2660
|
+
functionResponse: {
|
|
2661
|
+
name,
|
|
2662
|
+
response: await callData.results
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
return functionResponseParts;
|
|
2667
|
+
}
|
|
2668
|
+
else {
|
|
2669
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, `No function declarations were provided in "tools".`);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2486
2672
|
}
|
|
2487
2673
|
|
|
2488
2674
|
/**
|
|
@@ -2586,7 +2772,7 @@ class GenerativeModel extends AIModel {
|
|
|
2586
2772
|
*/
|
|
2587
2773
|
async generateContentStream(request, singleRequestOptions) {
|
|
2588
2774
|
const formattedParams = formatGenerateContentInput(request);
|
|
2589
|
-
|
|
2775
|
+
const { stream, response } = await generateContentStream(this._apiSettings, this.model, {
|
|
2590
2776
|
generationConfig: this.generationConfig,
|
|
2591
2777
|
safetySettings: this.safetySettings,
|
|
2592
2778
|
tools: this.tools,
|
|
@@ -2599,6 +2785,7 @@ class GenerativeModel extends AIModel {
|
|
|
2599
2785
|
...this.requestOptions,
|
|
2600
2786
|
...singleRequestOptions
|
|
2601
2787
|
});
|
|
2788
|
+
return { stream, response };
|
|
2602
2789
|
}
|
|
2603
2790
|
/**
|
|
2604
2791
|
* Gets a new {@link ChatSession} instance which can be used for
|