@dxos/assistant 0.7.4 → 0.7.5-labs.071a3e2
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-AXUUNA7O.mjs +565 -0
- package/dist/lib/browser/chunk-AXUUNA7O.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1212 -47
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +15 -9
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/chunk-QPHGQINW.cjs +577 -0
- package/dist/lib/node/chunk-QPHGQINW.cjs.map +7 -0
- package/dist/lib/node/index.cjs +1212 -54
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +21 -14
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/chunk-GOVFOBIB.mjs +567 -0
- package/dist/lib/node-esm/chunk-GOVFOBIB.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1212 -47
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +15 -9
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/ai-service/client.d.ts +16 -32
- package/dist/types/src/ai-service/client.d.ts.map +1 -1
- package/dist/types/src/ai-service/index.d.ts +4 -1
- package/dist/types/src/ai-service/index.d.ts.map +1 -1
- package/dist/types/src/ai-service/parser.d.ts +136 -0
- package/dist/types/src/ai-service/parser.d.ts.map +1 -0
- package/dist/types/src/ai-service/stream.d.ts +39 -0
- package/dist/types/src/ai-service/stream.d.ts.map +1 -0
- package/dist/types/src/ai-service/stream.test.d.ts +2 -0
- package/dist/types/src/ai-service/stream.test.d.ts.map +1 -0
- package/dist/types/src/ai-service/testing.d.ts +7 -0
- package/dist/types/src/ai-service/testing.d.ts.map +1 -0
- package/dist/types/src/ai-service/transform.d.ts +27 -0
- package/dist/types/src/ai-service/transform.d.ts.map +1 -0
- package/dist/types/src/ai-service/types.d.ts +150 -0
- package/dist/types/src/ai-service/types.d.ts.map +1 -0
- package/dist/types/src/ai-service/util/index.d.ts +2 -0
- package/dist/types/src/ai-service/util/index.d.ts.map +1 -0
- package/dist/types/src/ai-service/{sse.d.ts → util/sse.d.ts} +5 -0
- package/dist/types/src/ai-service/util/sse.d.ts.map +1 -0
- package/dist/types/src/conversation/conversation.d.ts +61 -6
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/conversation/index.d.ts +0 -1
- package/dist/types/src/conversation/index.d.ts.map +1 -1
- package/dist/types/src/conversation/types.d.ts +44 -13
- package/dist/types/src/conversation/types.d.ts.map +1 -1
- package/dist/types/src/cypher/combinators.d.ts.map +1 -1
- package/dist/types/src/cypher/parjs.d.ts +1 -1
- package/dist/types/src/cypher/parjs.d.ts.map +1 -1
- package/dist/types/src/cypher/schema.d.ts.map +1 -1
- package/dist/types/src/testing/artifacts.test.d.ts +1 -0
- package/dist/types/src/testing/artifacts.test.d.ts.map +1 -0
- package/dist/types/src/testing/data.d.ts +2 -0
- package/dist/types/src/testing/data.d.ts.map +1 -0
- package/dist/types/src/testing/defs.d.ts +5 -0
- package/dist/types/src/testing/defs.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/logger.d.ts +1 -1
- package/dist/types/src/testing/logger.d.ts.map +1 -1
- package/dist/types/src/testing/query-promts.d.ts +1 -1
- package/dist/types/src/testing/query-promts.d.ts.map +1 -1
- package/dist/types/src/testing/test-data.d.ts +1 -1
- package/dist/types/src/testing/test-data.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +4 -4
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -18
- package/src/ai-service/client.test.ts +69 -54
- package/src/ai-service/client.ts +29 -184
- package/src/ai-service/index.ts +4 -1
- package/src/ai-service/parser.ts +338 -0
- package/src/ai-service/stream.test.ts +101 -0
- package/src/ai-service/stream.ts +116 -0
- package/src/ai-service/testing.ts +93 -0
- package/src/ai-service/transform.ts +121 -0
- package/src/ai-service/types.ts +107 -0
- package/src/ai-service/util/index.ts +5 -0
- package/src/ai-service/{sse.ts → util/sse.ts} +5 -0
- package/src/bin/chat.ts +10 -11
- package/src/conversation/conversation.test.ts +16 -17
- package/src/conversation/conversation.ts +126 -20
- package/src/conversation/index.ts +0 -1
- package/src/conversation/types.ts +55 -16
- package/src/cypher/combinators.test.ts +4 -4
- package/src/cypher/combinators.ts +0 -1
- package/src/cypher/execution-plan.test.ts +5 -3
- package/src/cypher/parjs-shims.d.ts +4 -0
- package/src/cypher/parjs.ts +2 -2
- package/src/cypher/query-executor.test.ts +1 -1
- package/src/cypher/schema.ts +6 -2
- package/src/index.ts +1 -1
- package/src/testing/artifacts.test.ts +182 -0
- package/src/testing/data.ts +13 -0
- package/src/testing/defs.ts +8 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/logger.ts +3 -2
- package/src/testing/query-promts.ts +3 -3
- package/src/testing/query.test.ts +11 -10
- package/src/testing/test-data.ts +8 -2
- package/vendor/parjs-browser.mjs +4 -0
- package/vendor/parjs-default.mjs +8 -0
- package/dist/lib/browser/chunk-AKP45LQH.mjs +0 -1394
- package/dist/lib/browser/chunk-AKP45LQH.mjs.map +0 -7
- package/dist/lib/node/chunk-G6CGWEMO.cjs +0 -1418
- package/dist/lib/node/chunk-G6CGWEMO.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-WUPUPEFB.mjs +0 -1396
- package/dist/lib/node-esm/chunk-WUPUPEFB.mjs.map +0 -7
- package/dist/types/src/ai-service/schema.d.ts +0 -296
- package/dist/types/src/ai-service/schema.d.ts.map +0 -1
- package/dist/types/src/ai-service/sse.d.ts.map +0 -1
- package/dist/types/src/sanity.test.d.ts +0 -2
- package/dist/types/src/sanity.test.d.ts.map +0 -1
- package/src/ai-service/schema.ts +0 -260
- package/src/sanity.test.ts +0 -9
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Tool, ToolResult, Message } from '@dxos/artifact';
|
|
6
|
+
import { createStatic, ObjectId } from '@dxos/echo-schema';
|
|
5
7
|
import { invariant } from '@dxos/invariant';
|
|
6
8
|
import { type SpaceId } from '@dxos/keys';
|
|
7
9
|
import { log } from '@dxos/log';
|
|
8
10
|
|
|
9
|
-
import { type
|
|
10
|
-
import { ObjectId, type LLMTool } from '../ai-service';
|
|
11
|
-
import { type AIServiceClient, type Message, type ResultStreamEvent, type LLMModel } from '../ai-service';
|
|
11
|
+
import { type AIServiceClient, type GenerationStreamEvent, type LLMModel } from '../ai-service';
|
|
12
12
|
|
|
13
13
|
export type CreateLLMConversationParams = {
|
|
14
14
|
model: LLMModel;
|
|
15
15
|
|
|
16
|
+
spaceId?: SpaceId;
|
|
17
|
+
threadId?: ObjectId;
|
|
18
|
+
|
|
16
19
|
/**
|
|
17
20
|
* System prompt that specifies instructions for the LLM.
|
|
18
21
|
*/
|
|
19
22
|
system?: string;
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
tools: LLMTool[];
|
|
25
|
-
|
|
24
|
+
tools: Tool[];
|
|
25
|
+
history?: Message[];
|
|
26
26
|
client: AIServiceClient;
|
|
27
27
|
|
|
28
28
|
logger?: (event: ConversationEvent) => void;
|
|
@@ -33,49 +33,59 @@ export type ConversationEvent =
|
|
|
33
33
|
type: 'message';
|
|
34
34
|
message: Message;
|
|
35
35
|
}
|
|
36
|
-
|
|
|
36
|
+
| GenerationStreamEvent;
|
|
37
37
|
|
|
38
|
+
// TODO(burdon): Replace with processor from plugin-automation?
|
|
38
39
|
export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
39
40
|
let conversationResult: any = null;
|
|
41
|
+
const history = params.history ?? [];
|
|
40
42
|
|
|
41
43
|
const generate = async () => {
|
|
42
44
|
log('llm generate', { tools: params.tools });
|
|
43
45
|
const beginTs = Date.now();
|
|
44
|
-
const
|
|
46
|
+
const stream = await params.client.generate({
|
|
45
47
|
model: params.model,
|
|
46
48
|
spaceId: params.spaceId,
|
|
47
49
|
threadId: params.threadId,
|
|
50
|
+
history,
|
|
48
51
|
systemPrompt: params.system,
|
|
49
52
|
tools: params.tools as any,
|
|
50
53
|
});
|
|
51
54
|
|
|
52
|
-
for await (const event of
|
|
55
|
+
for await (const event of stream) {
|
|
53
56
|
params.logger?.(event);
|
|
54
57
|
}
|
|
55
|
-
|
|
58
|
+
|
|
59
|
+
// TODO(burdon): !!!
|
|
60
|
+
await stream.complete();
|
|
61
|
+
const messages: Message[] = [];
|
|
56
62
|
const message = messages.at(-1);
|
|
63
|
+
invariant(message);
|
|
57
64
|
|
|
58
65
|
log('llm result', { time: Date.now() - beginTs, message });
|
|
59
66
|
invariant(message);
|
|
60
67
|
params.logger?.({ type: 'message', message });
|
|
61
|
-
|
|
62
|
-
messages.map(
|
|
68
|
+
history.push(
|
|
69
|
+
...messages.map(
|
|
70
|
+
(message): Message => ({ ...message, content: message.content.filter((block) => block.type !== 'image') }),
|
|
71
|
+
),
|
|
63
72
|
);
|
|
64
73
|
|
|
65
74
|
const isToolUse = message.content.at(-1)?.type === 'tool_use';
|
|
66
75
|
if (isToolUse) {
|
|
67
|
-
const toolCalls = message.content.filter((
|
|
76
|
+
const toolCalls = message.content.filter((block) => block.type === 'tool_use');
|
|
68
77
|
invariant(toolCalls.length === 1);
|
|
69
78
|
const toolCall = toolCalls[0];
|
|
70
|
-
const tool = params.tools.find((
|
|
79
|
+
const tool = params.tools.find((tool) => tool.name === toolCall.name);
|
|
71
80
|
if (!tool) {
|
|
72
81
|
throw new Error(`Tool not found: ${toolCall.name}`);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
invariant(tool.execute);
|
|
85
|
+
const toolResult = await tool.execute(toolCall.input);
|
|
76
86
|
switch (toolResult.kind) {
|
|
77
87
|
case 'error': {
|
|
78
|
-
log('tool error', { message: toolResult.message });
|
|
88
|
+
log.warn('tool error', { message: toolResult.message });
|
|
79
89
|
const resultMessage: Message = {
|
|
80
90
|
id: ObjectId.random(),
|
|
81
91
|
spaceId: params.spaceId,
|
|
@@ -91,10 +101,11 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
91
101
|
],
|
|
92
102
|
};
|
|
93
103
|
params.logger?.({ type: 'message', message: resultMessage });
|
|
94
|
-
|
|
104
|
+
history.push(resultMessage);
|
|
95
105
|
|
|
96
106
|
return true;
|
|
97
107
|
}
|
|
108
|
+
|
|
98
109
|
case 'success': {
|
|
99
110
|
log('tool success', { result: toolResult.result });
|
|
100
111
|
const resultMessage: Message = {
|
|
@@ -111,10 +122,11 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
111
122
|
],
|
|
112
123
|
};
|
|
113
124
|
params.logger?.({ type: 'message', message: resultMessage });
|
|
114
|
-
|
|
125
|
+
history.push(resultMessage);
|
|
115
126
|
|
|
116
127
|
return true;
|
|
117
128
|
}
|
|
129
|
+
|
|
118
130
|
case 'break': {
|
|
119
131
|
log('tool break', { result: toolResult.result });
|
|
120
132
|
conversationResult = toolResult.result;
|
|
@@ -122,6 +134,7 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
}
|
|
137
|
+
|
|
125
138
|
return false;
|
|
126
139
|
};
|
|
127
140
|
|
|
@@ -130,5 +143,98 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
130
143
|
|
|
131
144
|
return {
|
|
132
145
|
result: conversationResult,
|
|
146
|
+
history,
|
|
133
147
|
};
|
|
134
148
|
};
|
|
149
|
+
|
|
150
|
+
export const isToolUse = (message: Message) => {
|
|
151
|
+
const block = message.content.at(-1);
|
|
152
|
+
invariant(block);
|
|
153
|
+
return block.type === 'tool_use';
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type RunToolsOptions = {
|
|
157
|
+
message: Message;
|
|
158
|
+
tools: Tool[];
|
|
159
|
+
extensions?: ToolContextExtensions;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type RunToolsResult =
|
|
163
|
+
| {
|
|
164
|
+
type: 'continue';
|
|
165
|
+
message: Message;
|
|
166
|
+
}
|
|
167
|
+
| {
|
|
168
|
+
type: 'break';
|
|
169
|
+
result: unknown;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const runTools = async ({ message, tools, extensions }: RunToolsOptions): Promise<RunToolsResult> => {
|
|
173
|
+
const toolCalls = message.content.filter((block) => block.type === 'tool_use');
|
|
174
|
+
invariant(toolCalls.length === 1);
|
|
175
|
+
const toolCall = toolCalls[0];
|
|
176
|
+
const tool = tools.find((tool) => tool.name === toolCall.name);
|
|
177
|
+
if (!tool) {
|
|
178
|
+
throw new Error(`Tool not found: ${toolCall.name}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let toolResult: ToolResult;
|
|
182
|
+
try {
|
|
183
|
+
invariant(tool.execute);
|
|
184
|
+
toolResult = await tool.execute(toolCall.input, { extensions });
|
|
185
|
+
} catch (error: any) {
|
|
186
|
+
log('tool error', { error });
|
|
187
|
+
toolResult = ToolResult.Error(error.message);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
switch (toolResult.kind) {
|
|
191
|
+
case 'error': {
|
|
192
|
+
log('tool error', { message: toolResult.message });
|
|
193
|
+
const resultMessage = createStatic(Message, {
|
|
194
|
+
role: 'user',
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: 'tool_result',
|
|
198
|
+
toolUseId: toolCall.id,
|
|
199
|
+
content: toolResult.message,
|
|
200
|
+
isError: true,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// TODO(burdon): Retry count?
|
|
206
|
+
return {
|
|
207
|
+
type: 'continue',
|
|
208
|
+
message: resultMessage,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case 'success': {
|
|
213
|
+
log('tool success', { result: toolResult.result });
|
|
214
|
+
const resultMessage = createStatic(Message, {
|
|
215
|
+
role: 'user',
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: 'tool_result',
|
|
219
|
+
toolUseId: toolCall.id,
|
|
220
|
+
content:
|
|
221
|
+
typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result) ?? '',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
type: 'continue',
|
|
228
|
+
message: resultMessage,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
case 'break': {
|
|
233
|
+
log('tool break', { result: toolResult.result });
|
|
234
|
+
return {
|
|
235
|
+
type: 'break',
|
|
236
|
+
result: toolResult.result,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema as S } from '@effect/schema';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { JsonSchemaType } from '@dxos/echo-schema';
|
|
7
|
+
import { type Message, type MessageContentBlock } from '@dxos/artifact';
|
|
8
|
+
import { toJsonSchema, JsonSchemaType, ObjectId } from '@dxos/echo-schema';
|
|
9
9
|
import type { SpaceId } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
|
|
12
|
-
import { ObjectId, type Message } from '../ai-service';
|
|
13
|
-
|
|
14
12
|
export const createUserMessage = (spaceId: SpaceId, threadId: ObjectId, text: string): Message => ({
|
|
15
13
|
id: ObjectId.random(),
|
|
16
14
|
spaceId,
|
|
@@ -19,46 +17,80 @@ export const createUserMessage = (spaceId: SpaceId, threadId: ObjectId, text: st
|
|
|
19
17
|
content: [{ type: 'text', text }],
|
|
20
18
|
});
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Use {@link LLMTool} instead.
|
|
22
|
+
*/
|
|
23
|
+
export const ToolDefinition = S.Struct({
|
|
23
24
|
name: S.String,
|
|
24
25
|
description: S.String,
|
|
25
26
|
parameters: JsonSchemaType,
|
|
26
27
|
execute: S.Any,
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use {@link LLMTool} instead.
|
|
32
|
+
*/
|
|
33
|
+
export type ToolDefinition = {
|
|
30
34
|
name: string;
|
|
31
35
|
description: string;
|
|
32
36
|
parameters: JsonSchemaType;
|
|
33
|
-
execute: (params: unknown, context: ToolExecutionContext) => Promise<
|
|
37
|
+
execute: (params: unknown, context: ToolExecutionContext) => Promise<ToolResult>;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
declare global {
|
|
41
|
+
/**
|
|
42
|
+
* Extensions to the tool execution context.
|
|
43
|
+
*
|
|
44
|
+
* Different modules can extend this definition to add their own properties.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* declare global {
|
|
49
|
+
* interface ToolContextExtensions {
|
|
50
|
+
* chess: {
|
|
51
|
+
* board: string;
|
|
52
|
+
* };
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
|
|
58
|
+
*/
|
|
59
|
+
interface ToolContextExtensions {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ToolExecutionContext = {
|
|
63
|
+
extensions: ToolContextExtensions;
|
|
64
|
+
};
|
|
37
65
|
|
|
38
|
-
export type
|
|
39
|
-
| { kind: 'success'; result: unknown }
|
|
66
|
+
export type ToolResult =
|
|
67
|
+
| { kind: 'success'; result: unknown; extractContentBlocks?: MessageContentBlock[] }
|
|
40
68
|
| { kind: 'error'; message: string }
|
|
41
69
|
| { kind: 'break'; result: unknown };
|
|
42
70
|
|
|
43
|
-
export const
|
|
71
|
+
export const ToolResult = Object.freeze({
|
|
44
72
|
/**
|
|
45
73
|
* The tool execution was successful.
|
|
46
74
|
* Gives the result back to the LLM.
|
|
47
75
|
*/
|
|
48
|
-
Success: (result: unknown):
|
|
76
|
+
Success: (result: unknown, extractContentBlocks?: MessageContentBlock[]): ToolResult => ({
|
|
77
|
+
kind: 'success',
|
|
78
|
+
result,
|
|
79
|
+
extractContentBlocks,
|
|
80
|
+
}),
|
|
49
81
|
|
|
50
|
-
Error: (message: string):
|
|
82
|
+
Error: (message: string): ToolResult => ({ kind: 'error', message }),
|
|
51
83
|
/**
|
|
52
84
|
* Stop the conversation and return the result.
|
|
53
85
|
*/
|
|
54
|
-
Break: (result: unknown):
|
|
86
|
+
Break: (result: unknown): ToolResult => ({ kind: 'break', result }),
|
|
55
87
|
});
|
|
56
88
|
|
|
57
89
|
export type DefineToolParams<Params extends S.Schema.AnyNoContext> = {
|
|
58
90
|
name: string;
|
|
59
91
|
description: string;
|
|
60
92
|
schema: Params;
|
|
61
|
-
execute: (params: S.Schema.Type<Params>, context: ToolExecutionContext) => Promise<
|
|
93
|
+
execute: (params: S.Schema.Type<Params>, context: ToolExecutionContext) => Promise<ToolResult>;
|
|
62
94
|
};
|
|
63
95
|
|
|
64
96
|
export const defineTool = <Params extends S.Schema.AnyNoContext>({
|
|
@@ -66,7 +98,7 @@ export const defineTool = <Params extends S.Schema.AnyNoContext>({
|
|
|
66
98
|
description,
|
|
67
99
|
schema,
|
|
68
100
|
execute,
|
|
69
|
-
}: DefineToolParams<Params>):
|
|
101
|
+
}: DefineToolParams<Params>): ToolDefinition => {
|
|
70
102
|
return {
|
|
71
103
|
name,
|
|
72
104
|
description,
|
|
@@ -78,9 +110,16 @@ export const defineTool = <Params extends S.Schema.AnyNoContext>({
|
|
|
78
110
|
};
|
|
79
111
|
};
|
|
80
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Adapts schems to be able to pass to AI providers.
|
|
115
|
+
*/
|
|
81
116
|
const toFunctionParameterSchema = (schema: S.Schema.All) => {
|
|
82
117
|
const jsonSchema = toJsonSchema(schema);
|
|
83
118
|
log('tool schema', { jsonSchema });
|
|
119
|
+
delete jsonSchema.anyOf;
|
|
120
|
+
delete jsonSchema.$id;
|
|
121
|
+
jsonSchema.type = 'object';
|
|
122
|
+
jsonSchema.properties ??= {};
|
|
84
123
|
for (const key in jsonSchema.properties) {
|
|
85
124
|
if (typeof jsonSchema.properties![key].description !== 'string') {
|
|
86
125
|
throw new Error(`Property missing description: ${key}`);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
|
7
7
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
|
-
import type
|
|
9
|
+
import { type Parjser } from 'parjs';
|
|
10
10
|
import { describe, test } from 'vitest';
|
|
11
11
|
|
|
12
12
|
import { log } from '@dxos/log';
|
|
@@ -58,13 +58,13 @@ describe('Cypher Combinators', () => {
|
|
|
58
58
|
const parser = or(node)(relation);
|
|
59
59
|
const input = '[a:b]';
|
|
60
60
|
const result = parser.parse(input);
|
|
61
|
-
log
|
|
61
|
+
log('result', { result });
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('properties', ({ expect }) => {
|
|
65
65
|
const input = "{name: 'John'}";
|
|
66
66
|
const result = properties.parse(input);
|
|
67
|
-
log
|
|
67
|
+
log('result', { result });
|
|
68
68
|
expect(result).toMatchObject({ isOk: true });
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -92,7 +92,7 @@ describe('Cypher Combinators', () => {
|
|
|
92
92
|
const cases = ['RETURN n, m'];
|
|
93
93
|
for (const input of cases) {
|
|
94
94
|
const result = returnClause.parse(input);
|
|
95
|
-
log
|
|
95
|
+
log('result', { result });
|
|
96
96
|
expect(result).toMatchObject({ isOk: true });
|
|
97
97
|
}
|
|
98
98
|
});
|
|
@@ -9,10 +9,12 @@ import { log } from '@dxos/log';
|
|
|
9
9
|
import { createExecutionPlan } from './execution-plan';
|
|
10
10
|
import { parseCypherQuery } from './parser';
|
|
11
11
|
|
|
12
|
+
// log.config({ filter: 'debug' });
|
|
13
|
+
|
|
12
14
|
describe('Execution Plan', () => {
|
|
13
15
|
test('trivial', ({ expect }) => {
|
|
14
16
|
const plan = createExecutionPlan(parseCypherQuery('MATCH (n:Person) RETURN n'));
|
|
15
|
-
log
|
|
17
|
+
log('', { plan });
|
|
16
18
|
expect(plan.steps.map((step) => step.type)).toEqual(['NodeScan', 'ProduceResults']);
|
|
17
19
|
});
|
|
18
20
|
|
|
@@ -22,9 +24,9 @@ describe('Execution Plan', () => {
|
|
|
22
24
|
"MATCH (org:Org {name: 'DXOS'})-[:ORG_EMPLOYEES]->(c:Contact)<-[:TASK_ASSIGNEE]-(:Task)-[:TASK_PROJECT]->(p:Project {name: 'Composer'}) RETURN c.name",
|
|
23
25
|
),
|
|
24
26
|
);
|
|
25
|
-
log
|
|
27
|
+
log('', { plan });
|
|
26
28
|
for (const step of plan.steps) {
|
|
27
|
-
log
|
|
29
|
+
log('step', { step });
|
|
28
30
|
}
|
|
29
31
|
expect(plan.steps.map((step) => step.type)).toEqual([
|
|
30
32
|
'NodeScan',
|
package/src/cypher/parjs.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
// Workaround for https://github.com/vitest-dev/vitest/issues/7174.
|
|
6
|
-
import { parjsCombinators, parjs } from '#parjs';
|
|
7
|
-
|
|
8
6
|
import type { Parjser } from 'parjs';
|
|
9
7
|
|
|
8
|
+
import { parjsCombinators, parjs } from '#parjs';
|
|
9
|
+
|
|
10
10
|
export { type Parjser, parjsCombinators, parjs };
|
|
@@ -9,7 +9,7 @@ import { log } from '@dxos/log';
|
|
|
9
9
|
import { executeQuery } from './query-executor';
|
|
10
10
|
import { createTestData } from '../testing';
|
|
11
11
|
|
|
12
|
-
test('run', async ({ expect }) => {
|
|
12
|
+
test.skip('run', async ({ expect }) => {
|
|
13
13
|
const dataSource = createTestData();
|
|
14
14
|
// for (const node of dataSource.nodes) {
|
|
15
15
|
// log.info('node', { id: node.id, label: node.label, name: node.properties.name });
|
package/src/cypher/schema.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { raise } from '@dxos/debug';
|
|
6
6
|
import { JSON_SCHEMA_ECHO_REF_ID, type JsonSchemaType } from '@dxos/echo-schema';
|
|
7
|
-
import { DXN } from '@dxos/keys';
|
|
8
7
|
|
|
9
8
|
import { trim } from '../util';
|
|
10
9
|
|
|
@@ -79,4 +78,9 @@ export const formatInferredRelationshipLabel = (typename: string, property: stri
|
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
export const isReference = (schema: JsonSchemaType) =>
|
|
82
|
-
schema.$id === JSON_SCHEMA_ECHO_REF_ID ||
|
|
81
|
+
schema.$id === JSON_SCHEMA_ECHO_REF_ID ||
|
|
82
|
+
(schema.type === 'array' &&
|
|
83
|
+
!Array.isArray(schema.items) &&
|
|
84
|
+
typeof schema.items === 'object' &&
|
|
85
|
+
'$id' in schema.items &&
|
|
86
|
+
schema.items.$id === JSON_SCHEMA_ECHO_REF_ID);
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
import { Schema as S } from '@effect/schema';
|
|
7
|
+
import { describe, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { ObjectId, toJsonSchema } from '@dxos/echo-schema';
|
|
10
|
+
import { invariant } from '@dxos/invariant';
|
|
11
|
+
import { SpaceId } from '@dxos/keys';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
|
|
14
|
+
import { AIServiceClientImpl, Tool } from '../ai-service';
|
|
15
|
+
import { defineTool, ToolResult } from '../conversation';
|
|
16
|
+
|
|
17
|
+
// TODO(dmaretskyi): Effect schema.
|
|
18
|
+
const ArtifactDef = S.Struct({
|
|
19
|
+
typename: S.String,
|
|
20
|
+
description: S.String,
|
|
21
|
+
schema: S.Any,
|
|
22
|
+
actions: S.Array(Tool),
|
|
23
|
+
}).pipe(S.mutable);
|
|
24
|
+
|
|
25
|
+
type ArtifactDef = S.Schema.Type<typeof ArtifactDef>;
|
|
26
|
+
|
|
27
|
+
const ListItem = S.Struct({
|
|
28
|
+
name: S.String,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const ListSchema = S.Struct({
|
|
32
|
+
id: ObjectId,
|
|
33
|
+
title: S.String,
|
|
34
|
+
items: S.Array(ListItem).pipe(S.mutable),
|
|
35
|
+
}).pipe(S.mutable);
|
|
36
|
+
|
|
37
|
+
type ListSchema = S.Schema.Type<typeof ListSchema>;
|
|
38
|
+
|
|
39
|
+
const list: ArtifactDef = {
|
|
40
|
+
typename: 'example.com/type/List',
|
|
41
|
+
description: 'Ordered set of text items.',
|
|
42
|
+
schema: ListSchema,
|
|
43
|
+
actions: [
|
|
44
|
+
defineTool({
|
|
45
|
+
name: 'list.query',
|
|
46
|
+
description: 'Query all lists',
|
|
47
|
+
schema: S.Struct({}),
|
|
48
|
+
execute: async ({}, context) => {
|
|
49
|
+
return ToolResult.Success(LISTS[id]);
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
defineTool({
|
|
53
|
+
name: 'list.inspect',
|
|
54
|
+
description: 'Get contents of the list',
|
|
55
|
+
schema: S.Struct({
|
|
56
|
+
id: ObjectId.annotations({ description: 'The list to inspect' }),
|
|
57
|
+
}),
|
|
58
|
+
execute: async ({ id }, context) => {
|
|
59
|
+
return ToolResult.Success(LISTS[id]);
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
defineTool({
|
|
63
|
+
name: 'list.add',
|
|
64
|
+
description: 'Add one or more items to an existing list',
|
|
65
|
+
schema: S.Struct({
|
|
66
|
+
id: ObjectId.annotations({ description: 'The list to add items to' }),
|
|
67
|
+
items: S.Array(ListItem).annotations({ description: 'The items to add to the list' }),
|
|
68
|
+
}),
|
|
69
|
+
execute: async ({ id, items }, context) => {
|
|
70
|
+
LISTS[id].items.push(...items);
|
|
71
|
+
return ToolResult.Success(LISTS[id]);
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const LISTS: Record<ObjectId, ListSchema> = {};
|
|
78
|
+
|
|
79
|
+
const artifacts = [list];
|
|
80
|
+
|
|
81
|
+
const getArtifactDefinitions = defineTool({
|
|
82
|
+
name: 'getArtifactDefinitions',
|
|
83
|
+
description: 'Queries the definitions and metadata of artifacts defined in the system',
|
|
84
|
+
schema: S.Any,
|
|
85
|
+
execute: async (_, context) => {
|
|
86
|
+
return ToolResult.Success(
|
|
87
|
+
artifacts.map((artifact) => ({
|
|
88
|
+
typename: artifact.typename,
|
|
89
|
+
description: artifact.description,
|
|
90
|
+
schema: toJsonSchema(artifact.schema),
|
|
91
|
+
actions: artifact.actions.map((action) => ({
|
|
92
|
+
name: action.name,
|
|
93
|
+
description: action.description,
|
|
94
|
+
})),
|
|
95
|
+
})),
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const ARTIFACT_INSTRUCTIONS = `
|
|
101
|
+
You have ability to interact with artifacts.
|
|
102
|
+
Artifacts are persistent structured objects that have actions associated with them.
|
|
103
|
+
|
|
104
|
+
Before replying to the user you should first query the artifact definitions and metadata.
|
|
105
|
+
If there's
|
|
106
|
+
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
describe('Artifacts', () => {
|
|
110
|
+
test('shopping list', async () => {
|
|
111
|
+
const client = new AIServiceClientImpl({
|
|
112
|
+
endpoint: ENDPOINT,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const custodian: Tool = {
|
|
116
|
+
name: 'custodian',
|
|
117
|
+
description: 'Custodian can tell you the password if you say the magic word',
|
|
118
|
+
parameters: toJsonSchema(
|
|
119
|
+
S.Struct({
|
|
120
|
+
magicWord: S.String.annotations({ description: 'The magic word. Should be exactly "pretty please"' }),
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const spaceId = SpaceId.random();
|
|
126
|
+
const threadId = ObjectId.random();
|
|
127
|
+
|
|
128
|
+
await client.appendMessages([
|
|
129
|
+
{
|
|
130
|
+
id: ObjectId.random(),
|
|
131
|
+
spaceId,
|
|
132
|
+
threadId,
|
|
133
|
+
role: 'user',
|
|
134
|
+
content: [{ type: 'text', text: 'What is the password? Ask the custodian' }],
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const stream = await client.generate({
|
|
139
|
+
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
140
|
+
spaceId,
|
|
141
|
+
threadId,
|
|
142
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
143
|
+
tools: [custodian],
|
|
144
|
+
});
|
|
145
|
+
for await (const event of stream) {
|
|
146
|
+
log.info('event', event);
|
|
147
|
+
}
|
|
148
|
+
const [message] = await stream.complete();
|
|
149
|
+
log.info('full message', {
|
|
150
|
+
message,
|
|
151
|
+
});
|
|
152
|
+
await client.appendMessages([message]);
|
|
153
|
+
|
|
154
|
+
const toolUse = message.content.find(({ type }) => type === 'tool_use')!;
|
|
155
|
+
invariant(toolUse.type === 'tool_use');
|
|
156
|
+
await client.appendMessages([
|
|
157
|
+
{
|
|
158
|
+
id: ObjectId.random(),
|
|
159
|
+
spaceId,
|
|
160
|
+
threadId,
|
|
161
|
+
role: 'user',
|
|
162
|
+
content: [{ type: 'tool_result', toolUseId: toolUse.id, content: 'password="The sky is gray"' }],
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const stream2 = await client.generate({
|
|
167
|
+
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
168
|
+
spaceId,
|
|
169
|
+
threadId,
|
|
170
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
171
|
+
tools: [custodian],
|
|
172
|
+
});
|
|
173
|
+
for await (const event of stream2) {
|
|
174
|
+
log.info('event', event);
|
|
175
|
+
}
|
|
176
|
+
const [message2] = await stream2.complete();
|
|
177
|
+
log.info('full message', {
|
|
178
|
+
message: message2,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
*/
|