@sqlrooms/ai-core 0.26.0-rc.2 → 0.26.0-rc.3
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/README.md +238 -348
- package/dist/AiSlice.d.ts +37 -7
- package/dist/AiSlice.d.ts.map +1 -1
- package/dist/AiSlice.js +203 -147
- package/dist/AiSlice.js.map +1 -1
- package/dist/chatTransport.d.ts +48 -0
- package/dist/chatTransport.d.ts.map +1 -0
- package/dist/chatTransport.js +270 -0
- package/dist/chatTransport.js.map +1 -0
- package/dist/components/AiThinkingDots.d.ts +10 -0
- package/dist/components/AiThinkingDots.d.ts.map +1 -0
- package/dist/components/AiThinkingDots.js +18 -0
- package/dist/components/AiThinkingDots.js.map +1 -0
- package/dist/components/AnalysisResult.d.ts +1 -2
- package/dist/components/AnalysisResult.d.ts.map +1 -1
- package/dist/components/AnalysisResult.js +48 -18
- package/dist/components/AnalysisResult.js.map +1 -1
- package/dist/components/AnalysisResultsContainer.d.ts.map +1 -1
- package/dist/components/AnalysisResultsContainer.js +5 -10
- package/dist/components/AnalysisResultsContainer.js.map +1 -1
- package/dist/components/QueryControls.d.ts.map +1 -1
- package/dist/components/QueryControls.js +6 -3
- package/dist/components/QueryControls.js.map +1 -1
- package/dist/components/ToolCallInfo.d.ts +25 -0
- package/dist/components/ToolCallInfo.d.ts.map +1 -0
- package/dist/components/ToolCallInfo.js +32 -0
- package/dist/components/ToolCallInfo.js.map +1 -0
- package/dist/components/tools/ToolResult.d.ts +12 -7
- package/dist/components/tools/ToolResult.d.ts.map +1 -1
- package/dist/components/tools/ToolResult.js +8 -6
- package/dist/components/tools/ToolResult.js.map +1 -1
- package/dist/hooks/useAiChat.d.ts +60 -0
- package/dist/hooks/useAiChat.d.ts.map +1 -0
- package/dist/hooks/useAiChat.js +92 -0
- package/dist/hooks/useAiChat.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +23 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -1
- package/package.json +15 -14
- package/dist/analysis.d.ts +0 -51
- package/dist/analysis.d.ts.map +0 -1
- package/dist/analysis.js +0 -43
- package/dist/analysis.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,48 +19,12 @@ npm install @sqlrooms/ai
|
|
|
19
19
|
yarn add @sqlrooms/ai
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Since version 0.8.2, you will need to install the LLM providers you want to use. For example, to use OpenAI, you can install the `@ai-sdk/openai` package:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm install @ai-sdk/openai
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Google LLM provider:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install @ai-sdk/google
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Anthropic LLM provider:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npm install @ai-sdk/anthropic
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
DeepSeek LLM provider:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
npm install @ai-sdk/deepseek
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
XAI LLM provider:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npm install @ai-sdk/xai
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
ollama LLM provider:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install ollama-ai-provider-v2
|
|
56
|
-
```
|
|
57
|
-
|
|
58
22
|
## Basic Usage
|
|
59
23
|
|
|
60
|
-
### Setting Up AI
|
|
24
|
+
### Setting Up SqlRooms AI Chat for Browser-only application
|
|
61
25
|
|
|
62
26
|
```tsx
|
|
63
|
-
import {createAiSlice} from '@sqlrooms/ai';
|
|
27
|
+
import {createAiSlice, createAiSettingsSlice} from '@sqlrooms/ai';
|
|
64
28
|
import {createRoomStore} from '@sqlrooms/room-shell';
|
|
65
29
|
|
|
66
30
|
// Create a room store with AI capabilities
|
|
@@ -71,22 +35,22 @@ const {roomStore, useRoomStore} = createRoomStore({
|
|
|
71
35
|
// Your room configuration
|
|
72
36
|
},
|
|
73
37
|
}),
|
|
38
|
+
// Ai model config slice
|
|
39
|
+
...createAiSettingsSlice({})(set, get, store),
|
|
74
40
|
// Add AI slice
|
|
75
41
|
...createAiSlice({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return process.env.OPENAI_API_KEY || '';
|
|
42
|
+
getInstructions: () => {
|
|
43
|
+
return `You are an AI assistant that can answer questions and help with tasks.`;
|
|
79
44
|
},
|
|
80
45
|
initialAnalysisPrompt: 'What insights can you provide from my data?',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Your custom tools
|
|
46
|
+
tools: {
|
|
47
|
+
// Your tools
|
|
84
48
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return
|
|
49
|
+
getInstructions: () => {
|
|
50
|
+
// add custom instructions here
|
|
51
|
+
return createDefaultAiInstructions(store);
|
|
88
52
|
},
|
|
89
|
-
}),
|
|
53
|
+
})(set, get, store),
|
|
90
54
|
});
|
|
91
55
|
|
|
92
56
|
function MyApp() {
|
|
@@ -98,311 +62,205 @@ function MyApp() {
|
|
|
98
62
|
}
|
|
99
63
|
```
|
|
100
64
|
|
|
101
|
-
###
|
|
65
|
+
### Setting Up SqlRooms AI Chat for Server-side application
|
|
102
66
|
|
|
103
|
-
|
|
67
|
+
- api/chat/route.ts
|
|
104
68
|
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
createSqlEditorSlice,
|
|
109
|
-
createDefaultSqlEditorConfig,
|
|
110
|
-
} from '@sqlrooms/sql-editor';
|
|
111
|
-
import {createRoomStore, createRoomShellSlice} from '@sqlrooms/room-shell';
|
|
112
|
-
|
|
113
|
-
// Define your application state type
|
|
114
|
-
export type RoomState = RoomState<RoomConfig> &
|
|
115
|
-
AiSliceState &
|
|
116
|
-
SqlEditorSliceState;
|
|
117
|
-
|
|
118
|
-
// Create the store with multiple slices
|
|
119
|
-
export const {roomStore, useRoomStore} = createRoomStore<RoomConfig, RoomState>(
|
|
120
|
-
(set, get, store) => ({
|
|
121
|
-
// Base room slice
|
|
122
|
-
...createRoomShellSlice({
|
|
123
|
-
config: {
|
|
124
|
-
...createDefaultSqlEditorConfig(),
|
|
125
|
-
},
|
|
126
|
-
}),
|
|
127
|
-
// AI slice
|
|
128
|
-
...createAiSlice({
|
|
129
|
-
config: {
|
|
130
|
-
// Optional: Pre-configured AI sessions
|
|
131
|
-
sessions: [
|
|
132
|
-
{
|
|
133
|
-
id: 'default-session',
|
|
134
|
-
name: 'Default Analysis',
|
|
135
|
-
modelProvider: 'openai',
|
|
136
|
-
model: 'gpt-4o',
|
|
137
|
-
analysisResults: [],
|
|
138
|
-
createdAt: new Date(),
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
currentSessionId: 'default-session',
|
|
142
|
-
}
|
|
143
|
-
getApiKey: (modelProvider) => {
|
|
144
|
-
// Return API key based on provider
|
|
145
|
-
return apiKeys[modelProvider] || '';
|
|
146
|
-
},
|
|
147
|
-
// Custom tools and instructions
|
|
148
|
-
}),
|
|
149
|
-
// SQL Editor slice
|
|
150
|
-
...createSqlEditorSlice(),
|
|
151
|
-
}),
|
|
152
|
-
);
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Using AI Query Controls
|
|
69
|
+
```typescript
|
|
70
|
+
export async function POST(req: Request) {
|
|
71
|
+
const {messages} = await req.json();
|
|
156
72
|
|
|
157
|
-
|
|
158
|
-
|
|
73
|
+
const stream = createUIMessageStream({
|
|
74
|
+
execute: async ({writer}) => {
|
|
75
|
+
const result = streamText({
|
|
76
|
+
model: openai('gpt-4.1'),
|
|
77
|
+
system: systemPrompt,
|
|
78
|
+
messages,
|
|
79
|
+
tools: {
|
|
80
|
+
// Your tools: remove exeucte for client tools so they run on the client side
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
writer.merge(result.toUIMessageStream({originalMessages: messages}));
|
|
84
|
+
},
|
|
85
|
+
});
|
|
159
86
|
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<div className="rounded-lg border p-4">
|
|
163
|
-
<h2 className="mb-4 text-xl font-bold">Ask AI</h2>
|
|
164
|
-
<QueryControls
|
|
165
|
-
placeholder="Ask a question about your data..."
|
|
166
|
-
onSubmit={(query) => console.log('Processing query:', query)}
|
|
167
|
-
/>
|
|
168
|
-
</div>
|
|
169
|
-
);
|
|
87
|
+
return stream.toUIMessageStreamResponse();
|
|
170
88
|
}
|
|
171
89
|
```
|
|
172
90
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
```tsx
|
|
176
|
-
import {AnalysisResultsContainer, AnalysisResult} from '@sqlrooms/ai';
|
|
91
|
+
- page.tsx
|
|
177
92
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
93
|
+
```typescript
|
|
94
|
+
const {roomStore, useRoomStore} = createRoomStore({
|
|
95
|
+
...createRoomShellSlice({
|
|
96
|
+
// Your room configuration
|
|
97
|
+
})(set, get, store),
|
|
98
|
+
...createAiSettingsSlice({
|
|
99
|
+
// Your AI settings
|
|
100
|
+
})(set, get, store),
|
|
101
|
+
...createAiSlice({
|
|
102
|
+
chatEndPoint: '/api/chat', // Point to the server-side endpoint
|
|
103
|
+
tools: {
|
|
104
|
+
// Your tools
|
|
105
|
+
},
|
|
106
|
+
})(set, get, store),
|
|
107
|
+
});
|
|
182
108
|
|
|
109
|
+
function MyApp() {
|
|
183
110
|
return (
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
{analysisResults.map((result) => (
|
|
188
|
-
<AnalysisResult key={result.id} result={result} />
|
|
189
|
-
))}
|
|
190
|
-
</AnalysisResultsContainer>
|
|
191
|
-
</div>
|
|
111
|
+
<RoomStateProvider roomStore={roomStore}>
|
|
112
|
+
<MyDataApp />
|
|
113
|
+
</RoomStateProvider>
|
|
192
114
|
);
|
|
193
115
|
}
|
|
194
116
|
```
|
|
195
117
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```tsx
|
|
199
|
-
function AiStatusIndicator() {
|
|
200
|
-
const isRunningAnalysis = useRoomStore((state) => state.ai.isRunningAnalysis);
|
|
201
|
-
const analysisPrompt = useRoomStore((state) => state.ai.analysisPrompt);
|
|
202
|
-
const currentSession = useRoomStore((state) => state.ai.getCurrentSession());
|
|
203
|
-
const lastResult =
|
|
204
|
-
currentSession?.analysisResults[currentSession.analysisResults.length - 1];
|
|
205
|
-
|
|
206
|
-
if (isRunningAnalysis) {
|
|
207
|
-
return <div>AI is analyzing your data...</div>;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (lastResult?.errorMessage) {
|
|
211
|
-
return <div>Error: {lastResult.errorMessage.message}</div>;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (analysisPrompt) {
|
|
215
|
-
return <div>Last query: "{analysisPrompt}"</div>;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return <div>Ask AI a question about your data</div>;
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## AiSlice API Reference
|
|
223
|
-
|
|
224
|
-
The AiSlice provides a comprehensive set of state fields and methods for managing AI interactions in your application.
|
|
225
|
-
|
|
226
|
-
### State Fields
|
|
227
|
-
|
|
228
|
-
#### `analysisPrompt`
|
|
229
|
-
|
|
230
|
-
The current prompt text entered by the user for analysis.
|
|
231
|
-
|
|
232
|
-
```tsx
|
|
233
|
-
const prompt = useRoomStore((state) => state.ai.analysisPrompt);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
#### `isRunningAnalysis`
|
|
237
|
-
|
|
238
|
-
Boolean flag indicating whether an analysis is currently in progress.
|
|
239
|
-
|
|
240
|
-
```tsx
|
|
241
|
-
const isRunning = useRoomStore((state) => state.ai.isRunningAnalysis);
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
#### `tools`
|
|
245
|
-
|
|
246
|
-
Record of available AI tools that can be used during analysis.
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
const availableTools = useRoomStore((state) => state.ai.tools);
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### `analysisAbortController`
|
|
253
|
-
|
|
254
|
-
Optional AbortController instance that can be used to cancel an ongoing analysis.
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
const abortController = useRoomStore(
|
|
258
|
-
(state) => state.ai.analysisAbortController,
|
|
259
|
-
);
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Methods
|
|
263
|
-
|
|
264
|
-
#### `setAnalysisPrompt(prompt: string)`
|
|
265
|
-
|
|
266
|
-
Sets the current analysis prompt text.
|
|
267
|
-
|
|
268
|
-
```tsx
|
|
269
|
-
const setPrompt = useRoomStore((state) => state.ai.setAnalysisPrompt);
|
|
270
|
-
setPrompt('Analyze sales trends for the last quarter');
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
#### `startAnalysis()`
|
|
118
|
+
See [ai-nextjs](https://github.com/sqlrooms/sqlrooms/tree/main/examples/ai-nextjs) for a complete example.
|
|
274
119
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
```tsx
|
|
278
|
-
const startAnalysis = useRoomStore((state) => state.ai.startAnalysis);
|
|
279
|
-
await startAnalysis();
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
#### `cancelAnalysis()`
|
|
120
|
+
## Data Structure
|
|
283
121
|
|
|
284
|
-
|
|
122
|
+
The basic data structure of the AI package is:
|
|
285
123
|
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
|
|
124
|
+
```ts
|
|
125
|
+
ai: {
|
|
126
|
+
sessions: [
|
|
127
|
+
{
|
|
128
|
+
id: string, // CUID2 identifier
|
|
129
|
+
name: string, // Session display name
|
|
130
|
+
modelProvider: string, // e.g., 'openai', 'anthropic'
|
|
131
|
+
model: string, // e.g., 'gpt-4o', 'claude-3-5-sonnet'
|
|
132
|
+
createdAt: Date,
|
|
133
|
+
// Primary storage: Full conversation history (AI SDK v5 format)
|
|
134
|
+
uiMessages: UIMessage[],
|
|
135
|
+
// Secondary storage: Error messages and legacy compatibility
|
|
136
|
+
analysisResults: AnalysisResult[],
|
|
137
|
+
// Tool execution data
|
|
138
|
+
toolAdditionalData: Record<string, unknown>,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
currentSessionId: string,
|
|
142
|
+
}
|
|
289
143
|
```
|
|
290
144
|
|
|
291
|
-
|
|
145
|
+
### Session Schema
|
|
292
146
|
|
|
293
|
-
|
|
147
|
+
Each session contains:
|
|
294
148
|
|
|
295
|
-
|
|
296
|
-
const setModel = useRoomStore((state) => state.ai.setAiModel);
|
|
297
|
-
setModel('openai', 'gpt-4o');
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
#### `createSession(name?: string, modelProvider?: string, model?: string)`
|
|
149
|
+
#### `uiMessages` - Complete Chat History
|
|
301
150
|
|
|
302
|
-
|
|
151
|
+
The `uiMessages` array stores the complete, flat conversation history using the Vercel AI SDK v5 `UIMessage` format. This includes:
|
|
303
152
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
153
|
+
- User messages
|
|
154
|
+
- Assistant messages
|
|
155
|
+
- Tool call messages
|
|
156
|
+
- All message parts (text, tool invocations, etc.)
|
|
308
157
|
|
|
309
|
-
|
|
158
|
+
This is the **primary data structure** and serves as:
|
|
310
159
|
|
|
311
|
-
|
|
160
|
+
- The full context for AI model interactions
|
|
161
|
+
- The source for displaying conversation history
|
|
162
|
+
- The base for reconstructing analysis results
|
|
312
163
|
|
|
313
164
|
```tsx
|
|
314
|
-
|
|
315
|
-
|
|
165
|
+
// Example: Accessing UI messages
|
|
166
|
+
const currentSession = useRoomStore((state) => state.ai.getCurrentSession());
|
|
167
|
+
const messages = currentSession?.uiMessages || [];
|
|
316
168
|
```
|
|
317
169
|
|
|
318
|
-
#### `
|
|
319
|
-
|
|
320
|
-
Renames an existing analysis session.
|
|
170
|
+
#### `analysisResults` - Structured Analysis View
|
|
321
171
|
|
|
322
|
-
|
|
323
|
-
const renameSession = useRoomStore((state) => state.ai.renameSession);
|
|
324
|
-
renameSession('session-123', 'Q4 Sales Analysis');
|
|
325
|
-
```
|
|
172
|
+
The `analysisResults` array is a **derived structure** that organizes messages into user prompt → AI response pairs. It primarily serves to:
|
|
326
173
|
|
|
327
|
-
|
|
174
|
+
- Store error messages that occur during analysis
|
|
175
|
+
- Provide backward compatibility with legacy data
|
|
176
|
+
- Offer a simplified view of analysis history
|
|
328
177
|
|
|
329
|
-
|
|
178
|
+
Analysis results are dynamically generated from `uiMessages` using the `transformMessagesToAnalysisResults` utility function.
|
|
330
179
|
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
|
|
180
|
+
```ts
|
|
181
|
+
type AnalysisResult = {
|
|
182
|
+
id: string; // Matches the UIMessage.id
|
|
183
|
+
prompt: string; // User's question/request
|
|
184
|
+
errorMessage?: ErrorMessageSchema; // Error if analysis failed
|
|
185
|
+
isCompleted: boolean; // Whether AI finished responding
|
|
186
|
+
};
|
|
334
187
|
```
|
|
335
188
|
|
|
336
|
-
#### `
|
|
337
|
-
|
|
338
|
-
Returns the current active analysis session.
|
|
189
|
+
#### `toolAdditionalData` - Rich Tool Outputs
|
|
339
190
|
|
|
340
|
-
|
|
341
|
-
const currentSession = useRoomStore((state) => state.ai.getCurrentSession());
|
|
342
|
-
```
|
|
191
|
+
Each session also maintains a `toolAdditionalData` object that stores additional data from tool executions, keyed by `toolCallId`. This data is used for:
|
|
343
192
|
|
|
344
|
-
|
|
193
|
+
- Rendering tool-specific UI components
|
|
194
|
+
- Passing data between tool calls
|
|
195
|
+
- Preserving rich data that doesn't go back to the LLM
|
|
345
196
|
|
|
346
|
-
|
|
197
|
+
```ts
|
|
198
|
+
type ToolAdditionalData = Record<string, unknown>;
|
|
347
199
|
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
200
|
+
// Example: Storing tool additional data
|
|
201
|
+
const setToolData = useRoomStore((state) => state.ai.setSessionToolAdditionalData);
|
|
202
|
+
setToolData(sessionId, toolCallId, {chartData: [...]});
|
|
351
203
|
```
|
|
352
204
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
Finds the React component associated with a specific tool.
|
|
205
|
+
## Tools
|
|
356
206
|
|
|
357
|
-
|
|
358
|
-
const ChartComponent = useRoomStore((state) =>
|
|
359
|
-
state.ai.findToolComponent('chart'),
|
|
360
|
-
);
|
|
361
|
-
```
|
|
207
|
+
In AI package, we provide a OpenAssistantTool type that supports not only `execute` function, but also `context` object and `component` object:
|
|
362
208
|
|
|
363
|
-
|
|
209
|
+
- `execute` needs to return
|
|
210
|
+
- llmResult: the result send back to LLM (no raw data)
|
|
211
|
+
- additionalData: the data will be used by `component` and next `tool`
|
|
212
|
+
- `context`
|
|
213
|
+
- provide e.g. runtime or async data for `execute`
|
|
214
|
+
- `execute` can access `context` via `options.context`
|
|
215
|
+
- `component`
|
|
216
|
+
- use `additionalData` to render a React component for this `tool`
|
|
364
217
|
|
|
365
|
-
|
|
218
|
+
For example, the `weather` tool is defined as follows:
|
|
366
219
|
|
|
367
220
|
```ts
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
221
|
+
const weatherTool: OpenAssistantTool = {
|
|
222
|
+
name: 'weather',
|
|
223
|
+
description: 'Get the weather in a city from a weather station',
|
|
224
|
+
parameters: z.object({cityName: z.string()}),
|
|
225
|
+
execute: async ({cityName}, options) => {
|
|
226
|
+
const getStation = options.context?.getStation;
|
|
227
|
+
const station = getStation ? await getStation(cityName) : null;
|
|
228
|
+
return {
|
|
229
|
+
llmResult: {
|
|
230
|
+
success: true,
|
|
231
|
+
details: `The weather in ${cityName} is sunny from weather station ${station}.`,
|
|
232
|
+
},
|
|
233
|
+
additionalData: {
|
|
234
|
+
weather: 'sunny',
|
|
235
|
+
station,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
context: {
|
|
240
|
+
getStation: async (cityName: string) => {
|
|
241
|
+
const stations = {
|
|
242
|
+
'New York': '123',
|
|
243
|
+
'Los Angeles': '456',
|
|
244
|
+
Chicago: '789',
|
|
245
|
+
};
|
|
246
|
+
return stations[cityName];
|
|
377
247
|
},
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
248
|
+
},
|
|
249
|
+
component: WeatherStationComponent,
|
|
250
|
+
};
|
|
381
251
|
```
|
|
382
252
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
- `id`: The unique identifier for the analysis result
|
|
386
|
-
- `prompt`: The user prompt that was used to generate the analysis result
|
|
387
|
-
- `streamMessage`: The stream message from the LLM
|
|
388
|
-
- `errorMessage`: The error message from the LLM
|
|
389
|
-
- `isCompleted`: Whether the analysis result has been completed
|
|
390
|
-
|
|
391
|
-
For each user prompt, the LLM will run multiple tools (e.g. `query`, `chart`) and return the result as the `streamMessage`. The structure of the `streamMessage` is as follows:
|
|
392
|
-
|
|
393
|
-
- `text`: the final response from the LLM (streamable)
|
|
394
|
-
- `reasoning`: the reasoning of the LLM (only for reason models)
|
|
395
|
-
- `toolCallMessages`: the message array of the tool calls executed by the LLM
|
|
253
|
+
### Tool Execution Flow
|
|
396
254
|
|
|
397
|
-
|
|
255
|
+
1. User sends a prompt → creates a user `UIMessage`
|
|
256
|
+
2. AI processes and may call tools → creates assistant `UIMessage` with tool invocations
|
|
257
|
+
3. Tools execute and return:
|
|
258
|
+
- `llmResult`: Text summary sent back to the LLM
|
|
259
|
+
- `additionalData`: Rich data stored in `toolAdditionalData` for UI rendering
|
|
260
|
+
4. AI responds with final answer → creates assistant `UIMessage` with text
|
|
261
|
+
5. On completion: `uiMessages` updated, `analysisResult` created with user message ID
|
|
398
262
|
|
|
399
|
-
|
|
400
|
-
- `toolCallId`: the id of the tool call
|
|
401
|
-
- `args`: the arguments of the tool call
|
|
402
|
-
- `llmResult`: the result from the execution of the tool, which will be sent back to the LLM as response.
|
|
403
|
-
- `additionalData`: the additional data of the tool, which can be used to pass the output of the tool to next tool call or the component for rendering.
|
|
404
|
-
|
|
405
|
-
## Rendering
|
|
263
|
+
### Rendering Tool Results
|
|
406
264
|
|
|
407
265
|
```text
|
|
408
266
|
|--------------------------------|
|
|
@@ -411,71 +269,103 @@ Each `toolCallMessages` has the following properties:
|
|
|
411
269
|
| |--------------------------| |
|
|
412
270
|
| | AnalysisResult | |
|
|
413
271
|
| | | |
|
|
414
|
-
| |
|
|
272
|
+
| | ErrorMessage | |
|
|
273
|
+
| | ------------ | |
|
|
274
|
+
| | UIMessage | |
|
|
415
275
|
| | | |
|
|
416
276
|
| | |---------------------| | |
|
|
417
|
-
| | |
|
|
277
|
+
| | | Parts | | |
|
|
418
278
|
| | |---------------------| | |
|
|
419
279
|
| | | |---------------| | | |
|
|
420
|
-
| | | |
|
|
421
|
-
| | | |---------------| | | |
|
|
280
|
+
| | | |TextPart | | | |
|
|
422
281
|
| | | |---------------| | | |
|
|
423
|
-
| | | |
|
|
282
|
+
| | | |ToolPart | | | |
|
|
424
283
|
| | | |---------------| | | |
|
|
425
284
|
| | | ... | | |
|
|
426
285
|
| | |---------------------| | |
|
|
427
286
|
| | | |
|
|
428
|
-
| | text | |
|
|
429
287
|
| |--------------------------| |
|
|
430
288
|
|--------------------------------|
|
|
431
289
|
```
|
|
432
290
|
|
|
433
|
-
|
|
291
|
+
### Transfer Additional Tool Output Data to Client
|
|
434
292
|
|
|
435
|
-
In AI package, we provide a tool() to allow creating function tool for LLM to use. It is an extension of the `tool` from `vercel ai sdk`, and it supports not only `execute` function, but also `context` object and `component` object:
|
|
436
293
|
|
|
437
|
-
|
|
438
|
-
- llmResult: the result send back to LLM (no raw data)
|
|
439
|
-
- additionalData: the data will be used by `component` and next `tool`
|
|
440
|
-
- `context`
|
|
441
|
-
- provide e.g. runtime or async data for `execute`
|
|
442
|
-
- `execute` can access `context` via `options.context`
|
|
443
|
-
- `component`
|
|
444
|
-
- use `additionalData` to render a React component for this `tool`
|
|
294
|
+
#### The Problem
|
|
445
295
|
|
|
446
|
-
|
|
296
|
+
When tools execute, they often generate additional data (like detailed search results, charts, metadata) that needs to be sent to the client for UI rendering, but should NOT be included in the conversation history sent back to the LLM.
|
|
447
297
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
298
|
+
If the tool execution is done on the server side, the additional data needs to be transferred to the client side for UI rendering. We use the `data-tool-additional-output` data part type to transfer the additional data to the client.
|
|
299
|
+
|
|
300
|
+
#### Using `transient: true`
|
|
301
|
+
|
|
302
|
+
The AI SDK v5 provides a built-in solution through the `transient` flag on data parts. When you write a data part with `transient: true`, the SDK automatically prevents it from being added to the message history.
|
|
303
|
+
|
|
304
|
+
##### Backend Implementation (route.ts)
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
writer.write({
|
|
308
|
+
type: 'data-tool-additional-output',
|
|
309
|
+
transient: true, // Won't be added to message history
|
|
310
|
+
data: {
|
|
311
|
+
toolCallId: chunk.toolCallId,
|
|
312
|
+
toolName: chunk.toolName,
|
|
313
|
+
output: getToolAdditionalData(chunk.toolCallId),
|
|
314
|
+
timestamp: new Date().toISOString(),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### The Flow
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
Backend (route.ts)
|
|
323
|
+
│
|
|
324
|
+
├─> Tool executes
|
|
325
|
+
│ └─> writer.write({
|
|
326
|
+
│ type: 'data-tool-additional-output',
|
|
327
|
+
│ transient: true, // ✅ SDK handles exclusion
|
|
328
|
+
│ data: {...}
|
|
329
|
+
│ })
|
|
330
|
+
│
|
|
331
|
+
↓
|
|
332
|
+
Client receives stream
|
|
333
|
+
│
|
|
334
|
+
├─> onData callback
|
|
335
|
+
│ └─> setSessionToolAdditionalData() ✅ Stores in toolAdditionalData
|
|
336
|
+
│
|
|
337
|
+
└─> messages array ✅ Automatically excludes transient data parts
|
|
338
|
+
|
|
339
|
+
Session Storage (clean) → AI SDK → UI Display
|
|
340
|
+
↓
|
|
341
|
+
Session Storage
|
|
342
|
+
↓
|
|
343
|
+
Backend/LLM
|
|
477
344
|
```
|
|
478
345
|
|
|
346
|
+
#### Benefits of This Approach
|
|
347
|
+
|
|
348
|
+
1. **✅ Clean Conversation History**: Transient data parts never appear in message history
|
|
349
|
+
2. **✅ Efficient Token Usage**: No unnecessary data sent to the LLM
|
|
350
|
+
3. **✅ Proper Data Storage**: Tool data is stored separately in `toolAdditionalData`
|
|
351
|
+
4. **✅ UI Flexibility**: Components can access tool data via `toolAdditionalData[toolCallId]`
|
|
352
|
+
5. **✅ Simple & Native**: Uses built-in SDK feature, no custom utilities needed
|
|
353
|
+
6. **✅ Maintainable**: Follows SDK conventions and patterns
|
|
354
|
+
7. **✅ No Manual Filtering**: SDK handles exclusion automatically
|
|
355
|
+
|
|
356
|
+
#### Usage in Components
|
|
357
|
+
|
|
358
|
+
To access the additional tool data in your components:
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
const currentSession = useRoomStore((state) => state.ai.getCurrentSession());
|
|
362
|
+
const toolData = currentSession?.toolAdditionalData?.[toolCallId];
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Alternative Considered: Message Annotations
|
|
366
|
+
|
|
367
|
+
AI SDK v5 supports message annotations, but these are still part of the message structure. The `transient` flag is specifically designed for data that should only be sent once and not persist in conversation history.
|
|
368
|
+
|
|
479
369
|
## Advanced Features
|
|
480
370
|
|
|
481
371
|
- **Custom AI Tools**: Define custom tools for AI to use with the tool() function
|