Skip to content

[Firebase AI] Add thought summary and signature support #9192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/brave-llamas-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@firebase/ai': minor
'firebase': minor
---

Add `thoughtSummary()` convenience method to `EnhancedGenerateContentResponse`.
23 changes: 22 additions & 1 deletion common/api-review/ai.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ export { Date_2 as Date }

// @public
export interface EnhancedGenerateContentResponse extends GenerateContentResponse {
// (undocumented)
functionCalls: () => FunctionCall[] | undefined;
inlineDataParts: () => InlineDataPart[] | undefined;
text: () => string;
thoughtSummary: () => string | undefined;
}

// @public
Expand Down Expand Up @@ -249,6 +249,10 @@ export interface FileDataPart {
inlineData?: never;
// (undocumented)
text?: never;
// (undocumented)
thought?: boolean;
// @internal (undocumented)
thoughtSignature?: never;
}

// @public
Expand Down Expand Up @@ -303,6 +307,10 @@ export interface FunctionCallPart {
inlineData?: never;
// (undocumented)
text?: never;
// (undocumented)
thought?: boolean;
// @internal (undocumented)
thoughtSignature?: never;
}

// @public
Expand Down Expand Up @@ -335,6 +343,10 @@ export interface FunctionResponsePart {
inlineData?: never;
// (undocumented)
text?: never;
// (undocumented)
thought?: boolean;
// @internal (undocumented)
thoughtSignature?: never;
}

// @public
Expand Down Expand Up @@ -717,6 +729,10 @@ export interface InlineDataPart {
inlineData: GenerativeContentBlob;
// (undocumented)
text?: never;
// (undocumented)
thought?: boolean;
// @internal (undocumented)
thoughtSignature?: never;
videoMetadata?: VideoMetadata;
}

Expand Down Expand Up @@ -1048,10 +1064,15 @@ export interface TextPart {
inlineData?: never;
// (undocumented)
text: string;
// (undocumented)
thought?: boolean;
// @internal (undocumented)
thoughtSignature?: string;
}

// @public
export interface ThinkingConfig {
includeThoughts?: boolean;
thinkingBudget?: number;
}

Expand Down
19 changes: 18 additions & 1 deletion docs-devsite/ai.enhancedgeneratecontentresponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ export interface EnhancedGenerateContentResponse extends GenerateContentResponse

| Property | Type | Description |
| --- | --- | --- |
| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () =&gt; [FunctionCall](./ai.functioncall.md#functioncall_interface)<!-- -->\[\] \| undefined | |
| [functionCalls](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsefunctioncalls) | () =&gt; [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. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT BLOCKING

...returns all FunctionCalls from...

We shouldn't pluralize like this.

Instead, tweak the sentence so that you're not pluralizing. Something like:
"... returns every FunctionCall from the..."

(same comment for GenerateContentResponse and a couple other similar instances throughout)

| [inlineDataParts](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponseinlinedataparts) | () =&gt; [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) | () =&gt; string | Returns the text string from the response, if available. Throws if the prompt or candidate was blocked. |
| [thoughtSummary](./ai.enhancedgeneratecontentresponse.md#enhancedgeneratecontentresponsethoughtsummary) | () =&gt; string \| undefined | Aggregates and returns all [TextPart](./ai.textpart.md#textpart_interface)<!-- -->s with their <code>thought</code> property set to <code>true</code> 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.

<b>Signature:</b>

```typescript
Expand All @@ -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`<!-- -->.

<b>Signature:</b>

```typescript
thoughtSummary: () => string | undefined;
```
9 changes: 9 additions & 0 deletions docs-devsite/ai.filedatapart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -67,3 +68,11 @@ inlineData?: never;
```typescript
text?: never;
```

## FileDataPart.thought

<b>Signature:</b>

```typescript
thought?: boolean;
```
9 changes: 9 additions & 0 deletions docs-devsite/ai.functioncallpart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -58,3 +59,11 @@ inlineData?: never;
```typescript
text?: never;
```

## FunctionCallPart.thought

<b>Signature:</b>

```typescript
thought?: boolean;
```
9 changes: 9 additions & 0 deletions docs-devsite/ai.functionresponsepart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -58,3 +59,11 @@ inlineData?: never;
```typescript
text?: never;
```

## FunctionResponsePart.thought

<b>Signature:</b>

```typescript
thought?: boolean;
```
9 changes: 9 additions & 0 deletions docs-devsite/ai.inlinedatapart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>inlineData</code> is a video. |

## InlineDataPart.functionCall
Expand Down Expand Up @@ -60,6 +61,14 @@ inlineData: GenerativeContentBlob;
text?: never;
```

## InlineDataPart.thought

<b>Signature:</b>

```typescript
thought?: boolean;
```

## InlineDataPart.videoMetadata

Applicable if `inlineData` is a video.
Expand Down
9 changes: 9 additions & 0 deletions docs-devsite/ai.textpart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -58,3 +59,11 @@ inlineData?: never;
```typescript
text: string;
```

## TextPart.thought

<b>Signature:</b>

```typescript
thought?: boolean;
```
13 changes: 13 additions & 0 deletions docs-devsite/ai.thinkingconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<b>Signature:</b>

```typescript
includeThoughts?: boolean;
```

## ThinkingConfig.thinkingBudget

The thinking budget, in tokens.
Expand Down
60 changes: 58 additions & 2 deletions packages/ai/src/methods/chat-session-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
},
{
Expand All @@ -125,13 +133,15 @@ describe('chat-session-helpers', () => {
]
}
],
errorShouldInclude: `function`,
isValid: false
},
{
history: [
{ role: 'user', parts: [{ text: 'hi' }] },
{ role: 'user', parts: [{ text: 'hi' }] }
],
errorShouldInclude: `can't follow 'user'`,
isValid: false
},
{
Expand All @@ -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
}
];
Expand All @@ -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);
}
}
}
});
});
Expand Down
12 changes: 8 additions & 4 deletions packages/ai/src/methods/chat-session-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ const VALID_PART_FIELDS: Array<keyof Part> = [
'text',
'inlineData',
'functionCall',
'functionResponse'
'functionResponse',
'thought',
'thoughtSignature'
];

const VALID_PARTS_PER_ROLE: { [key in Role]: Array<keyof Part> } = {
user: ['text', 'inlineData'],
function: ['functionResponse'],
model: ['text', 'functionCall'],
model: ['text', 'functionCall', 'thought', 'thoughtSignature'],
// System instructions shouldn't be in history anyway.
system: ['text']
};
Expand Down Expand Up @@ -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`
);
}

Expand All @@ -80,7 +82,9 @@ export function validateChatHistory(history: Content[]): void {
text: 0,
inlineData: 0,
functionCall: 0,
functionResponse: 0
functionResponse: 0,
thought: 0,
thoughtSignature: 0
};

for (const part of parts) {
Expand Down
Loading
Loading