@dxos/assistant 0.8.4-main.e8ec1fe → 0.8.4-main.ef1bc66f44
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/neutral/chunk-ZPC54VRM.mjs +596 -0
- package/dist/lib/neutral/chunk-ZPC54VRM.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/extraction/index.mjs +5 -5
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/index.mjs +320 -681
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/testing/index.mjs +38 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +97 -62
- package/dist/types/src/conversation/context.d.ts.map +1 -1
- package/dist/types/src/conversation/context.test.d.ts +2 -0
- package/dist/types/src/conversation/context.test.d.ts.map +1 -0
- package/dist/types/src/conversation/conversation.d.ts +40 -15
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.test.d.ts +2 -0
- package/dist/types/src/conversation/conversation.test.d.ts.map +1 -0
- package/dist/types/src/diff.d.ts +3 -1
- package/dist/types/src/diff.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts +12 -16
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/execution-graph.d.ts +2 -2
- package/dist/types/src/execution-graph.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +247 -245
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +342 -308
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/quotes.d.ts +2 -2
- package/dist/types/src/extraction/quotes.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +6 -7
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/functions.test.d.ts +2 -0
- package/dist/types/src/functions.test.d.ts.map +1 -0
- package/dist/types/src/session/GenericToolkit.d.ts +23 -0
- package/dist/types/src/session/GenericToolkit.d.ts.map +1 -1
- package/dist/types/src/session/artifact-diff.d.ts +1 -1
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
- package/dist/types/src/session/format.d.ts +3 -3
- package/dist/types/src/session/format.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +1 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts +4 -3
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +14 -9
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +5 -6
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +2 -2
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/layer.d.ts +22 -0
- package/dist/types/src/testing/layer.d.ts.map +1 -0
- package/dist/types/src/types/artifact.d.ts +13 -5
- package/dist/types/src/types/artifact.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +40 -33
- package/src/conversation/context.test.ts +46 -0
- package/src/conversation/context.ts +211 -67
- package/src/conversation/conversation.test.ts +65 -0
- package/src/conversation/conversation.ts +123 -48
- package/src/diff.test.ts +35 -4
- package/src/diff.ts +13 -4
- package/src/errors.ts +2 -2
- package/src/execution-graph.test.ts +1 -1
- package/src/execution-graph.ts +4 -4
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +7 -7
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +1 -1
- package/src/extraction/extraction.ts +4 -4
- package/src/extraction/quotes.ts +4 -5
- package/src/functions/services.ts +55 -30
- package/src/functions.conversations.json +1 -0
- package/src/functions.test.ts +74 -0
- package/src/session/GenericToolkit.ts +30 -0
- package/src/session/artifact-diff.ts +1 -1
- package/src/session/format.test.ts +101 -38
- package/src/session/format.ts +12 -13
- package/src/session/index.ts +1 -0
- package/src/session/observer.ts +3 -1
- package/src/session/session.conversations.json +1 -1
- package/src/session/session.test.ts +26 -13
- package/src/session/session.ts +36 -14
- package/src/session/toolkit.ts +13 -22
- package/src/session/version-pin.ts +4 -3
- package/src/templates/instructions/format.tpl +0 -4
- package/src/testing/index.ts +5 -0
- package/src/testing/layer.ts +84 -0
- package/src/types/artifact.ts +31 -13
- package/dist/lib/browser/extraction/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/extraction/index.mjs +0 -226
- package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1291
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
5
6
|
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
6
|
-
import
|
|
7
|
+
import type { Registry } from '@effect-atom/atom-react';
|
|
8
|
+
import * as Context from 'effect/Context';
|
|
7
9
|
import * as Effect from 'effect/Effect';
|
|
8
|
-
import * as
|
|
10
|
+
import * as Layer from 'effect/Layer';
|
|
9
11
|
|
|
12
|
+
import { type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
|
|
10
13
|
import { Resource } from '@dxos/context';
|
|
11
14
|
import { Obj } from '@dxos/echo';
|
|
12
15
|
import { type Queue } from '@dxos/echo-db';
|
|
13
|
-
import {
|
|
16
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
17
|
+
import { QueueService } from '@dxos/functions';
|
|
18
|
+
import { invariant } from '@dxos/invariant';
|
|
14
19
|
import { log } from '@dxos/log';
|
|
15
20
|
import { Message } from '@dxos/types';
|
|
16
21
|
|
|
@@ -24,49 +29,48 @@ import {
|
|
|
24
29
|
|
|
25
30
|
import { AiContextBinder, AiContextService, type ContextBinding } from './context';
|
|
26
31
|
|
|
27
|
-
export interface
|
|
32
|
+
export interface AiConversationRunProps {
|
|
28
33
|
prompt: string;
|
|
29
34
|
system?: string;
|
|
30
35
|
observer?: GenerationObserver;
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
export type AiConversationOptions = {
|
|
39
|
+
queue: Queue<Message.Message | ContextBinding>;
|
|
40
|
+
toolkit?: Toolkit.Any;
|
|
41
|
+
registry?: Registry.Registry;
|
|
42
|
+
};
|
|
43
|
+
|
|
33
44
|
/**
|
|
34
45
|
* Durable conversation state (initiated by users and agents) backed by a Queue.
|
|
35
46
|
* Executes tools based on AI responses and supports cancellation of in-progress requests.
|
|
36
47
|
*/
|
|
37
48
|
export class AiConversation extends Resource {
|
|
38
49
|
/**
|
|
39
|
-
*
|
|
50
|
+
* Blueprints and objects bound to the conversation.
|
|
40
51
|
*/
|
|
52
|
+
private readonly _binder: AiContextBinder;
|
|
41
53
|
private readonly _queue: Queue<Message.Message | ContextBinding>;
|
|
54
|
+
private readonly _toolkit?: Toolkit.Any;
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
* Blueprints bound to the conversation.
|
|
45
|
-
*/
|
|
46
|
-
private readonly _context: AiContextBinder;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Toolkit from the current session request.
|
|
50
|
-
*/
|
|
51
|
-
private _toolkit: Toolkit.WithHandler<any> | undefined;
|
|
52
|
-
|
|
53
|
-
public constructor(queue: Queue<Message.Message | ContextBinding>) {
|
|
56
|
+
public constructor(options: AiConversationOptions) {
|
|
54
57
|
super();
|
|
55
|
-
this._queue = queue;
|
|
56
|
-
this.
|
|
58
|
+
this._queue = options.queue;
|
|
59
|
+
this._toolkit = options.toolkit;
|
|
60
|
+
invariant(this._queue);
|
|
61
|
+
this._binder = new AiContextBinder({ queue: this._queue, registry: options.registry });
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
protected override async _open(): Promise<void> {
|
|
60
|
-
|
|
61
|
-
await this._context.open();
|
|
65
|
+
await this._binder.open(this._ctx);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
public get queue() {
|
|
69
|
+
return this._queue;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
public get context() {
|
|
69
|
-
return this.
|
|
73
|
+
return this._binder;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
public get toolkit() {
|
|
@@ -74,55 +78,126 @@ export class AiConversation extends Resource {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
public async getHistory(): Promise<Message.Message[]> {
|
|
77
|
-
const queueItems = await this._queue.queryObjects();
|
|
81
|
+
const queueItems = await this._queue.queryObjects(); // TODO(burdon): Update.
|
|
78
82
|
return queueItems.filter(Obj.instanceOf(Message.Message));
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
getTools(): Effect.Effect<Record<string, Tool.Any>, never, ToolExecutionService | ToolResolverService> {
|
|
86
|
+
return Effect.gen(this, function* () {
|
|
87
|
+
const blueprints = this.context.getBlueprints();
|
|
88
|
+
const tookit = yield* createToolkit({ toolkit: this._toolkit, blueprints });
|
|
89
|
+
return tookit.tools;
|
|
90
|
+
}).pipe(Effect.orDie);
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* Creates a new cancelable request effect.
|
|
83
95
|
*/
|
|
84
|
-
createRequest(
|
|
85
|
-
params:
|
|
96
|
+
public createRequest(
|
|
97
|
+
params: AiConversationRunProps,
|
|
86
98
|
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> {
|
|
87
99
|
return Effect.gen(this, function* () {
|
|
88
|
-
const session = new AiSession();
|
|
89
100
|
const history = yield* Effect.promise(() => this.getHistory());
|
|
90
101
|
|
|
91
|
-
// Get context objects.
|
|
92
|
-
const context = yield* Effect.promise(() => this.context.query());
|
|
93
|
-
const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.loadOption).pipe(
|
|
94
|
-
Effect.map(Array.filter(Option.isSome)),
|
|
95
|
-
Effect.map(Array.map((option) => option.value)),
|
|
96
|
-
);
|
|
97
|
-
const objects = yield* Effect.forEach(context.objects.values(), DatabaseService.loadOption).pipe(
|
|
98
|
-
Effect.map(Array.filter(Option.isSome)),
|
|
99
|
-
Effect.map(Array.map((option) => option.value)),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
102
|
// Create toolkit.
|
|
103
|
-
const
|
|
104
|
-
|
|
103
|
+
const blueprints = this.context.getBlueprints();
|
|
104
|
+
const toolkit = yield* createToolkit({
|
|
105
|
+
toolkit: this._toolkit,
|
|
106
|
+
blueprints,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Context objects.
|
|
110
|
+
const objects = this.context.getObjects();
|
|
105
111
|
|
|
106
|
-
const start = Date.now();
|
|
107
112
|
log('run', {
|
|
108
113
|
history: history.length,
|
|
109
114
|
blueprints: blueprints.length,
|
|
115
|
+
tools: Object.keys(toolkit.tools).length,
|
|
110
116
|
objects: objects.length,
|
|
111
|
-
tools: this._toolkit?.tools.length ?? 0,
|
|
112
117
|
});
|
|
113
118
|
|
|
114
119
|
// Process request.
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
const session = new AiSession();
|
|
121
|
+
const messages = yield* session.run({ history, blueprints, toolkit, objects, ...params }).pipe(
|
|
122
|
+
Effect.provide(
|
|
123
|
+
Layer.mergeAll(
|
|
124
|
+
Layer.succeed(AiContextService, {
|
|
125
|
+
binder: this.context,
|
|
126
|
+
}),
|
|
127
|
+
Layer.succeed(AiConversationService, this),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
119
130
|
);
|
|
120
131
|
|
|
121
|
-
log('result', {
|
|
132
|
+
log('result', {
|
|
133
|
+
messages: messages.length,
|
|
134
|
+
duration: session.duration,
|
|
135
|
+
toolCalls: session.toolCalls,
|
|
136
|
+
});
|
|
122
137
|
|
|
123
138
|
// Append to queue.
|
|
124
139
|
yield* Effect.promise(() => this._queue.append(messages));
|
|
125
140
|
return messages;
|
|
126
|
-
})
|
|
141
|
+
});
|
|
127
142
|
}
|
|
128
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gives access to the ai conversation.
|
|
147
|
+
*/
|
|
148
|
+
export class AiConversationService extends Context.Tag('@dxos/assistant/AiConversationService')<
|
|
149
|
+
AiConversationService,
|
|
150
|
+
AiConversation
|
|
151
|
+
>() {
|
|
152
|
+
/**
|
|
153
|
+
* Create a new conversation layer from options.
|
|
154
|
+
*/
|
|
155
|
+
static layer = (options: AiConversationOptions): Layer.Layer<AiConversationService | AiContextService> =>
|
|
156
|
+
aiContextFromConversation.pipe(
|
|
157
|
+
Layer.provideMerge(
|
|
158
|
+
Layer.scoped(
|
|
159
|
+
AiConversationService,
|
|
160
|
+
Effect.gen(function* () {
|
|
161
|
+
const conversation = yield* acquireReleaseResource(() => new AiConversation(options));
|
|
162
|
+
return conversation;
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create a new conversation with a new queue.
|
|
170
|
+
*/
|
|
171
|
+
static layerNewQueue = (
|
|
172
|
+
options?: Omit<AiConversationOptions, 'queue'>,
|
|
173
|
+
): Layer.Layer<AiConversationService | AiContextService, never, QueueService> =>
|
|
174
|
+
Layer.unwrapScoped(
|
|
175
|
+
Effect.gen(function* () {
|
|
176
|
+
return AiConversationService.layer({
|
|
177
|
+
...options,
|
|
178
|
+
queue: yield* QueueService.createQueue<Message.Message | ContextBinding>(),
|
|
179
|
+
});
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Run a prompt in the current conversation.
|
|
185
|
+
*/
|
|
186
|
+
static run = (
|
|
187
|
+
params: AiConversationRunProps,
|
|
188
|
+
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements | AiConversationService> =>
|
|
189
|
+
Effect.gen(function* () {
|
|
190
|
+
const conversation = yield* AiConversationService;
|
|
191
|
+
return yield* conversation.createRequest(params);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const aiContextFromConversation = Layer.effect(
|
|
196
|
+
AiContextService,
|
|
197
|
+
Effect.gen(function* () {
|
|
198
|
+
const conversation = yield* AiConversationService;
|
|
199
|
+
return {
|
|
200
|
+
binder: conversation.context,
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
203
|
+
);
|
package/src/diff.test.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { createDocAccessor } from '@dxos/echo-db';
|
|
8
8
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
@@ -12,10 +12,17 @@ import { trim } from '@dxos/util';
|
|
|
12
12
|
import { applyDiffs } from './diff';
|
|
13
13
|
|
|
14
14
|
describe('diff', () => {
|
|
15
|
+
let builder: EchoTestBuilder;
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
builder = await new EchoTestBuilder().open();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await builder.close();
|
|
22
|
+
});
|
|
23
|
+
|
|
15
24
|
it('should apply diffs', async () => {
|
|
16
|
-
const
|
|
17
|
-
const { db, graph } = await builder.createDatabase();
|
|
18
|
-
graph.schemaRegistry.addSchema([Text.Text]);
|
|
25
|
+
const { db } = await builder.createDatabase({ types: [Text.Text] });
|
|
19
26
|
|
|
20
27
|
const document = trim`
|
|
21
28
|
# Hello World
|
|
@@ -40,4 +47,28 @@ describe('diff', () => {
|
|
|
40
47
|
But not this one.
|
|
41
48
|
`);
|
|
42
49
|
});
|
|
50
|
+
|
|
51
|
+
it('should apply diffs that append content at the end', async () => {
|
|
52
|
+
const { db } = await builder.createDatabase({ types: [Text.Text] });
|
|
53
|
+
|
|
54
|
+
const document = '# Shopping list';
|
|
55
|
+
|
|
56
|
+
const text = db.add(Text.make(document));
|
|
57
|
+
const accessor = createDocAccessor(text, ['content']);
|
|
58
|
+
const result = applyDiffs(accessor, ['- # Shopping list', '+ # Shopping list\n- Milk']);
|
|
59
|
+
|
|
60
|
+
expect(result).toBe('# Shopping list\n- Milk');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should append text to an empty document', async () => {
|
|
64
|
+
const builder = new EchoTestBuilder();
|
|
65
|
+
const { db, graph } = await builder.createDatabase();
|
|
66
|
+
await graph.schemaRegistry.register([Text.Text]);
|
|
67
|
+
|
|
68
|
+
const text = db.add(Text.make(''));
|
|
69
|
+
const accessor = createDocAccessor(text, ['content']);
|
|
70
|
+
const result = applyDiffs(accessor, ['+ # Shopping list']);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe('# Shopping list');
|
|
73
|
+
});
|
|
43
74
|
});
|
package/src/diff.ts
CHANGED
|
@@ -47,9 +47,10 @@ export const reduceDiffs = (diffs: readonly string[]): Diff[] => {
|
|
|
47
47
|
}
|
|
48
48
|
return acc;
|
|
49
49
|
}
|
|
50
|
+
default: {
|
|
51
|
+
throw new Error(`Invalid diff format: each line must start with "- " or "+ ", got: ${JSON.stringify(diff)}`);
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
|
-
|
|
52
|
-
return acc;
|
|
53
54
|
}, []);
|
|
54
55
|
};
|
|
55
56
|
|
|
@@ -65,7 +66,11 @@ export const computeDiffsWithCursors = <T>(accessor: DocAccessor<T>, diffs: read
|
|
|
65
66
|
.filter(isNonNullable);
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
export const applyDiffs = <T>(
|
|
69
|
+
export const applyDiffs = <T>(
|
|
70
|
+
accessor: DocAccessor<T>,
|
|
71
|
+
diffs: readonly string[],
|
|
72
|
+
{ errorOnNotFound = false }: { errorOnNotFound?: boolean } = {},
|
|
73
|
+
): string => {
|
|
69
74
|
for (const diff of reduceDiffs(diffs)) {
|
|
70
75
|
accessor.handle.change((doc: Doc<T>) => {
|
|
71
76
|
const text = DocAccessor.getValue<string>(accessor);
|
|
@@ -75,7 +80,11 @@ export const applyDiffs = <T>(accessor: DocAccessor<T>, diffs: readonly string[]
|
|
|
75
80
|
// NOTE: This only replaces the first match.
|
|
76
81
|
A.splice(doc, accessor.path as A.Prop[], idx, diff.match.length, diff.replace);
|
|
77
82
|
} else {
|
|
78
|
-
|
|
83
|
+
if (errorOnNotFound) {
|
|
84
|
+
throw new Error(`Diff not found: ${diff.match}`);
|
|
85
|
+
} else {
|
|
86
|
+
log.warn('diff not found', diff);
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
});
|
|
81
90
|
}
|
package/src/errors.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { BaseError } from '@dxos/errors';
|
|
|
7
7
|
/**
|
|
8
8
|
* Generic error from AI model.
|
|
9
9
|
*/
|
|
10
|
-
export class AiModelError extends BaseError.extend('
|
|
10
|
+
export class AiModelError extends BaseError.extend('AiModelError', 'AI model error') {}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Generic error for AI agent execution.
|
|
14
14
|
*/
|
|
15
|
-
export class AiAssistantError extends BaseError.extend('
|
|
15
|
+
export class AiAssistantError extends BaseError.extend('AiAssistantError', 'AI assistant error') {}
|
|
@@ -6,7 +6,7 @@ import { describe, expect, it } from '@effect/vitest';
|
|
|
6
6
|
|
|
7
7
|
import { AgentStatus } from '@dxos/ai';
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { ObjectId } from '@dxos/
|
|
9
|
+
import { ObjectId } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { type ContentBlock, Message } from '@dxos/types';
|
|
12
12
|
|
package/src/execution-graph.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { AgentStatus } from '@dxos/ai';
|
|
6
|
-
import { Obj, type Ref } from '@dxos/echo';
|
|
6
|
+
import { type Entity, Obj, type Ref } from '@dxos/echo';
|
|
7
7
|
import { MESSAGE_PROPERTY_TOOL_CALL_ID } from '@dxos/functions';
|
|
8
8
|
import { type ObjectId } from '@dxos/keys';
|
|
9
9
|
import { LogLevel } from '@dxos/log';
|
|
@@ -65,7 +65,7 @@ export class ExecutionGraph {
|
|
|
65
65
|
/**
|
|
66
66
|
* Adds events to the graph.
|
|
67
67
|
*/
|
|
68
|
-
addEvents(events:
|
|
68
|
+
addEvents(events: Entity.Unknown[]) {
|
|
69
69
|
const sortedEvents = this.sortObjectsByCreated(events);
|
|
70
70
|
for (const event of sortedEvents) {
|
|
71
71
|
if (Obj.instanceOf(Message.Message, event)) {
|
|
@@ -496,7 +496,7 @@ const getParentId = (message: Message.Message) => {
|
|
|
496
496
|
}
|
|
497
497
|
};
|
|
498
498
|
|
|
499
|
-
const stringifyRef = (ref: Ref.
|
|
499
|
+
const stringifyRef = (ref: Ref.Unknown) => {
|
|
500
500
|
if (ref.target) {
|
|
501
501
|
return stringifyObject(ref.target);
|
|
502
502
|
}
|
|
@@ -504,6 +504,6 @@ const stringifyRef = (ref: Ref.Any) => {
|
|
|
504
504
|
return ref.dxn.asEchoDXN()?.echoId ?? ref.dxn.asQueueDXN()?.objectId ?? '';
|
|
505
505
|
};
|
|
506
506
|
|
|
507
|
-
const stringifyObject = (obj: Obj.
|
|
507
|
+
const stringifyObject = (obj: Obj.Unknown) => {
|
|
508
508
|
return Obj.getLabel(obj) ?? Obj.getTypename(obj) ?? obj.id;
|
|
509
509
|
};
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { beforeAll, describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type Database } from '@dxos/echo';
|
|
8
8
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
9
9
|
import { FunctionExecutor, ServiceContainer } from '@dxos/functions-runtime';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
|
-
import {
|
|
11
|
+
import { TestSchema } from '@dxos/schema/testing';
|
|
12
12
|
import { type Message, Organization, Person } from '@dxos/types';
|
|
13
13
|
import { createTestData } from '@dxos/types/testing';
|
|
14
14
|
import { range } from '@dxos/util';
|
|
@@ -19,25 +19,25 @@ import { extractionNerFunction } from './extraction-ner-function';
|
|
|
19
19
|
|
|
20
20
|
describe.skip('NER EntityExtraction', () => {
|
|
21
21
|
let builder: EchoTestBuilder;
|
|
22
|
-
let db:
|
|
22
|
+
let db: Database.Database;
|
|
23
23
|
let executor: FunctionExecutor;
|
|
24
24
|
let testData: {
|
|
25
25
|
transcriptJosiah: Message.Message[];
|
|
26
26
|
transcriptWoflram: Message.Message[];
|
|
27
27
|
transcriptMessages: Message.Message[];
|
|
28
|
-
documents:
|
|
28
|
+
documents: TestSchema.DocumentType[];
|
|
29
29
|
contacts: Record<string, Person.Person>;
|
|
30
30
|
organizations: Record<string, Organization.Organization>;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const TYPES = [Organization.Organization, Person.Person,
|
|
33
|
+
const TYPES = [Organization.Organization, Person.Person, TestSchema.Person, TestSchema.DocumentType];
|
|
34
34
|
|
|
35
35
|
beforeAll(async () => {
|
|
36
36
|
// TODO(dmaretskyi): Helper to scaffold this from a config.
|
|
37
37
|
builder = await new EchoTestBuilder().open();
|
|
38
|
-
const { db: db1 } = await builder.createDatabase(
|
|
38
|
+
const { db: db1 } = await builder.createDatabase();
|
|
39
39
|
db = db1;
|
|
40
|
-
db.graph.schemaRegistry.
|
|
40
|
+
await db.graph.schemaRegistry.register(TYPES);
|
|
41
41
|
const data = createTestData();
|
|
42
42
|
testData = {
|
|
43
43
|
transcriptJosiah: data.transcriptJosiah,
|
|
@@ -20,7 +20,7 @@ export const extractionNerFunction = defineFunction({
|
|
|
20
20
|
throw new Error('not implemented');
|
|
21
21
|
// log.info('input', { message, options });
|
|
22
22
|
// const startTime = performance.now();
|
|
23
|
-
// const { db } = context.getService(
|
|
23
|
+
// const { db } = context.getService(Database.Service);
|
|
24
24
|
// const { extractFullEntities } = await import('./named-entity-recognition');
|
|
25
25
|
|
|
26
26
|
// const entitiesPromise = Promise.all(
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
7
|
import { asyncTimeout } from '@dxos/async';
|
|
8
|
-
import {
|
|
8
|
+
import { Type } from '@dxos/echo';
|
|
9
9
|
import { type FunctionDefinition } from '@dxos/functions';
|
|
10
10
|
import { type FunctionExecutor } from '@dxos/functions-runtime';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
@@ -13,7 +13,7 @@ import { Message } from '@dxos/types';
|
|
|
13
13
|
|
|
14
14
|
export const ExtractionInput = Schema.Struct({
|
|
15
15
|
message: Message.Message,
|
|
16
|
-
objects: Schema.optional(Schema.Array(
|
|
16
|
+
objects: Schema.optional(Schema.Array(Type.Obj)),
|
|
17
17
|
options: Schema.optional(
|
|
18
18
|
Schema.Struct({
|
|
19
19
|
timeout: Schema.optional(Schema.Number),
|
|
@@ -29,7 +29,7 @@ export const ExtractionOutput = Schema.Struct({
|
|
|
29
29
|
});
|
|
30
30
|
export interface ExtractionOutput extends Schema.Schema.Type<typeof ExtractionOutput> {}
|
|
31
31
|
|
|
32
|
-
export type
|
|
32
|
+
export type ProcessTranscriptMessageProps = {
|
|
33
33
|
input: ExtractionInput;
|
|
34
34
|
function: FunctionDefinition<ExtractionInput, ExtractionOutput>;
|
|
35
35
|
executor: FunctionExecutor;
|
|
@@ -55,7 +55,7 @@ export type ExtractionFunction = FunctionDefinition<ExtractionInput, ExtractionO
|
|
|
55
55
|
* Extract entities from the transcript message and add them to the message.
|
|
56
56
|
*/
|
|
57
57
|
// TODO(dmaretskyi): Move context to a vector search index.
|
|
58
|
-
export const processTranscriptMessage = async (params:
|
|
58
|
+
export const processTranscriptMessage = async (params: ProcessTranscriptMessageProps): Promise<ExtractionOutput> => {
|
|
59
59
|
try {
|
|
60
60
|
if (params.options?.timeout && params.options.timeout > 0) {
|
|
61
61
|
return await asyncTimeout(params.executor.invoke(params.function, params.input), params.options.timeout);
|
package/src/extraction/quotes.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
|
-
import { Filter, Query } from '@dxos/echo';
|
|
8
|
-
import { ObjectId } from '@dxos/
|
|
9
|
-
import { type EchoDatabase } from '@dxos/echo-db';
|
|
7
|
+
import { type Database, Filter, Query } from '@dxos/echo';
|
|
8
|
+
import { ObjectId } from '@dxos/keys';
|
|
10
9
|
import { DXN } from '@dxos/keys';
|
|
11
10
|
import { log } from '@dxos/log';
|
|
12
11
|
|
|
@@ -28,10 +27,10 @@ export const ReferencedQuotes = Schema.Struct({
|
|
|
28
27
|
});
|
|
29
28
|
export type ReferencedQuotes = Schema.Schema.Type<typeof ReferencedQuotes>;
|
|
30
29
|
|
|
31
|
-
export const findQuotes = async (quotes: string[], db:
|
|
30
|
+
export const findQuotes = async (quotes: string[], db: Database.Database): Promise<ReferencedQuotes> => {
|
|
32
31
|
const references = await Promise.all(
|
|
33
32
|
quotes.map(async (quote) => {
|
|
34
|
-
const
|
|
33
|
+
const objects = await db.query(Query.select(Filter.text(quote, { type: 'vector' }))).run();
|
|
35
34
|
log.info('found quotes in vector index', { objects, quote });
|
|
36
35
|
return objects.length > 0 ? [{ id: objects[0].id.toString(), quote }] : [];
|
|
37
36
|
}),
|
|
@@ -9,14 +9,17 @@ import * as Effect from 'effect/Effect';
|
|
|
9
9
|
import * as Layer from 'effect/Layer';
|
|
10
10
|
import * as Record from 'effect/Record';
|
|
11
11
|
import * as Schema from 'effect/Schema';
|
|
12
|
+
import type * as SchemaAST from 'effect/SchemaAST';
|
|
12
13
|
|
|
13
14
|
import { AiToolNotFoundError, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
14
15
|
import { todo } from '@dxos/debug';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { Function, FunctionDefinition, FunctionInvocationService } from '@dxos/functions';
|
|
16
|
+
import { Type } from '@dxos/echo';
|
|
17
|
+
import { type FunctionDefinition, FunctionInvocationService } from '@dxos/functions';
|
|
18
18
|
import { invariant } from '@dxos/invariant';
|
|
19
19
|
|
|
20
|
+
import { GenericToolkit } from '../session';
|
|
21
|
+
import { RefFromLLM } from '../types';
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* Constructs a `ToolResolverService` whose `resolve(id)` looks up tools in the following order:
|
|
22
25
|
* 1. Toolkit: return an existing tool from the provided `toolkit` if one is present under `id`.
|
|
@@ -26,51 +29,50 @@ import { invariant } from '@dxos/invariant';
|
|
|
26
29
|
*
|
|
27
30
|
* If none of the above yield a match, the effect fails with `AiToolNotFoundError`.
|
|
28
31
|
*
|
|
29
|
-
* Requires `
|
|
32
|
+
* Requires `Database.Service` in the environment.
|
|
30
33
|
*/
|
|
31
|
-
export const makeToolResolverFromFunctions = (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
export const makeToolResolverFromFunctions = (): Layer.Layer<
|
|
35
|
+
ToolResolverService,
|
|
36
|
+
never,
|
|
37
|
+
GenericToolkit.Provider | FunctionInvocationService
|
|
38
|
+
> => {
|
|
35
39
|
return Layer.effect(
|
|
36
40
|
ToolResolverService,
|
|
37
41
|
Effect.gen(function* () {
|
|
38
|
-
const
|
|
42
|
+
const toolkitProvider = yield* GenericToolkit.Provider;
|
|
43
|
+
const functionInvocationService = yield* FunctionInvocationService;
|
|
39
44
|
return {
|
|
40
45
|
resolve: (id): Effect.Effect<Tool.Any, AiToolNotFoundError> =>
|
|
41
46
|
Effect.gen(function* () {
|
|
42
|
-
const
|
|
47
|
+
const toolkit = toolkitProvider.getToolkit();
|
|
48
|
+
|
|
49
|
+
const tool = toolkit.toolkit.tools[id];
|
|
43
50
|
if (tool) {
|
|
44
51
|
return tool;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? FunctionDefinition.deserialize(dbFunction)
|
|
53
|
-
: functions.find((fn) => fn.key === id);
|
|
54
|
-
|
|
55
|
-
if (!functionDef) {
|
|
56
|
-
return yield* Effect.fail(new AiToolNotFoundError(id));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return projectFunctionToTool(functionDef);
|
|
60
|
-
}).pipe(Effect.provideService(DatabaseService, dbService)),
|
|
54
|
+
return yield* functionInvocationService.resolveFunction(id).pipe(
|
|
55
|
+
Effect.map(projectFunctionToTool),
|
|
56
|
+
Effect.catchTag('FunctionNotFound', () => Effect.fail(new AiToolNotFoundError(id))),
|
|
57
|
+
);
|
|
58
|
+
}),
|
|
61
59
|
} satisfies Context.Tag.Service<ToolResolverService>;
|
|
62
60
|
}),
|
|
63
61
|
);
|
|
64
62
|
};
|
|
65
63
|
|
|
66
|
-
export const makeToolExecutionServiceFromFunctions = (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
export const makeToolExecutionServiceFromFunctions = (): Layer.Layer<
|
|
65
|
+
ToolExecutionService,
|
|
66
|
+
never,
|
|
67
|
+
FunctionInvocationService | GenericToolkit.Provider
|
|
68
|
+
> => {
|
|
70
69
|
return Layer.effect(
|
|
71
70
|
ToolExecutionService,
|
|
72
71
|
Effect.gen(function* () {
|
|
73
|
-
const
|
|
72
|
+
const toolkitProvider = yield* GenericToolkit.Provider;
|
|
73
|
+
const toolkit = toolkitProvider.getToolkit();
|
|
74
|
+
|
|
75
|
+
const toolkitHandler = yield* toolkit.toolkit.pipe(Effect.provide(toolkit.layer));
|
|
74
76
|
invariant(isHandlerLike(toolkitHandler));
|
|
75
77
|
const functionInvocationService = yield* FunctionInvocationService;
|
|
76
78
|
|
|
@@ -82,6 +84,7 @@ export const makeToolExecutionServiceFromFunctions = (
|
|
|
82
84
|
if (Tool.isProviderDefined(tool)) {
|
|
83
85
|
throw new Error('Attempted to call a provider-defined tool');
|
|
84
86
|
}
|
|
87
|
+
|
|
85
88
|
// TODO(wittjosiah): Everything is `never` here.
|
|
86
89
|
const { result } = yield* (toolkitHandler.handle as any)(tool.name, input);
|
|
87
90
|
return result;
|
|
@@ -104,6 +107,11 @@ export const makeToolExecutionServiceFromFunctions = (
|
|
|
104
107
|
);
|
|
105
108
|
};
|
|
106
109
|
|
|
110
|
+
export const ToolExecutionServices = Layer.mergeAll(
|
|
111
|
+
makeToolResolverFromFunctions(),
|
|
112
|
+
makeToolExecutionServiceFromFunctions(),
|
|
113
|
+
);
|
|
114
|
+
|
|
107
115
|
class FunctionToolAnnotation extends Context.Tag('@dxos/assistant/FunctionToolAnnotation')<
|
|
108
116
|
FunctionToolAnnotation,
|
|
109
117
|
{ definition: FunctionDefinition.Any }
|
|
@@ -149,7 +157,9 @@ const makeToolName = (name: string) => {
|
|
|
149
157
|
const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<string, Schema.Schema<any, any>> => {
|
|
150
158
|
switch (schema.ast._tag) {
|
|
151
159
|
case 'TypeLiteral':
|
|
152
|
-
return Object.fromEntries(
|
|
160
|
+
return Object.fromEntries(
|
|
161
|
+
schema.ast.propertySignatures.map((prop) => [prop.name, Schema.make(mapSchemaTypeForLLM(prop.type))]),
|
|
162
|
+
);
|
|
153
163
|
case 'VoidKeyword':
|
|
154
164
|
return {};
|
|
155
165
|
default:
|
|
@@ -157,6 +167,21 @@ const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<s
|
|
|
157
167
|
}
|
|
158
168
|
};
|
|
159
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Picks an LLM-friendly schema type for the given schema AST.
|
|
172
|
+
* The picked schema type decodes to the original schema type.
|
|
173
|
+
*/
|
|
174
|
+
const mapSchemaTypeForLLM = (ast: SchemaAST.AST): SchemaAST.AST => {
|
|
175
|
+
if (Type.Ref.isRefSchemaAST(ast)) {
|
|
176
|
+
const description = ast.annotations.description
|
|
177
|
+
? ast.annotations.description + '\n' + RefFromLLM.ast.annotations.description
|
|
178
|
+
: (RefFromLLM.ast.annotations.description as string);
|
|
179
|
+
return RefFromLLM.annotations({ description }).ast;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return ast;
|
|
183
|
+
};
|
|
184
|
+
|
|
160
185
|
const isHandlerLike = (value: unknown): value is Toolkit.WithHandler<Record<string, Tool.Any>> => {
|
|
161
186
|
return typeof (value as any).tools === 'object' && typeof (value as any).handle === 'function';
|
|
162
187
|
};
|