@dxos/assistant 0.8.2 → 0.8.3-main.7f5a14c
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/index.mjs +231 -84
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +240 -89
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +231 -84
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/blueprint/ai.test.d.ts +2 -0
- package/dist/types/src/blueprint/ai.test.d.ts.map +1 -0
- package/dist/types/src/blueprint/blueprint.d.ts +23 -48
- package/dist/types/src/blueprint/blueprint.d.ts.map +1 -1
- package/dist/types/src/blueprint/logger.d.ts +24 -2
- package/dist/types/src/blueprint/logger.d.ts.map +1 -1
- package/dist/types/src/blueprint/machine.d.ts +33 -9
- package/dist/types/src/blueprint/machine.d.ts.map +1 -1
- package/dist/types/src/research/graph.d.ts +6 -5
- package/dist/types/src/research/graph.d.ts.map +1 -1
- package/package.json +30 -20
- package/src/blueprint/ai.test.ts +156 -0
- package/src/blueprint/blueprint.test.ts +23 -14
- package/src/blueprint/blueprint.ts +22 -42
- package/src/blueprint/logger.ts +108 -19
- package/src/blueprint/machine.ts +75 -37
- package/src/research/graph.ts +65 -17
- package/src/research/research.test.ts +1 -1
- package/src/research/research.ts +1 -1
package/src/blueprint/machine.ts
CHANGED
|
@@ -6,21 +6,39 @@ import { Match, Schema } from 'effect';
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createTool,
|
|
9
|
-
type ExecutableTool,
|
|
10
|
-
Message,
|
|
11
|
-
ToolResult,
|
|
12
9
|
type AIServiceClient,
|
|
10
|
+
Message,
|
|
13
11
|
type MessageContentBlock,
|
|
12
|
+
ToolResult,
|
|
14
13
|
type ToolUseContentBlock,
|
|
14
|
+
type ToolRegistry,
|
|
15
15
|
} from '@dxos/ai';
|
|
16
16
|
import { Event } from '@dxos/async';
|
|
17
17
|
import { create } from '@dxos/echo-schema';
|
|
18
18
|
import { type ObjectId } from '@dxos/keys';
|
|
19
19
|
import { log } from '@dxos/log';
|
|
20
|
+
import { isNonNullable } from '@dxos/util';
|
|
20
21
|
|
|
21
22
|
import type { Blueprint, BlueprintStep } from './blueprint';
|
|
22
23
|
import { AISession } from '../session';
|
|
23
24
|
|
|
25
|
+
const SYSTEM_PROMPT = `
|
|
26
|
+
You are a smart Rule-Following Agent.
|
|
27
|
+
Rule-Following Agent executes the command that the user sent in the last message.
|
|
28
|
+
After doing the work, the Rule-Following Agent calls report tool.
|
|
29
|
+
If the Rule-Following agent believes that no action is needed, it calls the report tool with "skipped" status.
|
|
30
|
+
If the Rule-Following Agent is unable to perform the task, it calls the report tool with "bail" status.
|
|
31
|
+
Rule-Following Agent explains the reason it is unable to perform the task before bailing.
|
|
32
|
+
The Rule-Following Agent can express creativity and imagination in the way it performs the task.
|
|
33
|
+
The Rule-Following Agent precisely follows the instructions.
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export type BlueprintTraceStep = {
|
|
37
|
+
status: 'done' | 'bailed' | 'skipped';
|
|
38
|
+
stepId: ObjectId;
|
|
39
|
+
comment: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
24
42
|
export type BlueprintMachineState = {
|
|
25
43
|
history: Message[];
|
|
26
44
|
trace: BlueprintTraceStep[];
|
|
@@ -33,14 +51,8 @@ const INITIAL_STATE: BlueprintMachineState = {
|
|
|
33
51
|
trace: [],
|
|
34
52
|
};
|
|
35
53
|
|
|
36
|
-
export type BlueprintTraceStep = {
|
|
37
|
-
status: 'done' | 'bailed' | 'skipped';
|
|
38
|
-
stepId: ObjectId;
|
|
39
|
-
comment: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
54
|
type ExecutionOptions = {
|
|
43
|
-
|
|
55
|
+
aiClient: AIServiceClient;
|
|
44
56
|
|
|
45
57
|
/**
|
|
46
58
|
* Input to the blueprint.
|
|
@@ -48,6 +60,18 @@ type ExecutionOptions = {
|
|
|
48
60
|
input?: unknown;
|
|
49
61
|
};
|
|
50
62
|
|
|
63
|
+
export type BlueprintEvent =
|
|
64
|
+
| { type: 'begin' }
|
|
65
|
+
| { type: 'end' }
|
|
66
|
+
| { type: 'step-start'; step: BlueprintStep }
|
|
67
|
+
| { type: 'step-complete'; step: BlueprintStep }
|
|
68
|
+
| { type: 'message'; message: Message }
|
|
69
|
+
| { type: 'block'; block: MessageContentBlock };
|
|
70
|
+
|
|
71
|
+
export interface BlueprintLogger {
|
|
72
|
+
log(event: BlueprintEvent): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
/**
|
|
52
76
|
* Blueprint state machine.
|
|
53
77
|
*/
|
|
@@ -59,43 +83,62 @@ export class BlueprintMachine {
|
|
|
59
83
|
public readonly message = new Event<Message>();
|
|
60
84
|
public readonly block = new Event<MessageContentBlock>();
|
|
61
85
|
|
|
62
|
-
|
|
86
|
+
// TODO(burdon): Replace events with logger?
|
|
87
|
+
private logger?: BlueprintLogger;
|
|
63
88
|
|
|
64
|
-
|
|
89
|
+
private _state: BlueprintMachineState = structuredClone(INITIAL_STATE);
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
readonly registry: ToolRegistry,
|
|
93
|
+
readonly blueprint: Blueprint,
|
|
94
|
+
) {}
|
|
95
|
+
|
|
96
|
+
get state(): BlueprintMachineState {
|
|
97
|
+
return this._state;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setLogger(logger: BlueprintLogger): this {
|
|
101
|
+
this.logger = logger;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
65
104
|
|
|
66
105
|
async runToCompletion(options: ExecutionOptions): Promise<void> {
|
|
67
106
|
log.info('runToCompletion', options);
|
|
68
107
|
|
|
69
108
|
this.begin.emit();
|
|
109
|
+
this.logger?.log({ type: 'begin' });
|
|
110
|
+
|
|
70
111
|
let firstStep = true;
|
|
71
|
-
while (this.
|
|
112
|
+
while (this._state.state !== 'done') {
|
|
72
113
|
const input = firstStep ? options.input : undefined;
|
|
73
|
-
|
|
74
114
|
firstStep = false;
|
|
75
|
-
this.state = await this._execStep(this.state, {
|
|
76
|
-
input,
|
|
77
|
-
aiService: options.aiService,
|
|
78
|
-
});
|
|
79
115
|
|
|
80
|
-
this.
|
|
116
|
+
this._state = await this._execStep(this._state, { input, aiClient: options.aiClient });
|
|
117
|
+
|
|
118
|
+
const step = this.blueprint.steps.find((step) => step.id === this._state.trace.at(-1)?.stepId)!;
|
|
119
|
+
this.stepComplete.emit(step);
|
|
120
|
+
this.logger?.log({ type: 'step-complete', step });
|
|
81
121
|
|
|
82
|
-
if (this.
|
|
122
|
+
if (this._state.state === 'bail') {
|
|
83
123
|
throw new Error('Agent unable to follow the blueprint');
|
|
84
124
|
}
|
|
85
125
|
}
|
|
86
126
|
|
|
87
127
|
this.end.emit();
|
|
128
|
+
this.logger?.log({ type: 'end' });
|
|
88
129
|
}
|
|
89
130
|
|
|
90
131
|
private async _execStep(state: BlueprintMachineState, options: ExecutionOptions): Promise<BlueprintMachineState> {
|
|
91
|
-
const prevStep = this.blueprint.steps.findIndex((step) => step.id === this.
|
|
132
|
+
const prevStep = this.blueprint.steps.findIndex((step) => step.id === this._state.trace.at(-1)?.stepId);
|
|
92
133
|
if (prevStep === this.blueprint.steps.length - 1) {
|
|
93
134
|
throw new Error('Done execution blueprint');
|
|
94
135
|
}
|
|
95
136
|
|
|
96
137
|
const nextStep = this.blueprint.steps[prevStep + 1];
|
|
97
138
|
const onLastStep = prevStep === this.blueprint.steps.length - 2;
|
|
139
|
+
|
|
98
140
|
this.stepStart.emit(nextStep);
|
|
141
|
+
this.logger?.log({ type: 'step-start', step: nextStep });
|
|
99
142
|
|
|
100
143
|
const ReportSchema = Schema.Struct({
|
|
101
144
|
status: Schema.Literal('done', 'bailed', 'skipped').annotations({
|
|
@@ -109,7 +152,7 @@ export class BlueprintMachine {
|
|
|
109
152
|
|
|
110
153
|
const report = createTool('system', {
|
|
111
154
|
name: 'report',
|
|
112
|
-
description: 'This tool reports that the agent has completed the task or is unable to do so',
|
|
155
|
+
description: 'This tool reports that the agent has completed the task or is unable to do so.',
|
|
113
156
|
schema: ReportSchema,
|
|
114
157
|
execute: async (input) => ToolResult.Break(input),
|
|
115
158
|
});
|
|
@@ -130,30 +173,25 @@ export class BlueprintMachine {
|
|
|
130
173
|
}),
|
|
131
174
|
]
|
|
132
175
|
: [];
|
|
133
|
-
inputMessages.forEach((message) =>
|
|
176
|
+
inputMessages.forEach((message) => {
|
|
177
|
+
this.message.emit(message);
|
|
178
|
+
this.logger?.log({ type: 'message', message });
|
|
179
|
+
});
|
|
134
180
|
|
|
181
|
+
// TODO(wittjosiah): Warn if tool is not found.
|
|
182
|
+
const tools = nextStep.tools?.map((tool) => this.registry.get(tool)).filter(isNonNullable) ?? [];
|
|
135
183
|
const messages = await session.run({
|
|
136
|
-
systemPrompt:
|
|
137
|
-
You are a smart Rule-Following Agent.
|
|
138
|
-
Rule-Following Agent executes the command that the user sent in the last message.
|
|
139
|
-
After doing the work, the Rule-Following Agent calls report tool.
|
|
140
|
-
If the Rule-Following agent believes that no action is needed, it calls the report tool with "skipped" status.
|
|
141
|
-
If the Rule-Following Agent is unable to perform the task, it calls the report tool with "bail" status.
|
|
142
|
-
Rule-Following Agent explains the reason it is unable to perform the task before bailing.
|
|
143
|
-
The Rule-Following Agent can express creativity and imagination in the way it performs the task.
|
|
144
|
-
The Rule-Following Agent precisely follows the instructions.
|
|
145
|
-
`,
|
|
184
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
146
185
|
history: [...state.history, ...inputMessages],
|
|
147
|
-
tools: [...
|
|
186
|
+
tools: [...tools, report],
|
|
148
187
|
artifacts: [],
|
|
149
|
-
client: options.
|
|
188
|
+
client: options.aiClient,
|
|
150
189
|
prompt: nextStep.instructions,
|
|
151
190
|
});
|
|
152
191
|
|
|
153
192
|
const { messages: trimmedHistory, call: lastBlock } = popLastToolCall(messages);
|
|
154
|
-
|
|
155
193
|
if (!lastBlock) {
|
|
156
|
-
// TODO(dmaretskyi): Handle this with grace
|
|
194
|
+
// TODO(dmaretskyi): Handle this with grace.
|
|
157
195
|
throw new Error('Agent did not call the report tool');
|
|
158
196
|
}
|
|
159
197
|
|
package/src/research/graph.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { Schema, identity, Option, SchemaAST } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { createTool, ToolResult } from '@dxos/ai';
|
|
8
|
-
import {
|
|
8
|
+
import type { Obj, Relation } from '@dxos/echo';
|
|
9
|
+
import { type EchoDatabase, type Queue } from '@dxos/echo-db';
|
|
9
10
|
import { isEncodedReference } from '@dxos/echo-protocol';
|
|
10
11
|
import {
|
|
11
12
|
EntityKind,
|
|
@@ -20,14 +21,16 @@ import {
|
|
|
20
21
|
ObjectId,
|
|
21
22
|
Query,
|
|
22
23
|
ReferenceAnnotationId,
|
|
24
|
+
type BaseObject,
|
|
25
|
+
RelationSourceDXNId,
|
|
26
|
+
RelationTargetDXNId,
|
|
23
27
|
RelationSourceId,
|
|
24
28
|
RelationTargetId,
|
|
25
|
-
type BaseObject,
|
|
26
29
|
} from '@dxos/echo-schema';
|
|
27
30
|
import { mapAst } from '@dxos/effect';
|
|
28
31
|
import { DXN } from '@dxos/keys';
|
|
29
32
|
import { log } from '@dxos/log';
|
|
30
|
-
import { deepMapValues } from '@dxos/util';
|
|
33
|
+
import { deepMapValues, isNonNullable } from '@dxos/util';
|
|
31
34
|
|
|
32
35
|
// TODO(burdon): Unify with the graph schema.
|
|
33
36
|
export const Subgraph = Schema.Struct({
|
|
@@ -95,7 +98,7 @@ const isSchemaAddressableByDxn = (schema: Schema.Schema.AnyNoContext, dxn: DXN):
|
|
|
95
98
|
/**
|
|
96
99
|
* Perform vector search in the local database.
|
|
97
100
|
*/
|
|
98
|
-
export const createLocalSearchTool = (db: EchoDatabase) => {
|
|
101
|
+
export const createLocalSearchTool = (db: EchoDatabase, queue?: Queue) => {
|
|
99
102
|
return createTool('search', {
|
|
100
103
|
name: 'local_search',
|
|
101
104
|
description: 'Search the local database for information using a vector index',
|
|
@@ -106,9 +109,16 @@ export const createLocalSearchTool = (db: EchoDatabase) => {
|
|
|
106
109
|
}),
|
|
107
110
|
execute: async ({ query }) => {
|
|
108
111
|
const { objects } = await db.query(Query.select(Filter.text(query, { type: 'vector' }))).run();
|
|
112
|
+
const results = [...objects];
|
|
113
|
+
if (queue) {
|
|
114
|
+
const queueObjects = await queue.queryObjects();
|
|
115
|
+
// TODO(dmaretskyi): Text search on the queue.
|
|
116
|
+
results.push(...queueObjects);
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
return ToolResult.Success(`
|
|
110
120
|
<local_context>
|
|
111
|
-
${JSON.stringify(
|
|
121
|
+
${JSON.stringify(results, null, 2)}
|
|
112
122
|
</local_context>
|
|
113
123
|
`);
|
|
114
124
|
},
|
|
@@ -141,6 +151,7 @@ export const sanitizeObjects = async (
|
|
|
141
151
|
types: Schema.Schema.AnyNoContext[],
|
|
142
152
|
data: Record<string, readonly unknown[]>,
|
|
143
153
|
db: EchoDatabase,
|
|
154
|
+
queue?: Queue,
|
|
144
155
|
): Promise<BaseObject[]> => {
|
|
145
156
|
const entries = types
|
|
146
157
|
.map(
|
|
@@ -153,8 +164,8 @@ export const sanitizeObjects = async (
|
|
|
153
164
|
.flat();
|
|
154
165
|
|
|
155
166
|
const idMap = new Map<string, string>();
|
|
156
|
-
|
|
157
167
|
const existingIds = new Set<ObjectId>();
|
|
168
|
+
const enitties = new Map<ObjectId, Obj.Any | Relation.Any>();
|
|
158
169
|
|
|
159
170
|
const resolveId = (id: string): DXN | undefined => {
|
|
160
171
|
if (ObjectId.isValid(id)) {
|
|
@@ -210,23 +221,58 @@ export const sanitizeObjects = async (
|
|
|
210
221
|
}
|
|
211
222
|
delete data.source;
|
|
212
223
|
delete data.target;
|
|
213
|
-
data[
|
|
214
|
-
data[
|
|
224
|
+
data[RelationSourceDXNId] = sourceDxn;
|
|
225
|
+
data[RelationTargetDXNId] = targetDxn;
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
return
|
|
228
|
+
return {
|
|
229
|
+
data,
|
|
230
|
+
schema: entry.schema,
|
|
231
|
+
};
|
|
218
232
|
})
|
|
219
|
-
.filter((object) => !existingIds.has(object.id)); // TODO(dmaretskyi): This dissallows updating existing objects.
|
|
233
|
+
.filter((object) => !existingIds.has(object.data.id)); // TODO(dmaretskyi): This dissallows updating existing objects.
|
|
234
|
+
|
|
235
|
+
// TODO(dmaretskyi): Use ref resolver.
|
|
236
|
+
const { objects: dbObjects } = await db.query(Query.select(Filter.ids(...existingIds))).run();
|
|
237
|
+
const queueObjects = (await queue?.getObjectsById([...existingIds])) ?? [];
|
|
238
|
+
const objects = [...dbObjects, ...queueObjects].filter(isNonNullable);
|
|
220
239
|
|
|
221
|
-
const { objects } = await db.query(Query.select(Filter.ids(...existingIds))).run();
|
|
222
240
|
// TODO(dmaretskyi): Returns everything if IDs are empty!
|
|
223
|
-
log.info('objects', {
|
|
241
|
+
log.info('objects', { dbObjects, queueObjects, existingIds });
|
|
224
242
|
const missing = Array.from(existingIds).filter((id) => !objects.some((object) => object.id === id));
|
|
225
243
|
if (missing.length > 0) {
|
|
226
244
|
throw new Error(`Object IDs do not point to existing objects: ${missing.join(', ')}`);
|
|
227
245
|
}
|
|
228
246
|
|
|
229
|
-
return res
|
|
247
|
+
return res.flatMap(({ data, schema }) => {
|
|
248
|
+
let skip = false;
|
|
249
|
+
if (RelationSourceDXNId in data) {
|
|
250
|
+
const id = (data[RelationSourceDXNId] as DXN).asEchoDXN()?.echoId;
|
|
251
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
252
|
+
if (obj) {
|
|
253
|
+
delete data[RelationSourceDXNId];
|
|
254
|
+
data[RelationSourceId] = obj;
|
|
255
|
+
} else {
|
|
256
|
+
skip = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (RelationTargetDXNId in data) {
|
|
260
|
+
const id = (data[RelationTargetDXNId] as DXN).asEchoDXN()?.echoId;
|
|
261
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
262
|
+
if (obj) {
|
|
263
|
+
delete data[RelationTargetDXNId];
|
|
264
|
+
data[RelationTargetId] = obj;
|
|
265
|
+
} else {
|
|
266
|
+
skip = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!skip) {
|
|
270
|
+
const obj = create(schema, data);
|
|
271
|
+
enitties.set(obj.id, obj);
|
|
272
|
+
return [obj];
|
|
273
|
+
}
|
|
274
|
+
return [];
|
|
275
|
+
});
|
|
230
276
|
};
|
|
231
277
|
|
|
232
278
|
const SoftRef = Schema.Struct({
|
|
@@ -272,19 +318,21 @@ const preprocessSchema = (schema: Schema.Schema.AnyNoContext) => {
|
|
|
272
318
|
|
|
273
319
|
export const createGraphWriterTool = ({
|
|
274
320
|
db,
|
|
275
|
-
|
|
321
|
+
queue,
|
|
322
|
+
schema,
|
|
276
323
|
onDone = async (x) => x,
|
|
277
324
|
}: {
|
|
278
325
|
db: EchoDatabase;
|
|
279
|
-
|
|
326
|
+
queue?: Queue;
|
|
327
|
+
schema: Schema.Schema.AnyNoContext[];
|
|
280
328
|
onDone?: (data: AnyEchoObject[]) => Promise<any>;
|
|
281
329
|
}) => {
|
|
282
330
|
return createTool('graph', {
|
|
283
331
|
name: 'writer',
|
|
284
332
|
description: 'Write to the local graph database',
|
|
285
|
-
schema: createExtractionSchema(
|
|
333
|
+
schema: createExtractionSchema(schema),
|
|
286
334
|
execute: async (input) => {
|
|
287
|
-
const data = await sanitizeObjects(
|
|
335
|
+
const data = await sanitizeObjects(schema, input as any, db, queue);
|
|
288
336
|
return ToolResult.Success(await onDone(data as AnyEchoObject[]));
|
|
289
337
|
},
|
|
290
338
|
});
|
|
@@ -20,7 +20,7 @@ import { researchFn } from './research';
|
|
|
20
20
|
const REMOTE_AI = true;
|
|
21
21
|
const MOCK_SEARCH = false;
|
|
22
22
|
|
|
23
|
-
describe('Research', () => {
|
|
23
|
+
describe.skip('Research', () => {
|
|
24
24
|
let builder: EchoTestBuilder;
|
|
25
25
|
let db: EchoDatabase;
|
|
26
26
|
let executor: FunctionExecutor;
|
package/src/research/research.ts
CHANGED
|
@@ -60,7 +60,7 @@ export const researchFn = defineFunction({
|
|
|
60
60
|
});
|
|
61
61
|
session.streamEvent.on((event) => log('stream', { event }));
|
|
62
62
|
|
|
63
|
-
const graphWriteTool = createGraphWriterTool({ db,
|
|
63
|
+
const graphWriteTool = createGraphWriterTool({ db, schema: DataTypes });
|
|
64
64
|
log.info('graphWriteTool', { schema: graphWriteTool.parameters });
|
|
65
65
|
|
|
66
66
|
// TODO(dmaretskyi): Consider adding this pattern as the "Graph" output mode for the session.
|