From 0f106fc6417342e78cc6682a8bca598f877ecd46 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 1 Aug 2025 10:42:33 -0400 Subject: [PATCH 1/6] [Firebase AI] Add thought summary and signature support --- common/api-review/ai.api.md | 23 ++++++++++++++++++++ packages/ai/src/requests/response-helpers.ts | 14 ++++++++---- packages/ai/src/types/content.ts | 10 +++++++++ packages/ai/src/types/requests.ts | 9 ++++++++ packages/ai/src/types/responses.ts | 1 + 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/common/api-review/ai.api.md b/common/api-review/ai.api.md index 199b97b10a9..0e3ead30b63 100644 --- a/common/api-review/ai.api.md +++ b/common/api-review/ai.api.md @@ -208,6 +208,8 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse functionCalls: () => FunctionCall[] | undefined; inlineDataParts: () => InlineDataPart[] | undefined; text: () => string; + // (undocumented) + thoughtSummary: () => string | undefined; } // @public @@ -240,6 +242,10 @@ export interface FileDataPart { inlineData?: never; // (undocumented) text?: never; + // (undocumented) + thought?: boolean; + // (undocumented) + thoughtSignature?: string; } // @public @@ -294,6 +300,10 @@ export interface FunctionCallPart { inlineData?: never; // (undocumented) text?: never; + // (undocumented) + thought?: boolean; + // (undocumented) + thoughtSignature?: string; } // @public @@ -326,6 +336,10 @@ export interface FunctionResponsePart { inlineData?: never; // (undocumented) text?: never; + // (undocumented) + thought?: boolean; + // (undocumented) + thoughtSignature?: string; } // @public @@ -691,6 +705,10 @@ export interface InlineDataPart { inlineData: GenerativeContentBlob; // (undocumented) text?: never; + // (undocumented) + thought?: boolean; + // (undocumented) + thoughtSignature?: string; videoMetadata?: VideoMetadata; } @@ -957,10 +975,15 @@ export interface TextPart { inlineData?: never; // (undocumented) text: string; + // (undocumented) + thought?: boolean; + // (undocumented) + thoughtSignature?: string; } // @public export interface ThinkingConfig { + includeThoughts?: boolean; thinkingBudget?: number; } diff --git a/packages/ai/src/requests/response-helpers.ts b/packages/ai/src/requests/response-helpers.ts index 2505b5c9276..f8b9b008262 100644 --- a/packages/ai/src/requests/response-helpers.ts +++ b/packages/ai/src/requests/response-helpers.ts @@ -78,7 +78,7 @@ export function addHelpers( } ); } - return getText(response); + return getText(response, false); } else if (response.promptFeedback) { throw new AIError( AIErrorCode.RESPONSE_ERROR, @@ -160,13 +160,19 @@ export function addHelpers( } /** - * Returns all text found in all parts of first candidate. + * Returns all text from the first candidate's parts, filtering by whether they + * are part of the model's 'thought' process. + * + * @param response - The {@link GenerateContentResponse} from which to extract text. + * @param isThought - If `true`, extracts text from `thought` parts of the response, + * which represent the model's internal reasoning. If `false`, extracts text from + * regular response parts. */ -export function getText(response: GenerateContentResponse): string { +export function getText(response: GenerateContentResponse, isThought: boolean): string { const textStrings = []; if (response.candidates?.[0].content?.parts) { for (const part of response.candidates?.[0].content?.parts) { - if (part.text) { + if (part.text && (part.thought ?? false) === isThought) { textStrings.push(part.text); } } diff --git a/packages/ai/src/types/content.ts b/packages/ai/src/types/content.ts index ad2906671e4..dadb700d474 100644 --- a/packages/ai/src/types/content.ts +++ b/packages/ai/src/types/content.ts @@ -47,6 +47,8 @@ export interface TextPart { inlineData?: never; functionCall?: never; functionResponse?: never; + thought?: boolean; + thoughtSignature?: string; } /** @@ -62,6 +64,8 @@ export interface InlineDataPart { * Applicable if `inlineData` is a video. */ videoMetadata?: VideoMetadata; + thought?: boolean; + thoughtSignature?: string; } /** @@ -90,6 +94,8 @@ export interface FunctionCallPart { inlineData?: never; functionCall: FunctionCall; functionResponse?: never; + thought?: boolean; + thoughtSignature?: string; } /** @@ -101,6 +107,8 @@ export interface FunctionResponsePart { inlineData?: never; functionCall?: never; functionResponse: FunctionResponse; + thought?: boolean; + thoughtSignature?: string; } /** @@ -113,6 +121,8 @@ export interface FileDataPart { functionCall?: never; functionResponse?: never; fileData: FileData; + thought?: boolean; + thoughtSignature?: string; } /** diff --git a/packages/ai/src/types/requests.ts b/packages/ai/src/types/requests.ts index e68f3af161d..a9161b2280b 100644 --- a/packages/ai/src/types/requests.ts +++ b/packages/ai/src/types/requests.ts @@ -294,4 +294,13 @@ export interface ThinkingConfig { * feature or if the specified budget is not within the model's supported range. */ thinkingBudget?: number; + + /** + * Whether to include "thought summaries" in the model's response. + * + * Thought summaries provide a brief overview of the model's internal thinking process, + * offering insight into how it arrived at the final answer. This can be useful for + * debugging, understanding the model's reasoning, and verifying its accuracy. + */ + includeThoughts?: boolean; } diff --git a/packages/ai/src/types/responses.ts b/packages/ai/src/types/responses.ts index 323699e646b..e249bd873da 100644 --- a/packages/ai/src/types/responses.ts +++ b/packages/ai/src/types/responses.ts @@ -69,6 +69,7 @@ export interface EnhancedGenerateContentResponse */ inlineDataParts: () => InlineDataPart[] | undefined; functionCalls: () => FunctionCall[] | undefined; + thoughtSummary: () => string | undefined; } /** From e8df63dde381604d9381b43eb65680927aa96347 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 1 Aug 2025 11:45:06 -0400 Subject: [PATCH 2/6] Run formatter --- packages/ai/src/requests/response-helpers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/requests/response-helpers.ts b/packages/ai/src/requests/response-helpers.ts index f8b9b008262..1c723137aac 100644 --- a/packages/ai/src/requests/response-helpers.ts +++ b/packages/ai/src/requests/response-helpers.ts @@ -168,7 +168,10 @@ export function addHelpers( * which represent the model's internal reasoning. If `false`, extracts text from * regular response parts. */ -export function getText(response: GenerateContentResponse, isThought: boolean): string { +export function getText( + response: GenerateContentResponse, + isThought: boolean +): string { const textStrings = []; if (response.candidates?.[0].content?.parts) { for (const part of response.candidates?.[0].content?.parts) { From 3f62f8a0a1ea1d3dc83a132e3d442d77ddc103cf Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 12 Aug 2025 11:59:54 -0700 Subject: [PATCH 3/6] Implement thoughtSummary() method, add docs, changeset --- .changeset/brave-llamas-impress.md | 6 + common/api-review/ai.api.md | 12 -- .../ai.enhancedgeneratecontentresponse.md | 19 ++- docs-devsite/ai.filedatapart.md | 9 ++ docs-devsite/ai.functioncallpart.md | 9 ++ docs-devsite/ai.functionresponsepart.md | 9 ++ docs-devsite/ai.inlinedatapart.md | 9 ++ docs-devsite/ai.textpart.md | 9 ++ docs-devsite/ai.thinkingconfig.md | 13 ++ .../ai/src/methods/chat-session-helpers.ts | 3 +- .../ai/src/requests/response-helpers.test.ts | 31 +++++ packages/ai/src/requests/response-helpers.ts | 120 ++++++++---------- packages/ai/src/types/content.ts | 5 - packages/ai/src/types/requests.ts | 1 + packages/ai/src/types/responses.ts | 20 +++ 15 files changed, 191 insertions(+), 84 deletions(-) create mode 100644 .changeset/brave-llamas-impress.md diff --git a/.changeset/brave-llamas-impress.md b/.changeset/brave-llamas-impress.md new file mode 100644 index 00000000000..61d8f87ff41 --- /dev/null +++ b/.changeset/brave-llamas-impress.md @@ -0,0 +1,6 @@ +--- +'@firebase/ai': minor +'firebase': minor +--- + +Add `thoughtSummary()` convenience method to `EnhancedGenerateContentResponse`. diff --git a/common/api-review/ai.api.md b/common/api-review/ai.api.md index 0e3ead30b63..23f2d96fede 100644 --- a/common/api-review/ai.api.md +++ b/common/api-review/ai.api.md @@ -204,11 +204,9 @@ export { Date_2 as Date } // @public export interface EnhancedGenerateContentResponse extends GenerateContentResponse { - // (undocumented) functionCalls: () => FunctionCall[] | undefined; inlineDataParts: () => InlineDataPart[] | undefined; text: () => string; - // (undocumented) thoughtSummary: () => string | undefined; } @@ -244,8 +242,6 @@ export interface FileDataPart { text?: never; // (undocumented) thought?: boolean; - // (undocumented) - thoughtSignature?: string; } // @public @@ -302,8 +298,6 @@ export interface FunctionCallPart { text?: never; // (undocumented) thought?: boolean; - // (undocumented) - thoughtSignature?: string; } // @public @@ -338,8 +332,6 @@ export interface FunctionResponsePart { text?: never; // (undocumented) thought?: boolean; - // (undocumented) - thoughtSignature?: string; } // @public @@ -707,8 +699,6 @@ export interface InlineDataPart { text?: never; // (undocumented) thought?: boolean; - // (undocumented) - thoughtSignature?: string; videoMetadata?: VideoMetadata; } @@ -977,8 +967,6 @@ export interface TextPart { text: string; // (undocumented) thought?: boolean; - // (undocumented) - thoughtSignature?: string; } // @public diff --git a/docs-devsite/ai.enhancedgeneratecontentresponse.md b/docs-devsite/ai.enhancedgeneratecontentresponse.md index 330dc10f322..2ae62f952e1 100644 --- a/docs-devsite/ai.enhancedgeneratecontentresponse.md +++ b/docs-devsite/ai.enhancedgeneratecontentresponse.md @@ -23,12 +23,15 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse | Property | Type | Description | | --- | --- | --- | -| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | | +| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | Aggregates and returns all [FunctionCall](./ai.functioncall.md#functioncall_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | | [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () => [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)\[\] \| undefined | Aggregates and returns all [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | | [text](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsetext) | () => string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. | +| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () => string \| undefined | Aggregates and returns all [TextPart](./ai.textpart.md#textpart_interface)s with their thought property set to true from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | ## EnhancedGenerateContentResponse.functionCalls +Aggregates and returns all [FunctionCall](./ai.functioncall.md#functioncall_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. + Signature: ```typescript @@ -54,3 +57,17 @@ Returns the text string from the response, if available. Throws if the prompt or ```typescript text: () => string; ``` + +## EnhancedGenerateContentResponse.thoughtSummary + +Aggregates and returns all [TextPart](./ai.textpart.md#textpart_interface)s with their `thought` property set to `true` from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. + +Thought summaries provide a brief overview of the model's internal thinking process, offering insight into how it arrived at the final answer. This can be useful for debugging, understanding the model's reasoning, and verifying its accuracy. + +Thoughts will only be included if [ThinkingConfig.includeThoughts](./ai.thinkingconfig.md#thinkingconfigincludethoughts) is set to `true`. + +Signature: + +```typescript +thoughtSummary: () => string | undefined; +``` diff --git a/docs-devsite/ai.filedatapart.md b/docs-devsite/ai.filedatapart.md index 65cb9dc00ef..2b5179319f7 100644 --- a/docs-devsite/ai.filedatapart.md +++ b/docs-devsite/ai.filedatapart.md @@ -27,6 +27,7 @@ export interface FileDataPart | [functionResponse](./ai.filedatapart.md#filedatapartfunctionresponse) | never | | | [inlineData](./ai.filedatapart.md#filedatapartinlinedata) | never | | | [text](./ai.filedatapart.md#filedataparttext) | never | | +| [thought](./ai.filedatapart.md#filedatapartthought) | boolean | | ## FileDataPart.fileData @@ -67,3 +68,11 @@ inlineData?: never; ```typescript text?: never; ``` + +## FileDataPart.thought + +Signature: + +```typescript +thought?: boolean; +``` diff --git a/docs-devsite/ai.functioncallpart.md b/docs-devsite/ai.functioncallpart.md index b16e58f80a6..3f07c5d0d74 100644 --- a/docs-devsite/ai.functioncallpart.md +++ b/docs-devsite/ai.functioncallpart.md @@ -26,6 +26,7 @@ export interface FunctionCallPart | [functionResponse](./ai.functioncallpart.md#functioncallpartfunctionresponse) | never | | | [inlineData](./ai.functioncallpart.md#functioncallpartinlinedata) | never | | | [text](./ai.functioncallpart.md#functioncallparttext) | never | | +| [thought](./ai.functioncallpart.md#functioncallpartthought) | boolean | | ## FunctionCallPart.functionCall @@ -58,3 +59,11 @@ inlineData?: never; ```typescript text?: never; ``` + +## FunctionCallPart.thought + +Signature: + +```typescript +thought?: boolean; +``` diff --git a/docs-devsite/ai.functionresponsepart.md b/docs-devsite/ai.functionresponsepart.md index 9c80258f43f..4e8c9ea5724 100644 --- a/docs-devsite/ai.functionresponsepart.md +++ b/docs-devsite/ai.functionresponsepart.md @@ -26,6 +26,7 @@ export interface FunctionResponsePart | [functionResponse](./ai.functionresponsepart.md#functionresponsepartfunctionresponse) | [FunctionResponse](./ai.functionresponse.md#functionresponse_interface) | | | [inlineData](./ai.functionresponsepart.md#functionresponsepartinlinedata) | never | | | [text](./ai.functionresponsepart.md#functionresponseparttext) | never | | +| [thought](./ai.functionresponsepart.md#functionresponsepartthought) | boolean | | ## FunctionResponsePart.functionCall @@ -58,3 +59,11 @@ inlineData?: never; ```typescript text?: never; ``` + +## FunctionResponsePart.thought + +Signature: + +```typescript +thought?: boolean; +``` diff --git a/docs-devsite/ai.inlinedatapart.md b/docs-devsite/ai.inlinedatapart.md index 0dd68edda68..c9ead9d061d 100644 --- a/docs-devsite/ai.inlinedatapart.md +++ b/docs-devsite/ai.inlinedatapart.md @@ -26,6 +26,7 @@ export interface InlineDataPart | [functionResponse](./ai.inlinedatapart.md#inlinedatapartfunctionresponse) | never | | | [inlineData](./ai.inlinedatapart.md#inlinedatapartinlinedata) | [GenerativeContentBlob](./ai.generativecontentblob.md#generativecontentblob_interface) | | | [text](./ai.inlinedatapart.md#inlinedataparttext) | never | | +| [thought](./ai.inlinedatapart.md#inlinedatapartthought) | boolean | | | [videoMetadata](./ai.inlinedatapart.md#inlinedatapartvideometadata) | [VideoMetadata](./ai.videometadata.md#videometadata_interface) | Applicable if inlineData is a video. | ## InlineDataPart.functionCall @@ -60,6 +61,14 @@ inlineData: GenerativeContentBlob; text?: never; ``` +## InlineDataPart.thought + +Signature: + +```typescript +thought?: boolean; +``` + ## InlineDataPart.videoMetadata Applicable if `inlineData` is a video. diff --git a/docs-devsite/ai.textpart.md b/docs-devsite/ai.textpart.md index 2057d95d32e..2466f9cca8f 100644 --- a/docs-devsite/ai.textpart.md +++ b/docs-devsite/ai.textpart.md @@ -26,6 +26,7 @@ export interface TextPart | [functionResponse](./ai.textpart.md#textpartfunctionresponse) | never | | | [inlineData](./ai.textpart.md#textpartinlinedata) | never | | | [text](./ai.textpart.md#textparttext) | string | | +| [thought](./ai.textpart.md#textpartthought) | boolean | | ## TextPart.functionCall @@ -58,3 +59,11 @@ inlineData?: never; ```typescript text: string; ``` + +## TextPart.thought + +Signature: + +```typescript +thought?: boolean; +``` diff --git a/docs-devsite/ai.thinkingconfig.md b/docs-devsite/ai.thinkingconfig.md index ec348a20487..1ddc1626f48 100644 --- a/docs-devsite/ai.thinkingconfig.md +++ b/docs-devsite/ai.thinkingconfig.md @@ -24,8 +24,21 @@ export interface ThinkingConfig | Property | Type | Description | | --- | --- | --- | +| [includeThoughts](./ai.thinkingconfig.md#thinkingconfigincludethoughts) | boolean | Whether to include "thought summaries" in the model's response. | | [thinkingBudget](./ai.thinkingconfig.md#thinkingconfigthinkingbudget) | number | The thinking budget, in tokens.This parameter sets an upper limit on the number of tokens the model can use for its internal "thinking" process. A higher budget may result in higher quality responses for complex tasks but can also increase latency and cost.If you don't specify a budget, the model will determine the appropriate amount of thinking based on the complexity of the prompt.An error will be thrown if you set a thinking budget for a model that does not support this feature or if the specified budget is not within the model's supported range. | +## ThinkingConfig.includeThoughts + +Whether to include "thought summaries" in the model's response. + +Thought summaries provide a brief overview of the model's internal thinking process, offering insight into how it arrived at the final answer. This can be useful for debugging, understanding the model's reasoning, and verifying its accuracy. + +Signature: + +```typescript +includeThoughts?: boolean; +``` + ## ThinkingConfig.thinkingBudget The thinking budget, in tokens. diff --git a/packages/ai/src/methods/chat-session-helpers.ts b/packages/ai/src/methods/chat-session-helpers.ts index 1bb0e2798f2..34cc6d32fa1 100644 --- a/packages/ai/src/methods/chat-session-helpers.ts +++ b/packages/ai/src/methods/chat-session-helpers.ts @@ -80,7 +80,8 @@ export function validateChatHistory(history: Content[]): void { text: 0, inlineData: 0, functionCall: 0, - functionResponse: 0 + functionResponse: 0, + thought: 0 }; for (const part of parts) { diff --git a/packages/ai/src/requests/response-helpers.test.ts b/packages/ai/src/requests/response-helpers.test.ts index 97dd2f9fe30..8583ca9a733 100644 --- a/packages/ai/src/requests/response-helpers.test.ts +++ b/packages/ai/src/requests/response-helpers.test.ts @@ -48,6 +48,21 @@ const fakeResponseText: GenerateContentResponse = { ] }; +const fakeResponseThoughts: GenerateContentResponse = { + candidates: [ + { + index: 0, + content: { + role: 'model', + parts: [ + { text: 'Some text' }, + { text: 'and some thoughts', thought: true } + ] + } + } + ] +}; + const functionCallPart1 = { functionCall: { name: 'find_theaters', @@ -188,6 +203,7 @@ describe('response-helpers methods', () => { expect(enhancedResponse.text()).to.equal('Some text and some more text'); expect(enhancedResponse.functionCalls()).to.be.undefined; expect(enhancedResponse.inlineDataParts()).to.be.undefined; + expect(enhancedResponse.thoughtSummary()).to.be.undefined; }); it('good response functionCall', async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCall); @@ -196,6 +212,7 @@ describe('response-helpers methods', () => { functionCallPart1.functionCall ]); expect(enhancedResponse.inlineDataParts()).to.be.undefined; + expect(enhancedResponse.thoughtSummary()).to.be.undefined; }); it('good response functionCalls', async () => { const enhancedResponse = addHelpers(fakeResponseFunctionCalls); @@ -205,6 +222,7 @@ describe('response-helpers methods', () => { functionCallPart2.functionCall ]); expect(enhancedResponse.inlineDataParts()).to.be.undefined; + expect(enhancedResponse.thoughtSummary()).to.be.undefined; }); it('good response text/functionCall', async () => { const enhancedResponse = addHelpers(fakeResponseMixed1); @@ -213,6 +231,7 @@ describe('response-helpers methods', () => { ]); expect(enhancedResponse.text()).to.equal('some text'); expect(enhancedResponse.inlineDataParts()).to.be.undefined; + expect(enhancedResponse.thoughtSummary()).to.be.undefined; }); it('good response functionCall/text', async () => { const enhancedResponse = addHelpers(fakeResponseMixed2); @@ -221,6 +240,7 @@ describe('response-helpers methods', () => { ]); expect(enhancedResponse.text()).to.equal('some text'); expect(enhancedResponse.inlineDataParts()).to.be.undefined; + expect(enhancedResponse.thoughtSummary()).to.be.undefined; }); it('good response text/functionCall/text', async () => { const enhancedResponse = addHelpers(fakeResponseMixed3); @@ -228,17 +248,20 @@ describe('response-helpers methods', () => { functionCallPart1.functionCall ]); expect(enhancedResponse.text()).to.equal('some text and more text'); + expect(enhancedResponse.thoughtSummary()).to.be.undefined; expect(enhancedResponse.inlineDataParts()).to.be.undefined; }); it('bad response safety', async () => { const enhancedResponse = addHelpers(badFakeResponse); expect(enhancedResponse.text).to.throw('SAFETY'); + expect(enhancedResponse.thoughtSummary).to.throw('SAFETY'); expect(enhancedResponse.functionCalls).to.throw('SAFETY'); expect(enhancedResponse.inlineDataParts).to.throw('SAFETY'); }); it('good response inlineData', async () => { const enhancedResponse = addHelpers(fakeResponseInlineData); expect(enhancedResponse.text()).to.equal(''); + expect(enhancedResponse.thoughtSummary()).to.be.undefined; expect(enhancedResponse.functionCalls()).to.be.undefined; expect(enhancedResponse.inlineDataParts()).to.deep.equal([ inlineDataPart1, @@ -248,11 +271,19 @@ describe('response-helpers methods', () => { it('good response text/inlineData', async () => { const enhancedResponse = addHelpers(fakeResponseTextAndInlineData); expect(enhancedResponse.text()).to.equal('Describe this:'); + expect(enhancedResponse.thoughtSummary()).to.be.undefined; expect(enhancedResponse.functionCalls()).to.be.undefined; expect(enhancedResponse.inlineDataParts()).to.deep.equal([ inlineDataPart1 ]); }); + it('good response text/thought', async () => { + const enhancedResponse = addHelpers(fakeResponseThoughts); + expect(enhancedResponse.text()).to.equal('Some text'); + expect(enhancedResponse.thoughtSummary()).to.equal('and some thoughts'); + expect(enhancedResponse.functionCalls()).to.be.undefined; + expect(enhancedResponse.inlineDataParts()).to.be.undefined; + }); }); describe('getBlockString', () => { it('has no promptFeedback or bad finishReason', async () => { diff --git a/packages/ai/src/requests/response-helpers.ts b/packages/ai/src/requests/response-helpers.ts index 1c723137aac..394e27cbf9f 100644 --- a/packages/ai/src/requests/response-helpers.ts +++ b/packages/ai/src/requests/response-helpers.ts @@ -24,12 +24,43 @@ import { ImagenGCSImage, ImagenInlineImage, AIErrorCode, - InlineDataPart + InlineDataPart, + Part } from '../types'; import { AIError } from '../errors'; import { logger } from '../logger'; import { ImagenResponseInternal } from '../types/internal'; +/** + * Check that at least one candidate exists and does not have a bad + * finish reason. Warns if multiple candidates exist. + */ +function hasValidCandidates(response: GenerateContentResponse): boolean { + if (response.candidates && response.candidates.length > 0) { + if (response.candidates.length > 1) { + logger.warn( + `This response had ${response.candidates.length} ` + + `candidates. Returning text from the first candidate only. ` + + `Access response.candidates directly to use the other candidates.` + ); + } + if (hadBadFinishReason(response.candidates[0])) { + throw new AIError( + AIErrorCode.RESPONSE_ERROR, + `Response error: ${formatBlockErrorMessage( + response + )}. Response body stored in error.response`, + { + response + } + ); + } + return true; + } else { + return false; + } +} + /** * Creates an EnhancedGenerateContentResponse object that has helper functions and * other modifications that improve usability. @@ -59,26 +90,8 @@ export function addHelpers( response: GenerateContentResponse ): EnhancedGenerateContentResponse { (response as EnhancedGenerateContentResponse).text = () => { - if (response.candidates && response.candidates.length > 0) { - if (response.candidates.length > 1) { - logger.warn( - `This response had ${response.candidates.length} ` + - `candidates. Returning text from the first candidate only. ` + - `Access response.candidates directly to use the other candidates.` - ); - } - if (hadBadFinishReason(response.candidates[0])) { - throw new AIError( - AIErrorCode.RESPONSE_ERROR, - `Response error: ${formatBlockErrorMessage( - response - )}. Response body stored in error.response`, - { - response - } - ); - } - return getText(response, false); + if (hasValidCandidates(response)) { + return getText(response, part => !part.thought); } else if (response.promptFeedback) { throw new AIError( AIErrorCode.RESPONSE_ERROR, @@ -90,28 +103,25 @@ export function addHelpers( } return ''; }; + (response as EnhancedGenerateContentResponse).thoughtSummary = () => { + if (hasValidCandidates(response)) { + const result = getText(response, part => !!part.thought); + return result === '' ? undefined : result; + } else if (response.promptFeedback) { + throw new AIError( + AIErrorCode.RESPONSE_ERROR, + `Thought summary not available. ${formatBlockErrorMessage(response)}`, + { + response + } + ); + } + return undefined; + }; (response as EnhancedGenerateContentResponse).inlineDataParts = (): | InlineDataPart[] | undefined => { - if (response.candidates && response.candidates.length > 0) { - if (response.candidates.length > 1) { - logger.warn( - `This response had ${response.candidates.length} ` + - `candidates. Returning data from the first candidate only. ` + - `Access response.candidates directly to use the other candidates.` - ); - } - if (hadBadFinishReason(response.candidates[0])) { - throw new AIError( - AIErrorCode.RESPONSE_ERROR, - `Response error: ${formatBlockErrorMessage( - response - )}. Response body stored in error.response`, - { - response - } - ); - } + if (hasValidCandidates(response)) { return getInlineDataParts(response); } else if (response.promptFeedback) { throw new AIError( @@ -125,25 +135,7 @@ export function addHelpers( return undefined; }; (response as EnhancedGenerateContentResponse).functionCalls = () => { - if (response.candidates && response.candidates.length > 0) { - if (response.candidates.length > 1) { - logger.warn( - `This response had ${response.candidates.length} ` + - `candidates. Returning function calls from the first candidate only. ` + - `Access response.candidates directly to use the other candidates.` - ); - } - if (hadBadFinishReason(response.candidates[0])) { - throw new AIError( - AIErrorCode.RESPONSE_ERROR, - `Response error: ${formatBlockErrorMessage( - response - )}. Response body stored in error.response`, - { - response - } - ); - } + if (hasValidCandidates(response)) { return getFunctionCalls(response); } else if (response.promptFeedback) { throw new AIError( @@ -163,19 +155,17 @@ export function addHelpers( * Returns all text from the first candidate's parts, filtering by whether they * are part of the model's 'thought' process. * - * @param response - The {@link GenerateContentResponse} from which to extract text. - * @param isThought - If `true`, extracts text from `thought` parts of the response, - * which represent the model's internal reasoning. If `false`, extracts text from - * regular response parts. + * @param response - The `GenerateContentResponse` from which to extract text. + * @param partFilter - Only return `Part`s for which this returns true */ export function getText( response: GenerateContentResponse, - isThought: boolean + partFilter: (part: Part) => boolean ): string { const textStrings = []; if (response.candidates?.[0].content?.parts) { for (const part of response.candidates?.[0].content?.parts) { - if (part.text && (part.thought ?? false) === isThought) { + if (part.text && partFilter(part)) { textStrings.push(part.text); } } diff --git a/packages/ai/src/types/content.ts b/packages/ai/src/types/content.ts index dadb700d474..527be27f952 100644 --- a/packages/ai/src/types/content.ts +++ b/packages/ai/src/types/content.ts @@ -48,7 +48,6 @@ export interface TextPart { functionCall?: never; functionResponse?: never; thought?: boolean; - thoughtSignature?: string; } /** @@ -65,7 +64,6 @@ export interface InlineDataPart { */ videoMetadata?: VideoMetadata; thought?: boolean; - thoughtSignature?: string; } /** @@ -95,7 +93,6 @@ export interface FunctionCallPart { functionCall: FunctionCall; functionResponse?: never; thought?: boolean; - thoughtSignature?: string; } /** @@ -108,7 +105,6 @@ export interface FunctionResponsePart { functionCall?: never; functionResponse: FunctionResponse; thought?: boolean; - thoughtSignature?: string; } /** @@ -122,7 +118,6 @@ export interface FileDataPart { functionResponse?: never; fileData: FileData; thought?: boolean; - thoughtSignature?: string; } /** diff --git a/packages/ai/src/types/requests.ts b/packages/ai/src/types/requests.ts index a9161b2280b..5927e75d06e 100644 --- a/packages/ai/src/types/requests.ts +++ b/packages/ai/src/types/requests.ts @@ -298,6 +298,7 @@ export interface ThinkingConfig { /** * Whether to include "thought summaries" in the model's response. * + * @remarks * Thought summaries provide a brief overview of the model's internal thinking process, * offering insight into how it arrived at the final answer. This can be useful for * debugging, understanding the model's reasoning, and verifying its accuracy. diff --git a/packages/ai/src/types/responses.ts b/packages/ai/src/types/responses.ts index e249bd873da..27da861758d 100644 --- a/packages/ai/src/types/responses.ts +++ b/packages/ai/src/types/responses.ts @@ -68,7 +68,27 @@ export interface EnhancedGenerateContentResponse * @throws If the prompt or candidate was blocked. */ inlineDataParts: () => InlineDataPart[] | undefined; + /** + * Aggregates and returns all {@link FunctionCall}s from the {@link GenerateContentResponse}'s + * first candidate. + * + * @throws If the prompt or candidate was blocked. + */ functionCalls: () => FunctionCall[] | undefined; + /** + * Aggregates and returns all {@link TextPart}s with their `thought` property set + * to `true` from the {@link GenerateContentResponse}'s first candidate. + * + * @throws If the prompt or candidate was blocked. + * + * @remarks + * Thought summaries provide a brief overview of the model's internal thinking process, + * offering insight into how it arrived at the final answer. This can be useful for + * debugging, understanding the model's reasoning, and verifying its accuracy. + * + * Thoughts will only be included if {@link ThinkingConfig.includeThoughts} is + * set to `true`. + */ thoughtSummary: () => string | undefined; } From 364370e17447fbb9252bae7af80031990388b18b Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 13 Aug 2025 09:58:03 -0700 Subject: [PATCH 4/6] Add logic to account for thought and thoughtSignature in chat history --- common/api-review/ai.api.md | 10 ++++ .../src/methods/chat-session-helpers.test.ts | 60 ++++++++++++++++++- .../ai/src/methods/chat-session-helpers.ts | 11 ++-- packages/ai/src/methods/chat-session.test.ts | 49 ++++++++++++++- packages/ai/src/types/content.ts | 20 +++++++ 5 files changed, 143 insertions(+), 7 deletions(-) diff --git a/common/api-review/ai.api.md b/common/api-review/ai.api.md index 23f2d96fede..728848cc128 100644 --- a/common/api-review/ai.api.md +++ b/common/api-review/ai.api.md @@ -242,6 +242,8 @@ export interface FileDataPart { text?: never; // (undocumented) thought?: boolean; + // @internal (undocumented) + thoughtSignature?: never; } // @public @@ -298,6 +300,8 @@ export interface FunctionCallPart { text?: never; // (undocumented) thought?: boolean; + // @internal (undocumented) + thoughtSignature?: never; } // @public @@ -332,6 +336,8 @@ export interface FunctionResponsePart { text?: never; // (undocumented) thought?: boolean; + // @internal (undocumented) + thoughtSignature?: never; } // @public @@ -699,6 +705,8 @@ export interface InlineDataPart { text?: never; // (undocumented) thought?: boolean; + // @internal (undocumented) + thoughtSignature?: never; videoMetadata?: VideoMetadata; } @@ -967,6 +975,8 @@ export interface TextPart { text: string; // (undocumented) thought?: boolean; + // @internal (undocumented) + thoughtSignature?: string; } // @public diff --git a/packages/ai/src/methods/chat-session-helpers.test.ts b/packages/ai/src/methods/chat-session-helpers.test.ts index feab9fc3b05..e64f3e84e2d 100644 --- a/packages/ai/src/methods/chat-session-helpers.test.ts +++ b/packages/ai/src/methods/chat-session-helpers.test.ts @@ -22,7 +22,11 @@ import { FirebaseError } from '@firebase/util'; describe('chat-session-helpers', () => { describe('validateChatHistory', () => { - const TCS: Array<{ history: Content[]; isValid: boolean }> = [ + const TCS: Array<{ + history: Content[]; + isValid: boolean; + errorShouldInclude?: string; + }> = [ { history: [{ role: 'user', parts: [{ text: 'hi' }] }], isValid: true @@ -99,19 +103,23 @@ describe('chat-session-helpers', () => { { //@ts-expect-error history: [{ role: 'user', parts: '' }], + errorShouldInclude: `array of Parts`, isValid: false }, { //@ts-expect-error history: [{ role: 'user' }], + errorShouldInclude: `array of Parts`, isValid: false }, { history: [{ role: 'user', parts: [] }], + errorShouldInclude: `at least one part`, isValid: false }, { history: [{ role: 'model', parts: [{ text: 'hi' }] }], + errorShouldInclude: `model`, isValid: false }, { @@ -125,6 +133,7 @@ describe('chat-session-helpers', () => { ] } ], + errorShouldInclude: `function`, isValid: false }, { @@ -132,6 +141,7 @@ describe('chat-session-helpers', () => { { role: 'user', parts: [{ text: 'hi' }] }, { role: 'user', parts: [{ text: 'hi' }] } ], + errorShouldInclude: `can't follow 'user'`, isValid: false }, { @@ -140,6 +150,45 @@ describe('chat-session-helpers', () => { { role: 'model', parts: [{ text: 'hi' }] }, { role: 'model', parts: [{ text: 'hi' }] } ], + errorShouldInclude: `can't follow 'model'`, + isValid: false + }, + { + history: [ + { role: 'user', parts: [{ text: 'hi' }] }, + { + role: 'model', + parts: [ + { text: 'hi' }, + { + text: 'thought about hi', + thought: true, + thoughtSignature: 'thought signature' + } + ] + } + ], + isValid: true + }, + { + history: [ + { + role: 'user', + parts: [{ text: 'hi', thought: true, thoughtSignature: 'sig' }] + }, + { + role: 'model', + parts: [ + { text: 'hi' }, + { + text: 'thought about hi', + thought: true, + thoughtSignature: 'thought signature' + } + ] + } + ], + errorShouldInclude: 'thought', isValid: false } ]; @@ -149,7 +198,14 @@ describe('chat-session-helpers', () => { if (tc.isValid) { expect(fn).to.not.throw(); } else { - expect(fn).to.throw(FirebaseError); + try { + fn(); + } catch (e) { + expect(e).to.be.instanceOf(FirebaseError); + if (e instanceof FirebaseError && tc.errorShouldInclude) { + expect(e.message).to.include(tc.errorShouldInclude); + } + } } }); }); diff --git a/packages/ai/src/methods/chat-session-helpers.ts b/packages/ai/src/methods/chat-session-helpers.ts index 34cc6d32fa1..709f616f3c0 100644 --- a/packages/ai/src/methods/chat-session-helpers.ts +++ b/packages/ai/src/methods/chat-session-helpers.ts @@ -24,13 +24,15 @@ const VALID_PART_FIELDS: Array = [ 'text', 'inlineData', 'functionCall', - 'functionResponse' + 'functionResponse', + 'thought', + 'thoughtSignature' ]; const VALID_PARTS_PER_ROLE: { [key in Role]: Array } = { user: ['text', 'inlineData'], function: ['functionResponse'], - model: ['text', 'functionCall'], + model: ['text', 'functionCall', 'thought', 'thoughtSignature'], // System instructions shouldn't be in history anyway. system: ['text'] }; @@ -65,7 +67,7 @@ export function validateChatHistory(history: Content[]): void { if (!Array.isArray(parts)) { throw new AIError( AIErrorCode.INVALID_CONTENT, - `Content should have 'parts' but property with an array of Parts` + `Content should have 'parts' property with an array of Parts` ); } @@ -81,7 +83,8 @@ export function validateChatHistory(history: Content[]): void { inlineData: 0, functionCall: 0, functionResponse: 0, - thought: 0 + thought: 0, + thoughtSignature: 0 }; for (const part of parts) { diff --git a/packages/ai/src/methods/chat-session.test.ts b/packages/ai/src/methods/chat-session.test.ts index 0564aa84ed6..41316e5a3b2 100644 --- a/packages/ai/src/methods/chat-session.test.ts +++ b/packages/ai/src/methods/chat-session.test.ts @@ -20,7 +20,7 @@ import { match, restore, stub, useFakeTimers } from 'sinon'; import sinonChai from 'sinon-chai'; import chaiAsPromised from 'chai-as-promised'; import * as generateContentMethods from './generate-content'; -import { GenerateContentStreamResult } from '../types'; +import { Content, GenerateContentStreamResult } from '../types'; import { ChatSession } from './chat-session'; import { ApiSettings } from '../types/internal'; import { VertexAIBackend } from '../backend'; @@ -54,6 +54,53 @@ describe('ChatSession', () => { match.any ); }); + it('adds message and response to history', async () => { + const fakeContent: Content = { + role: 'model', + parts: [ + { text: 'hi' }, + { + text: 'thought about hi', + thoughtSignature: 'thought signature' + } + ] + }; + const fakeResponse = { + candidates: [ + { + index: 1, + content: fakeContent + } + ] + }; + const generateContentStub = stub( + generateContentMethods, + 'generateContent' + ).resolves({ + // @ts-ignore + response: fakeResponse + }); + const chatSession = new ChatSession(fakeApiSettings, 'a-model'); + const result = await chatSession.sendMessage('hello'); + // @ts-ignore + expect(result.response).to.equal(fakeResponse); + // Test: stores history correctly? + const history = await chatSession.getHistory(); + expect(history[0].role).to.equal('user'); + expect(history[0].parts[0].text).to.equal('hello'); + expect(history[1]).to.deep.equal(fakeResponse.candidates[0].content); + // Test: sends history correctly? + await chatSession.sendMessage('hello 2'); + expect(generateContentStub.args[1][2].contents[0].parts[0].text).to.equal( + 'hello' + ); + expect(generateContentStub.args[1][2].contents[1]).to.deep.equal( + fakeResponse.candidates[0].content + ); + expect(generateContentStub.args[1][2].contents[2].parts[0].text).to.equal( + 'hello 2' + ); + }); }); describe('sendMessageStream()', () => { it('generateContentStream errors should be catchable', async () => { diff --git a/packages/ai/src/types/content.ts b/packages/ai/src/types/content.ts index 527be27f952..a08af95086c 100644 --- a/packages/ai/src/types/content.ts +++ b/packages/ai/src/types/content.ts @@ -48,6 +48,10 @@ export interface TextPart { functionCall?: never; functionResponse?: never; thought?: boolean; + /** + * @internal + */ + thoughtSignature?: string; } /** @@ -64,6 +68,10 @@ export interface InlineDataPart { */ videoMetadata?: VideoMetadata; thought?: boolean; + /** + * @internal + */ + thoughtSignature?: never; } /** @@ -93,6 +101,10 @@ export interface FunctionCallPart { functionCall: FunctionCall; functionResponse?: never; thought?: boolean; + /** + * @internal + */ + thoughtSignature?: never; } /** @@ -105,6 +117,10 @@ export interface FunctionResponsePart { functionCall?: never; functionResponse: FunctionResponse; thought?: boolean; + /** + * @internal + */ + thoughtSignature?: never; } /** @@ -118,6 +134,10 @@ export interface FileDataPart { functionResponse?: never; fileData: FileData; thought?: boolean; + /** + * @internal + */ + thoughtSignature?: never; } /** From 2a828106e417c12cb766bf047556a064d47cf95e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 14 Aug 2025 08:50:39 -0700 Subject: [PATCH 5/6] Address PR comments --- docs-devsite/ai.enhancedgeneratecontentresponse.md | 12 ++++++------ packages/ai/src/requests/response-helpers.ts | 8 ++++---- packages/ai/src/types/responses.ts | 8 +++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs-devsite/ai.enhancedgeneratecontentresponse.md b/docs-devsite/ai.enhancedgeneratecontentresponse.md index 2ae62f952e1..90ec6dc5135 100644 --- a/docs-devsite/ai.enhancedgeneratecontentresponse.md +++ b/docs-devsite/ai.enhancedgeneratecontentresponse.md @@ -23,14 +23,14 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse | Property | Type | Description | | --- | --- | --- | -| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | Aggregates and returns all [FunctionCall](./ai.functioncall.md#functioncall_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | -| [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () => [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)\[\] \| undefined | Aggregates and returns all [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | +| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | +| [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () => [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)\[\] \| undefined | Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | | [text](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsetext) | () => string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. | -| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () => string \| undefined | Aggregates and returns all [TextPart](./ai.textpart.md#textpart_interface)s with their thought property set to true from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | +| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () => string \| undefined | Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their thought property set to true from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | ## EnhancedGenerateContentResponse.functionCalls -Aggregates and returns all [FunctionCall](./ai.functioncall.md#functioncall_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. Signature: @@ -40,7 +40,7 @@ functionCalls: () => FunctionCall[] | undefined; ## EnhancedGenerateContentResponse.inlineDataParts -Aggregates and returns all [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)s from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. Signature: @@ -60,7 +60,7 @@ text: () => string; ## EnhancedGenerateContentResponse.thoughtSummary -Aggregates and returns all [TextPart](./ai.textpart.md#textpart_interface)s with their `thought` property set to `true` from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their `thought` property set to `true` from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. Thought summaries provide a brief overview of the model's internal thinking process, offering insight into how it arrived at the final answer. This can be useful for debugging, understanding the model's reasoning, and verifying its accuracy. diff --git a/packages/ai/src/requests/response-helpers.ts b/packages/ai/src/requests/response-helpers.ts index 394e27cbf9f..16d55613487 100644 --- a/packages/ai/src/requests/response-helpers.ts +++ b/packages/ai/src/requests/response-helpers.ts @@ -152,8 +152,8 @@ export function addHelpers( } /** - * Returns all text from the first candidate's parts, filtering by whether they - * are part of the model's 'thought' process. + * Returns all text from the first candidate's parts, filtering by whether + * `partFilter()` returns true. * * @param response - The `GenerateContentResponse` from which to extract text. * @param partFilter - Only return `Part`s for which this returns true @@ -178,7 +178,7 @@ export function getText( } /** - * Returns {@link FunctionCall}s associated with first candidate. + * Returns every {@link FunctionCall} associated with first candidate. */ export function getFunctionCalls( response: GenerateContentResponse @@ -199,7 +199,7 @@ export function getFunctionCalls( } /** - * Returns {@link InlineDataPart}s in the first candidate if present. + * Returns every {@link InlineDataPart} in the first candidate if present. * * @internal */ diff --git a/packages/ai/src/types/responses.ts b/packages/ai/src/types/responses.ts index 27da861758d..2232f849240 100644 --- a/packages/ai/src/types/responses.ts +++ b/packages/ai/src/types/responses.ts @@ -60,23 +60,21 @@ export interface EnhancedGenerateContentResponse */ text: () => string; /** - * Aggregates and returns all {@link InlineDataPart}s from the {@link GenerateContentResponse}'s + * Aggregates and returns every {@link InlineDataPart} from the {@link GenerateContentResponse}'s * first candidate. * - * @returns An array of {@link InlineDataPart}s containing data from the response, if available. - * * @throws If the prompt or candidate was blocked. */ inlineDataParts: () => InlineDataPart[] | undefined; /** - * Aggregates and returns all {@link FunctionCall}s from the {@link GenerateContentResponse}'s + * Aggregates and returns every {@link FunctionCall} from the {@link GenerateContentResponse}'s * first candidate. * * @throws If the prompt or candidate was blocked. */ functionCalls: () => FunctionCall[] | undefined; /** - * Aggregates and returns all {@link TextPart}s with their `thought` property set + * Aggregates and returns every {@link TextPart} with their `thought` property set * to `true` from the {@link GenerateContentResponse}'s first candidate. * * @throws If the prompt or candidate was blocked. From fb7085aac8778b3503417b51aab5433fa2891d0e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 19 Aug 2025 10:11:04 -0700 Subject: [PATCH 6/6] fix apostrophe s link --- docs-devsite/ai.enhancedgeneratecontentresponse.md | 12 ++++++------ packages/ai/src/types/responses.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs-devsite/ai.enhancedgeneratecontentresponse.md b/docs-devsite/ai.enhancedgeneratecontentresponse.md index 90ec6dc5135..9e947add0cb 100644 --- a/docs-devsite/ai.enhancedgeneratecontentresponse.md +++ b/docs-devsite/ai.enhancedgeneratecontentresponse.md @@ -23,14 +23,14 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse | Property | Type | Description | | --- | --- | --- | -| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | -| [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () => [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)\[\] \| undefined | Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | +| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () => [FunctionCall](./ai.functioncall.md#functioncall_interface)\[\] \| undefined | Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). | +| [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () => [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface)\[\] \| undefined | Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). | | [text](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsetext) | () => string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. | -| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () => string \| undefined | Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their thought property set to true from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. | +| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () => string \| undefined | Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their thought property set to true from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). | ## EnhancedGenerateContentResponse.functionCalls -Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [FunctionCall](./ai.functioncall.md#functioncall_interface) from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). Signature: @@ -40,7 +40,7 @@ functionCalls: () => FunctionCall[] | undefined; ## EnhancedGenerateContentResponse.inlineDataParts -Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [InlineDataPart](./ai.inlinedatapart.md#inlinedatapart_interface) from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). Signature: @@ -60,7 +60,7 @@ text: () => string; ## EnhancedGenerateContentResponse.thoughtSummary -Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their `thought` property set to `true` from the [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface)'s first candidate. +Aggregates and returns every [TextPart](./ai.textpart.md#textpart_interface) with their `thought` property set to `true` from the first candidate of [GenerateContentResponse](./ai.generatecontentresponse.md#generatecontentresponse_interface). Thought summaries provide a brief overview of the model's internal thinking process, offering insight into how it arrived at the final answer. This can be useful for debugging, understanding the model's reasoning, and verifying its accuracy. diff --git a/packages/ai/src/types/responses.ts b/packages/ai/src/types/responses.ts index 2232f849240..d9b76155a3a 100644 --- a/packages/ai/src/types/responses.ts +++ b/packages/ai/src/types/responses.ts @@ -60,22 +60,22 @@ export interface EnhancedGenerateContentResponse */ text: () => string; /** - * Aggregates and returns every {@link InlineDataPart} from the {@link GenerateContentResponse}'s - * first candidate. + * Aggregates and returns every {@link InlineDataPart} from the first candidate of + * {@link GenerateContentResponse}. * * @throws If the prompt or candidate was blocked. */ inlineDataParts: () => InlineDataPart[] | undefined; /** - * Aggregates and returns every {@link FunctionCall} from the {@link GenerateContentResponse}'s - * first candidate. + * Aggregates and returns every {@link FunctionCall} from the first candidate of + * {@link GenerateContentResponse}. * * @throws If the prompt or candidate was blocked. */ functionCalls: () => FunctionCall[] | undefined; /** * Aggregates and returns every {@link TextPart} with their `thought` property set - * to `true` from the {@link GenerateContentResponse}'s first candidate. + * to `true` from the first candidate of {@link GenerateContentResponse}. * * @throws If the prompt or candidate was blocked. *