@dxos/assistant 0.8.4-main.fd6878d → 0.8.4-main.fffef41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/extraction/index.mjs +121 -165
- package/dist/lib/browser/extraction/index.mjs.map +4 -4
- package/dist/lib/browser/index.mjs +1097 -161
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/extraction/index.mjs +121 -165
- package/dist/lib/node-esm/extraction/index.mjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +1097 -161
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/conversation/context.d.ts +23 -10
- package/dist/types/src/conversation/context.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.d.ts +22 -31
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/diff.d.ts +15 -0
- package/dist/types/src/diff.d.ts.map +1 -0
- package/dist/types/src/diff.test.d.ts +2 -0
- package/dist/types/src/diff.test.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +45 -13
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/execution-graph.d.ts +85 -0
- package/dist/types/src/execution-graph.d.ts.map +1 -0
- package/dist/types/src/execution-graph.test.d.ts +2 -0
- package/dist/types/src/execution-graph.test.d.ts.map +1 -0
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +1 -1
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +82 -43
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +262 -225
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/quotes.d.ts +1 -1
- package/dist/types/src/extraction/quotes.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +18 -5
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/session/GenericToolkit.d.ts +84 -0
- package/dist/types/src/session/GenericToolkit.d.ts.map +1 -0
- package/dist/types/src/session/artifact-diff.d.ts +2 -2
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
- package/dist/types/src/session/error-handling.d.ts +4 -0
- package/dist/types/src/session/error-handling.d.ts.map +1 -0
- package/dist/types/src/session/format.d.ts +5 -161
- package/dist/types/src/session/format.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +3 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts +22 -0
- package/dist/types/src/session/observer.d.ts.map +1 -0
- package/dist/types/src/session/session.d.ts +22 -138
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +15 -0
- package/dist/types/src/session/toolkit.d.ts.map +1 -0
- package/dist/types/src/session/version-pin.d.ts +3 -3
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/templates/system.d.ts +8 -12
- package/dist/types/src/templates/system.d.ts.map +1 -1
- package/dist/types/src/types/artifact.d.ts +20 -0
- package/dist/types/src/types/artifact.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +29 -31
- package/src/conversation/context.ts +75 -22
- package/src/conversation/conversation.ts +80 -77
- package/src/diff.test.ts +43 -0
- package/src/diff.ts +84 -0
- package/src/errors.ts +6 -1
- package/src/execution-graph.test.ts +740 -0
- package/src/execution-graph.ts +509 -0
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +9 -9
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +47 -56
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +16 -16
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +42 -43
- package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +12 -6
- package/src/extraction/extraction-ner-function/named-entity-recognition.ts +2 -0
- package/src/extraction/extraction.ts +7 -6
- package/src/extraction/quotes.test.ts +2 -2
- package/src/extraction/quotes.ts +3 -3
- package/src/functions/services.ts +104 -56
- package/src/index.ts +3 -1
- package/src/session/GenericToolkit.ts +130 -0
- package/src/session/artifact-diff.ts +8 -4
- package/src/session/error-handling.ts +41 -0
- package/src/session/format.test.ts +5 -4
- package/src/session/format.ts +27 -22
- package/src/session/index.ts +4 -0
- package/src/session/observer.ts +59 -0
- package/src/session/session.conversations.json +1 -0
- package/src/session/session.test.ts +35 -191
- package/src/session/session.ts +119 -185
- package/src/session/toolkit.ts +48 -0
- package/src/session/version-pin.ts +4 -5
- package/src/templates/{format.tpl → instructions/format.tpl} +8 -2
- package/src/templates/{system.tpl → instructions/system.tpl} +15 -14
- package/src/templates/system.ts +25 -16
- package/src/{types.ts → types/artifact.ts} +22 -3
- package/src/types/index.ts +5 -0
- package/dist/lib/browser/chunk-6WRGDEMH.mjs +0 -89
- package/dist/lib/browser/chunk-6WRGDEMH.mjs.map +0 -7
- package/dist/lib/browser/chunk-NZGGCEF3.mjs +0 -341
- package/dist/lib/browser/chunk-NZGGCEF3.mjs.map +0 -7
- package/dist/lib/browser/named-entity-recognition-O46UTCLR.mjs +0 -13
- package/dist/lib/node-esm/chunk-3UU5V4FS.mjs +0 -91
- package/dist/lib/node-esm/chunk-3UU5V4FS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XOOPGSGW.mjs +0 -343
- package/dist/lib/node-esm/chunk-XOOPGSGW.mjs.map +0 -7
- package/dist/lib/node-esm/named-entity-recognition-W4VMRCH6.mjs +0 -14
- package/dist/types/src/session/version-pin.test.d.ts +0 -2
- package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
- package/dist/types/src/summarization/summarization.test.d.ts +0 -2
- package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
- package/dist/types/src/types.d.ts +0 -16
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/session/__snapshots__/session.test.ts.snap +0 -32
- package/src/session/version-pin.test.ts +0 -7
- package/src/summarization/summarization.test.ts +0 -197
- /package/dist/lib/browser/{named-entity-recognition-O46UTCLR.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
- /package/dist/lib/node-esm/{named-entity-recognition-W4VMRCH6.mjs.map → chunk-HSLMI22Q.mjs.map} +0 -0
- /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Tool from '@effect/ai/Tool';
|
|
6
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
6
7
|
import { describe, it } from '@effect/vitest';
|
|
7
|
-
import
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
import * as Schema from 'effect/Schema';
|
|
8
11
|
|
|
9
|
-
import { AiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
12
|
+
import { AiService, MemoizedAiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
10
13
|
import { AiServiceTestingPreset } from '@dxos/ai/testing';
|
|
11
14
|
import { Obj, Type } from '@dxos/echo';
|
|
15
|
+
import { TestHelpers } from '@dxos/effect';
|
|
16
|
+
import { TracingService } from '@dxos/functions';
|
|
12
17
|
import { log } from '@dxos/log';
|
|
13
18
|
|
|
14
19
|
import { AiSession } from './session';
|
|
@@ -28,8 +33,8 @@ const CalendarEventSchema = Schema.Struct({
|
|
|
28
33
|
|
|
29
34
|
type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
const TestToolkit = Toolkit.make(
|
|
37
|
+
Tool.make('Calculator', {
|
|
33
38
|
description: 'Basic calculator tool',
|
|
34
39
|
parameters: {
|
|
35
40
|
input: Schema.String.annotations({
|
|
@@ -41,7 +46,7 @@ class TestToolkit extends AiToolkit.make(
|
|
|
41
46
|
}),
|
|
42
47
|
failure: Schema.Never,
|
|
43
48
|
}),
|
|
44
|
-
)
|
|
49
|
+
);
|
|
45
50
|
|
|
46
51
|
// Tool handlers.
|
|
47
52
|
const toolkitLayer = TestToolkit.toLayer({
|
|
@@ -60,11 +65,23 @@ const toolkitLayer = TestToolkit.toLayer({
|
|
|
60
65
|
}),
|
|
61
66
|
});
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
const TestLayer = Layer.mergeAll(
|
|
69
|
+
toolkitLayer,
|
|
70
|
+
AiService.model('@anthropic/claude-3-5-sonnet-20241022'),
|
|
71
|
+
TracingService.layerNoop,
|
|
72
|
+
ToolResolverService.layerEmpty,
|
|
73
|
+
ToolExecutionService.layerEmpty,
|
|
74
|
+
).pipe(
|
|
75
|
+
//
|
|
76
|
+
Layer.provideMerge(MemoizedAiService.layerTest()),
|
|
77
|
+
Layer.provide(AiServiceTestingPreset('direct')),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
describe('AiSession', () => {
|
|
64
81
|
it.effect(
|
|
65
82
|
'no tools',
|
|
66
|
-
Effect.
|
|
67
|
-
function* (
|
|
83
|
+
Effect.fnUntraced(
|
|
84
|
+
function* (_) {
|
|
68
85
|
const session = new AiSession({ operationModel: 'configured' });
|
|
69
86
|
const response = yield* session.run({
|
|
70
87
|
prompt: 'Hello world!',
|
|
@@ -72,161 +89,28 @@ describe.runIf(process.env.DX_RUN_SLOW_TESTS)('AiSession', () => {
|
|
|
72
89
|
});
|
|
73
90
|
log.info('response', { response });
|
|
74
91
|
},
|
|
75
|
-
Effect.provide(
|
|
76
|
-
|
|
77
|
-
Layer.provideMerge(ToolResolverService.layerEmpty),
|
|
78
|
-
Layer.provideMerge(ToolExecutionService.layerEmpty),
|
|
79
|
-
Layer.provideMerge(AiServiceTestingPreset('direct')),
|
|
80
|
-
),
|
|
81
|
-
),
|
|
92
|
+
Effect.provide(TestLayer),
|
|
93
|
+
TestHelpers.provideTestContext,
|
|
82
94
|
),
|
|
83
95
|
);
|
|
84
96
|
|
|
85
97
|
it.effect(
|
|
86
98
|
'calculator',
|
|
87
|
-
Effect.
|
|
88
|
-
function* (
|
|
99
|
+
Effect.fnUntraced(
|
|
100
|
+
function* (_) {
|
|
89
101
|
const session = new AiSession({ operationModel: 'configured' });
|
|
102
|
+
const toolkit = yield* TestToolkit;
|
|
90
103
|
const response = yield* session.run({
|
|
91
|
-
|
|
104
|
+
toolkit,
|
|
105
|
+
prompt: 'What is 10 + 30?',
|
|
92
106
|
history: [],
|
|
93
|
-
toolkit: TestToolkit,
|
|
94
107
|
});
|
|
95
108
|
log.info('response', { response });
|
|
96
109
|
},
|
|
97
|
-
Effect.provide(
|
|
98
|
-
|
|
99
|
-
toolkitLayer,
|
|
100
|
-
ToolResolverService.layerEmpty,
|
|
101
|
-
ToolExecutionService.layerEmpty,
|
|
102
|
-
AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
|
|
103
|
-
Layer.provideMerge(AiServiceTestingPreset('direct')),
|
|
104
|
-
),
|
|
105
|
-
),
|
|
106
|
-
),
|
|
110
|
+
Effect.provide(TestLayer),
|
|
111
|
+
TestHelpers.provideTestContext,
|
|
107
112
|
),
|
|
108
113
|
);
|
|
109
|
-
|
|
110
|
-
// TODO(dmaretskyi): Revive test.
|
|
111
|
-
/*
|
|
112
|
-
it.skip('create calendar itinerary', { timeout: 60_000 }, async () => {
|
|
113
|
-
// overrides: { model: 'llama3.1:8b' },
|
|
114
|
-
const session = new AiSession({ operationModel: 'configured' });
|
|
115
|
-
const objects = new Set<string>();
|
|
116
|
-
|
|
117
|
-
// Define calendar artifact.
|
|
118
|
-
const calendarArtifact = defineArtifact({
|
|
119
|
-
id: 'artifact:dxos.org/plugin/calendar',
|
|
120
|
-
name: 'Calendar',
|
|
121
|
-
instructions: 'Use this to create and query calendar events.',
|
|
122
|
-
schema: CalendarEventSchema,
|
|
123
|
-
tools: [
|
|
124
|
-
createTool('calendar', {
|
|
125
|
-
name: 'query',
|
|
126
|
-
description: 'Query the calendar for events',
|
|
127
|
-
schema: Schema.Struct({}),
|
|
128
|
-
execute: async () => {
|
|
129
|
-
return ToolResult.Success(CALENDAR_EVENTS);
|
|
130
|
-
},
|
|
131
|
-
}),
|
|
132
|
-
],
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const tableArtifact = defineArtifact({
|
|
136
|
-
id: 'artifact:dxos.org/plugin/table',
|
|
137
|
-
name: 'Table',
|
|
138
|
-
instructions: 'Use this to create and manage tables. Each table has a unique id.',
|
|
139
|
-
schema: Schema.Struct({}),
|
|
140
|
-
tools: [
|
|
141
|
-
createTool('table', {
|
|
142
|
-
name: 'create',
|
|
143
|
-
description: 'Create a table',
|
|
144
|
-
schema: Schema.Struct({
|
|
145
|
-
data: Schema.Array(Schema.Any).annotations({ description: 'Array of data payloads to add as rows' }),
|
|
146
|
-
}),
|
|
147
|
-
execute: async ({ data }) => {
|
|
148
|
-
log('create table', { data });
|
|
149
|
-
const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
|
|
150
|
-
objects.add(id);
|
|
151
|
-
// TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
|
|
152
|
-
return ToolResult.Success(`table @${id}`);
|
|
153
|
-
},
|
|
154
|
-
}),
|
|
155
|
-
],
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const mapArtifact = defineArtifact({
|
|
159
|
-
id: 'artifact:dxos.org/plugin/map',
|
|
160
|
-
name: 'Map',
|
|
161
|
-
instructions:
|
|
162
|
-
'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
|
|
163
|
-
schema: Schema.Struct({}),
|
|
164
|
-
tools: [
|
|
165
|
-
createTool('map', {
|
|
166
|
-
name: 'create',
|
|
167
|
-
description: 'Create a map',
|
|
168
|
-
schema: Schema.Struct({
|
|
169
|
-
source: ArtifactId.annotations({
|
|
170
|
-
description: 'The table that will be used as the source of the map',
|
|
171
|
-
}),
|
|
172
|
-
}),
|
|
173
|
-
execute: async ({ source }) => {
|
|
174
|
-
// TODO(dmaretskyi): Use effect-schema decode instead of manual parsing.
|
|
175
|
-
const sourceId = ArtifactId.toDXN(source);
|
|
176
|
-
if (!objects.has(sourceId.toString())) {
|
|
177
|
-
return ToolResult.Error(`table id=${source} not found`);
|
|
178
|
-
}
|
|
179
|
-
log('create map', { sourceId });
|
|
180
|
-
const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
|
|
181
|
-
objects.add(id);
|
|
182
|
-
// TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
|
|
183
|
-
return ToolResult.Success(`map @${id}`);
|
|
184
|
-
},
|
|
185
|
-
}),
|
|
186
|
-
],
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const scriptArtifact = defineArtifact({
|
|
190
|
-
id: 'artifact:dxos.org/plugin/script',
|
|
191
|
-
name: 'Script',
|
|
192
|
-
instructions: 'Use this to create and manage scripts',
|
|
193
|
-
schema: Schema.Struct({}),
|
|
194
|
-
tools: [],
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// session.streamEvent.on((event) => {
|
|
198
|
-
// printStreamEvent(event);
|
|
199
|
-
// });
|
|
200
|
-
|
|
201
|
-
const printer = new ConsolePrinter();
|
|
202
|
-
session.message.on((message) => printer.printMessage(message));
|
|
203
|
-
session.userMessage.on((message) => printer.printMessage(message));
|
|
204
|
-
session.block.on((block) => printer.printContentBlock(block));
|
|
205
|
-
|
|
206
|
-
// session.update.on((update) => {
|
|
207
|
-
// log('update', { update });
|
|
208
|
-
// });
|
|
209
|
-
|
|
210
|
-
// Test creating an itinerary
|
|
211
|
-
const response = await session.run({
|
|
212
|
-
client: aiClient,
|
|
213
|
-
tools: [],
|
|
214
|
-
artifacts: [calendarArtifact, tableArtifact, mapArtifact, scriptArtifact],
|
|
215
|
-
requiredArtifactIds: [calendarArtifact.id, tableArtifact.id, mapArtifact.id, scriptArtifact.id],
|
|
216
|
-
history: [],
|
|
217
|
-
generationOptions: {
|
|
218
|
-
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
219
|
-
},
|
|
220
|
-
prompt: 'create a table and map for a travel itinerary based on events in my calendar',
|
|
221
|
-
toolResolver: new ToolRegistry([]),
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
log('result', {
|
|
225
|
-
objects,
|
|
226
|
-
finalMessage: response.at(-1),
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
*/
|
|
230
114
|
});
|
|
231
115
|
|
|
232
116
|
// Travel to rome, florence, livorno, siena, madrid for conferences
|
|
@@ -263,43 +147,3 @@ const _CALENDAR_EVENTS: CalendarEvent[] = [
|
|
|
263
147
|
description: 'Travel to Madrid',
|
|
264
148
|
}),
|
|
265
149
|
];
|
|
266
|
-
|
|
267
|
-
// const printStreamEvent = (event: GenerationStreamEvent) => {
|
|
268
|
-
// switch (event.type) {
|
|
269
|
-
// case 'message_start': {
|
|
270
|
-
// process.stdout.write(`${event.message.role.toUpperCase()}\n\n`);
|
|
271
|
-
// for (const content of event.message.content) {
|
|
272
|
-
// printContentBlock(content);
|
|
273
|
-
// }
|
|
274
|
-
// break;
|
|
275
|
-
// }
|
|
276
|
-
// case 'content_block_start': {
|
|
277
|
-
// printContentBlock(event.content);
|
|
278
|
-
// break;
|
|
279
|
-
// }
|
|
280
|
-
// case 'content_block_delta': {
|
|
281
|
-
// switch (event.delta.type) {
|
|
282
|
-
// case 'text_delta': {
|
|
283
|
-
// process.stdout.write(event.delta.text);
|
|
284
|
-
// break;
|
|
285
|
-
// }
|
|
286
|
-
// case 'input_json_delta': {
|
|
287
|
-
// process.stdout.write(event.delta.partial_json);
|
|
288
|
-
// break;
|
|
289
|
-
// }
|
|
290
|
-
// }
|
|
291
|
-
// break;
|
|
292
|
-
// }
|
|
293
|
-
// case 'content_block_stop': {
|
|
294
|
-
// process.stdout.write('\n');
|
|
295
|
-
// break;
|
|
296
|
-
// }
|
|
297
|
-
// case 'message_delta': {
|
|
298
|
-
// break;
|
|
299
|
-
// }
|
|
300
|
-
// case 'message_stop': {
|
|
301
|
-
// process.stdout.write('\n\n');
|
|
302
|
-
// break;
|
|
303
|
-
// }
|
|
304
|
-
// }
|
|
305
|
-
// };
|
package/src/session/session.ts
CHANGED
|
@@ -2,106 +2,58 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import type * as AiError from '@effect/ai/AiError';
|
|
6
|
+
import * as LanguageModel from '@effect/ai/LanguageModel';
|
|
7
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
8
|
+
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
9
|
+
import * as Chunk from 'effect/Chunk';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import type * as Schema from 'effect/Schema';
|
|
12
|
+
import * as Stream from 'effect/Stream';
|
|
7
13
|
|
|
8
14
|
import {
|
|
9
|
-
type AiInputPreprocessingError,
|
|
10
15
|
AiParser,
|
|
11
16
|
AiPreprocessor,
|
|
12
17
|
type AiToolNotFoundError,
|
|
13
|
-
type
|
|
14
|
-
ToolExecutionService,
|
|
15
|
-
ToolResolverService,
|
|
16
|
-
|
|
18
|
+
type PromptPreprocessingError,
|
|
19
|
+
type ToolExecutionService,
|
|
20
|
+
type ToolResolverService,
|
|
21
|
+
callTool,
|
|
17
22
|
getToolCalls,
|
|
18
23
|
} from '@dxos/ai';
|
|
19
24
|
import { type Blueprint } from '@dxos/blueprints';
|
|
20
25
|
import { todo } from '@dxos/debug';
|
|
21
26
|
import { Obj } from '@dxos/echo';
|
|
27
|
+
import { TracingService } from '@dxos/functions';
|
|
22
28
|
import { log } from '@dxos/log';
|
|
23
|
-
import {
|
|
24
|
-
import { isNotFalsy } from '@dxos/util';
|
|
29
|
+
import { Message } from '@dxos/types';
|
|
25
30
|
|
|
26
31
|
import { type AiAssistantError } from '../errors';
|
|
27
32
|
|
|
28
33
|
import { formatSystemPrompt, formatUserPrompt } from './format';
|
|
34
|
+
import { GenerationObserver } from './observer';
|
|
29
35
|
|
|
30
|
-
export type
|
|
36
|
+
export type AiSessionRunError = AiError.AiError | PromptPreprocessingError | AiToolNotFoundError | AiAssistantError;
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* Unparsed content block parts from the model.
|
|
38
|
-
*/
|
|
39
|
-
onPart: (part: AiResponse.Part) => Effect.Effect<void>;
|
|
38
|
+
export type AiSessionRunRequirements =
|
|
39
|
+
| LanguageModel.LanguageModel
|
|
40
|
+
| ToolExecutionService
|
|
41
|
+
| ToolResolverService
|
|
42
|
+
| TracingService;
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
* Parsed content blocks from the model.
|
|
43
|
-
* NOTE: Use block.pending to determine if the block was completed.
|
|
44
|
-
* For each block this will be called 0..n times with a pending block and then once with the final state of the block.
|
|
45
|
-
*
|
|
46
|
-
* Example:
|
|
47
|
-
* 1. { pending: true, text: "Hello"}
|
|
48
|
-
* 2. { pending: true, text: "Hello, I am a"}
|
|
49
|
-
* 3. { pending: false, text: "Hello, I am a helpful assistant!"}
|
|
50
|
-
*/
|
|
51
|
-
onBlock: (block: ContentBlock.Any) => Effect.Effect<void>;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Complete messages fired during the session, both from the model and from the user.
|
|
55
|
-
* This message will contain all message blocks emitted through the `onBlock` callback.
|
|
56
|
-
*/
|
|
57
|
-
onMessage: (message: DataType.Message) => Effect.Effect<void>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const GenerationObserver = Object.freeze({
|
|
61
|
-
make: ({
|
|
62
|
-
onPart = Function.constant(Effect.void),
|
|
63
|
-
onBlock = Function.constant(Effect.void),
|
|
64
|
-
onMessage = Function.constant(Effect.void),
|
|
65
|
-
}: Partial<GenerationObserver> = {}): GenerationObserver => ({
|
|
66
|
-
onPart,
|
|
67
|
-
onBlock,
|
|
68
|
-
onMessage,
|
|
69
|
-
}),
|
|
70
|
-
|
|
71
|
-
noop: () => GenerationObserver.make(),
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Debug printer to be used in unit-tests and browser devtools.
|
|
75
|
-
*/
|
|
76
|
-
fromPrinter: (printer: ConsolePrinter) =>
|
|
77
|
-
GenerationObserver.make({
|
|
78
|
-
onBlock: (block) =>
|
|
79
|
-
Effect.sync(() => {
|
|
80
|
-
if (block.pending) {
|
|
81
|
-
return; // Only prints full blocks (better for unit-tests and browser devtools).
|
|
82
|
-
}
|
|
83
|
-
printer.printContentBlock(block);
|
|
84
|
-
}),
|
|
85
|
-
onMessage: (message) =>
|
|
86
|
-
Effect.sync(() => {
|
|
87
|
-
if (message.sender.role === 'assistant') {
|
|
88
|
-
return; // Skip assistant messages since they are printed in the `onBlock` callback.
|
|
89
|
-
}
|
|
90
|
-
printer.printMessage(message);
|
|
91
|
-
}),
|
|
92
|
-
}),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
export type SessionRunParams<Tools extends AiTool.Any> = {
|
|
44
|
+
export type AiSessionRunParams<Tools extends Record<string, Tool.Any>> = {
|
|
96
45
|
prompt: string;
|
|
46
|
+
// TODO(wittjosiah): Rename to systemPrompt.
|
|
97
47
|
system?: string;
|
|
98
|
-
history?:
|
|
99
|
-
objects?: Obj.Any[];
|
|
48
|
+
history?: Message.Message[];
|
|
49
|
+
objects?: Obj.Any[];
|
|
100
50
|
blueprints?: Blueprint.Blueprint[];
|
|
101
|
-
toolkit?:
|
|
51
|
+
toolkit?: Toolkit.WithHandler<Tools>;
|
|
102
52
|
observer?: GenerationObserver;
|
|
103
53
|
};
|
|
104
54
|
|
|
55
|
+
export type AiSessionOptions = {};
|
|
56
|
+
|
|
105
57
|
/**
|
|
106
58
|
* Contains message history, tools, current context.
|
|
107
59
|
* Current context means the state of the app, time of day, and other contextual information.
|
|
@@ -113,142 +65,130 @@ export type SessionRunParams<Tools extends AiTool.Any> = {
|
|
|
113
65
|
* Could be personal or shared.
|
|
114
66
|
*/
|
|
115
67
|
export class AiSession {
|
|
116
|
-
/** Complete messages fired during the session, both from the model and from the user. */
|
|
117
|
-
public readonly messageQueue = Effect.runSync(Queue.unbounded<DataType.Message>());
|
|
118
|
-
|
|
119
|
-
// TODO(dmaretskyi): Remove the queues and convert everything to observer.
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Blocks streaming from the model during the session.
|
|
123
|
-
* @deprecated Use `observer.onBlock` instead.
|
|
124
|
-
*/
|
|
125
|
-
public readonly blockQueue = Effect.runSync(Queue.unbounded<Option.Option<ContentBlock.Any>>());
|
|
126
|
-
|
|
127
|
-
/** Unparsed events from the underlying generation stream. */
|
|
128
|
-
public readonly eventQueue = Effect.runSync(Queue.unbounded<AiResponse.Part>());
|
|
129
|
-
|
|
130
68
|
/** Prevents concurrent execution of session. */
|
|
131
69
|
private readonly _semaphore = Effect.runSync(Effect.makeSemaphore(1));
|
|
132
70
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
71
|
+
/** Message history from queue. */
|
|
72
|
+
// TODO(burdon): Evolve into supporting a git-like graph of messages.
|
|
73
|
+
private _history: Message.Message[] = [];
|
|
135
74
|
|
|
136
|
-
/**
|
|
137
|
-
private
|
|
75
|
+
/** Pending messages for this session (incl. the current prompt). */
|
|
76
|
+
private _pending: Message.Message[] = [];
|
|
138
77
|
|
|
139
78
|
constructor(private readonly _options: AiSessionOptions = {}) {}
|
|
140
79
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
AiAssistantError | AiInputPreprocessingError | AiToolNotFoundError | AiError.AiError,
|
|
151
|
-
AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
|
|
152
|
-
> =>
|
|
80
|
+
run = <Tools extends Record<string, Tool.Any>>({
|
|
81
|
+
prompt,
|
|
82
|
+
system: systemTemplate,
|
|
83
|
+
history = [],
|
|
84
|
+
objects = [],
|
|
85
|
+
blueprints = [],
|
|
86
|
+
toolkit,
|
|
87
|
+
observer = GenerationObserver.noop(),
|
|
88
|
+
}: AiSessionRunParams<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
|
|
153
89
|
Effect.gen(this, function* () {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const toolkit: AiToolkit.ToHandler<Tools> = yield* createToolkit(params);
|
|
90
|
+
this._history = [...history];
|
|
91
|
+
this._pending = [];
|
|
92
|
+
const pending = this._pending;
|
|
158
93
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
94
|
+
const submitMessage = Effect.fnUntraced(function* (message: Message.Message) {
|
|
95
|
+
pending.push(message);
|
|
96
|
+
yield* observer.onMessage(message);
|
|
97
|
+
yield* TracingService.emitConverationMessage(message);
|
|
98
|
+
return message;
|
|
99
|
+
});
|
|
163
100
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
yield*
|
|
167
|
-
yield* observer.onMessage(promptMessages);
|
|
101
|
+
// Submit the prompt.
|
|
102
|
+
// TODO(burdon): Remove if cancelled?
|
|
103
|
+
const promptMessage = yield* submitMessage(yield* formatUserPrompt({ prompt, history }));
|
|
168
104
|
|
|
169
|
-
|
|
170
|
-
|
|
105
|
+
// Generate system and prompt messages.
|
|
106
|
+
const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects });
|
|
171
107
|
|
|
172
|
-
//
|
|
108
|
+
// Tool call loop.
|
|
173
109
|
do {
|
|
174
|
-
log
|
|
175
|
-
prompt:
|
|
176
|
-
system: { snippet:
|
|
110
|
+
log('request', {
|
|
111
|
+
prompt: promptMessage,
|
|
112
|
+
system: { snippet: createSnippet(system), length: system.length },
|
|
177
113
|
pending: this._pending.length,
|
|
178
114
|
history: this._history.length,
|
|
179
|
-
objects:
|
|
180
|
-
blueprints: params.blueprints?.length ?? 0,
|
|
181
|
-
toolkit: Object.values(toolkit.tools).map((tool: AiTool.Any) => tool.name),
|
|
115
|
+
objects: objects?.length ?? 0,
|
|
182
116
|
});
|
|
183
117
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
118
|
+
const prompt = yield* AiPreprocessor.preprocessPrompt([...this._history, ...this._pending], { system });
|
|
119
|
+
|
|
120
|
+
// Execute the stream request.
|
|
121
|
+
// logDump('prompt', Prompt.Prompt.pipe(Schema.encodeSync)(prompt));
|
|
122
|
+
const blocks = yield* LanguageModel.streamText({
|
|
187
123
|
prompt,
|
|
188
|
-
system,
|
|
189
124
|
toolkit,
|
|
190
|
-
// TODO(burdon): Despite this flag, the model still calls tools.
|
|
191
|
-
// Flag is only used in generateText (not streamText); patch and submit bug.
|
|
192
|
-
// https://github.com/Effect-TS/effect/blob/main/packages/ai/ai/src/AiLanguageModel.ts#L401
|
|
193
125
|
disableToolCallResolution: true,
|
|
194
126
|
}).pipe(
|
|
127
|
+
// TOOD(dmaretskyi): Error mapping.
|
|
195
128
|
AiParser.parseResponse({
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
onPart: (part) =>
|
|
129
|
+
onBegin: () => observer.onBegin(),
|
|
130
|
+
onBlock: (block) => observer.onBlock(block),
|
|
131
|
+
onPart: (part) => observer.onPart(part),
|
|
132
|
+
onEnd: (summary) => observer.onEnd(summary),
|
|
199
133
|
}),
|
|
200
134
|
Stream.runCollect,
|
|
201
135
|
Effect.map(Chunk.toArray),
|
|
202
136
|
);
|
|
203
137
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
created: new Date().toISOString(),
|
|
213
|
-
sender: { role: 'assistant' },
|
|
214
|
-
blocks,
|
|
215
|
-
});
|
|
216
|
-
this._pending.push(response);
|
|
217
|
-
yield* this.messageQueue.offer(response);
|
|
218
|
-
yield* observer.onMessage(response);
|
|
138
|
+
// Create the response message.
|
|
139
|
+
const response = yield* submitMessage(
|
|
140
|
+
Obj.make(Message.Message, {
|
|
141
|
+
created: new Date().toISOString(),
|
|
142
|
+
sender: { role: 'assistant' },
|
|
143
|
+
blocks,
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
219
146
|
|
|
220
|
-
// Parse response for tool calls.
|
|
147
|
+
// Parse the response for tool calls.
|
|
221
148
|
const toolCalls = getToolCalls(response);
|
|
222
149
|
if (toolCalls.length === 0) {
|
|
223
150
|
break;
|
|
151
|
+
} else if (!toolkit) {
|
|
152
|
+
throw new Error('No toolkit provided'); // TODO(burdon): Throw user error?
|
|
224
153
|
}
|
|
225
154
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
155
|
+
// Execute the tool calls.
|
|
156
|
+
// TODO(burdon): Retry errors? Write result when each completes individually?
|
|
157
|
+
const toolResults = yield* Effect.forEach(toolCalls, (toolCall) => {
|
|
158
|
+
return callTool(toolkit, toolCall).pipe(
|
|
159
|
+
Effect.provide(
|
|
160
|
+
TracingService.layerSubframe((context) => ({
|
|
161
|
+
...context,
|
|
162
|
+
parentMessage: response.id,
|
|
163
|
+
toolCallId: toolCall.toolCallId,
|
|
164
|
+
})),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
232
167
|
});
|
|
233
168
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
169
|
+
// Add to queue and continue loop.
|
|
170
|
+
// TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
|
|
171
|
+
// This results in a broken execution graph.
|
|
172
|
+
yield* submitMessage(
|
|
173
|
+
Obj.make(Message.Message, {
|
|
174
|
+
created: new Date().toISOString(),
|
|
175
|
+
sender: { role: 'tool' },
|
|
176
|
+
blocks: toolResults,
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
237
179
|
} while (true);
|
|
238
180
|
|
|
239
|
-
// Signals to stream consumers that the session has completed and no more messages are coming.
|
|
240
|
-
yield* Queue.shutdown(this.messageQueue);
|
|
241
|
-
yield* Queue.shutdown(this.blockQueue);
|
|
242
|
-
yield* Queue.shutdown(this.eventQueue);
|
|
243
|
-
|
|
244
181
|
log('done', { pending: this._pending.length });
|
|
245
182
|
return this._pending;
|
|
246
183
|
}).pipe(this._semaphore.withPermits(1), Effect.withSpan('AiSession.run'));
|
|
247
184
|
|
|
248
|
-
|
|
185
|
+
/**
|
|
186
|
+
* @deprecated
|
|
187
|
+
*/
|
|
188
|
+
// TODO(burdon): Implement or remove.
|
|
249
189
|
async runStructured<S extends Schema.Schema.AnyNoContext>(
|
|
250
190
|
_schema: S,
|
|
251
|
-
_options:
|
|
191
|
+
_options: AiSessionRunParams<any>,
|
|
252
192
|
): Promise<Schema.Schema.Type<S>> {
|
|
253
193
|
return todo();
|
|
254
194
|
// const parser = structuredOutputParser(schema);
|
|
@@ -260,20 +200,14 @@ export class AiSession {
|
|
|
260
200
|
}
|
|
261
201
|
}
|
|
262
202
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*/
|
|
266
|
-
const createToolkit = <Tools extends AiTool.Any>({
|
|
267
|
-
toolkit,
|
|
268
|
-
blueprints = [],
|
|
269
|
-
}: Pick<SessionRunParams<Tools>, 'toolkit' | 'blueprints'>) =>
|
|
270
|
-
Effect.gen(function* () {
|
|
271
|
-
const blueprintToolkit = yield* ToolResolverService.resolveToolkit(blueprints.flatMap(({ tools }) => tools));
|
|
272
|
-
const blueprintToolkitHandler: Context.Context<AiTool.ToHandler<AiTool.Any>> = yield* blueprintToolkit.toContext(
|
|
273
|
-
ToolExecutionService.handlersFor(blueprintToolkit),
|
|
274
|
-
);
|
|
203
|
+
const createSnippet = (text: string, len = 32) =>
|
|
204
|
+
text.length <= len * 2 ? text : [text.slice(0, len), '...', text.slice(-len)].join('');
|
|
275
205
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
206
|
+
// TODO(dmaretskyi): Extract as a general util.
|
|
207
|
+
const logDump = (message: string, data: unknown) => {
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
209
|
+
const { writeFileSync } = require('node:fs');
|
|
210
|
+
const path = `/tmp/log-data-${Date.now()}.json`;
|
|
211
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
212
|
+
log.info(message, { path });
|
|
213
|
+
};
|