@dxos/assistant 0.8.1-main.ba2dec9 → 0.8.1-staging.31c3ee1
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-PND5VR6D.mjs +2315 -0
- package/dist/lib/browser/chunk-PND5VR6D.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +313 -1631
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +20 -6
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/chunk-IBOBRBDF.cjs +2326 -0
- package/dist/lib/node/chunk-IBOBRBDF.cjs.map +7 -0
- package/dist/lib/node/index.cjs +321 -1629
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +22 -11
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/chunk-DCQJOLSR.mjs +2317 -0
- package/dist/lib/node-esm/chunk-DCQJOLSR.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +313 -1631
- 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 +20 -6
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/ai-service/defs.d.ts +8 -2
- package/dist/types/src/ai-service/defs.d.ts.map +1 -1
- package/dist/types/src/ai-service/edge-client.d.ts.map +1 -1
- package/dist/types/src/ai-service/message-collector.d.ts +16 -16
- package/dist/types/src/ai-service/message-collector.d.ts.map +1 -1
- package/dist/types/src/ai-service/ollama-client.d.ts +1 -5
- package/dist/types/src/ai-service/ollama-client.d.ts.map +1 -1
- package/dist/types/src/ai-service/parser.d.ts +8 -8
- package/dist/types/src/ai-service/parser.d.ts.map +1 -1
- package/dist/types/src/ai-service/types.d.ts +90 -156
- package/dist/types/src/ai-service/types.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.d.ts +8 -8
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +2 -0
- package/dist/types/src/session/index.d.ts.map +1 -0
- package/dist/types/src/session/session.d.ts +216 -0
- package/dist/types/src/session/session.d.ts.map +1 -0
- package/dist/types/src/session/session.test.d.ts +2 -0
- package/dist/types/src/session/session.test.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/ollama.d.ts +6 -0
- package/dist/types/src/testing/ollama.d.ts.map +1 -0
- package/package.json +13 -13
- package/src/ai-service/client.test.ts +14 -14
- package/src/ai-service/defs.ts +18 -3
- package/src/ai-service/edge-client.ts +3 -2
- package/src/ai-service/ollama-client.ts +50 -24
- package/src/ai-service/parser.ts +4 -2
- package/src/ai-service/stream.test.ts +28 -4
- package/src/ai-service/types.ts +2 -2
- package/src/bin/chat.ts +3 -3
- package/src/conversation/conversation.test.ts +3 -3
- package/src/conversation/conversation.ts +17 -1
- package/src/cypher/query-executor.test.ts +3 -3
- package/src/cypher/schema.test.ts +1 -1
- package/src/index.ts +1 -0
- package/src/session/index.ts +5 -0
- package/src/session/session.test.ts +257 -0
- package/src/session/session.ts +375 -0
- package/src/testing/artifacts.test.ts +6 -6
- package/src/testing/index.ts +1 -0
- package/src/testing/object-extraction.test.ts +2 -2
- package/src/testing/ollama.ts +15 -0
- package/src/testing/query.test.ts +5 -5
- package/src/testing/test-schema.ts +5 -5
- package/dist/lib/browser/chunk-AXUUNA7O.mjs +0 -565
- package/dist/lib/browser/chunk-AXUUNA7O.mjs.map +0 -7
- package/dist/lib/node/chunk-QPHGQINW.cjs +0 -577
- package/dist/lib/node/chunk-QPHGQINW.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GOVFOBIB.mjs +0 -567
- package/dist/lib/node-esm/chunk-GOVFOBIB.mjs.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/assistant",
|
|
3
|
-
"version": "0.8.1-
|
|
3
|
+
"version": "0.8.1-staging.31c3ee1",
|
|
4
4
|
"description": "Natural language interface to DXOS",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -40,18 +40,18 @@
|
|
|
40
40
|
"parjs": "^1.3.9",
|
|
41
41
|
"parsimmon": "^1.18.1",
|
|
42
42
|
"ulidx": "^2.3.0",
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/debug": "0.8.1-
|
|
47
|
-
"@dxos/echo-
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/log": "0.8.1-
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
43
|
+
"@dxos/artifact": "0.8.1-staging.31c3ee1",
|
|
44
|
+
"@dxos/async": "0.8.1-staging.31c3ee1",
|
|
45
|
+
"@dxos/echo-db": "0.8.1-staging.31c3ee1",
|
|
46
|
+
"@dxos/debug": "0.8.1-staging.31c3ee1",
|
|
47
|
+
"@dxos/echo-schema": "0.8.1-staging.31c3ee1",
|
|
48
|
+
"@dxos/invariant": "0.8.1-staging.31c3ee1",
|
|
49
|
+
"@dxos/context": "0.8.1-staging.31c3ee1",
|
|
50
|
+
"@dxos/keys": "0.8.1-staging.31c3ee1",
|
|
51
|
+
"@dxos/live-object": "0.8.1-staging.31c3ee1",
|
|
52
|
+
"@dxos/log": "0.8.1-staging.31c3ee1",
|
|
53
|
+
"@dxos/node-std": "0.8.1-staging.31c3ee1",
|
|
54
|
+
"@dxos/util": "0.8.1-staging.31c3ee1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/parsimmon": "^1.10.9",
|
|
@@ -11,12 +11,12 @@ import { invariant } from '@dxos/invariant';
|
|
|
11
11
|
import { SpaceId } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { DEFAULT_EDGE_MODEL } from './defs';
|
|
15
15
|
import { AIServiceEdgeClient } from './edge-client';
|
|
16
16
|
import { OllamaClient } from './ollama-client';
|
|
17
17
|
import { MixedStreamParser } from './parser';
|
|
18
18
|
import { ToolTypes } from './types';
|
|
19
|
-
import { AI_SERVICE_ENDPOINT } from '../testing';
|
|
19
|
+
import { createTestOllamaClient, AI_SERVICE_ENDPOINT } from '../testing';
|
|
20
20
|
|
|
21
21
|
// log.config({ filter: 'debug' });
|
|
22
22
|
|
|
@@ -40,7 +40,7 @@ describe.skip('AI Service Client', () => {
|
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
const stream = await client.exec({
|
|
43
|
-
model:
|
|
43
|
+
model: DEFAULT_EDGE_MODEL,
|
|
44
44
|
spaceId,
|
|
45
45
|
threadId,
|
|
46
46
|
systemPrompt: 'You are a poet',
|
|
@@ -85,7 +85,7 @@ describe.skip('AI Service Client', () => {
|
|
|
85
85
|
|
|
86
86
|
{
|
|
87
87
|
const stream1 = await client.exec({
|
|
88
|
-
model:
|
|
88
|
+
model: DEFAULT_EDGE_MODEL,
|
|
89
89
|
spaceId,
|
|
90
90
|
threadId,
|
|
91
91
|
systemPrompt: 'You are a helpful assistant.',
|
|
@@ -119,7 +119,7 @@ describe.skip('AI Service Client', () => {
|
|
|
119
119
|
|
|
120
120
|
{
|
|
121
121
|
const stream2 = await client.exec({
|
|
122
|
-
model:
|
|
122
|
+
model: DEFAULT_EDGE_MODEL,
|
|
123
123
|
spaceId,
|
|
124
124
|
threadId,
|
|
125
125
|
systemPrompt: 'You are a helpful assistant.',
|
|
@@ -157,7 +157,7 @@ describe.skip('AI Service Client', () => {
|
|
|
157
157
|
]);
|
|
158
158
|
|
|
159
159
|
const stream = await client.exec({
|
|
160
|
-
model:
|
|
160
|
+
model: DEFAULT_EDGE_MODEL,
|
|
161
161
|
spaceId,
|
|
162
162
|
threadId,
|
|
163
163
|
tools: [
|
|
@@ -179,14 +179,14 @@ describe.skip('AI Service Client', () => {
|
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
describe('Ollama Client', () => {
|
|
182
|
+
describe.skip('Ollama Client', () => {
|
|
183
183
|
test('basic', async (ctx) => {
|
|
184
184
|
const isRunning = await OllamaClient.isRunning();
|
|
185
185
|
if (!isRunning) {
|
|
186
186
|
ctx.skip();
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
const client =
|
|
189
|
+
const client = createTestOllamaClient();
|
|
190
190
|
const parser = new MixedStreamParser();
|
|
191
191
|
|
|
192
192
|
const messages = await parser.parse(
|
|
@@ -198,7 +198,7 @@ describe('Ollama Client', () => {
|
|
|
198
198
|
}),
|
|
199
199
|
);
|
|
200
200
|
|
|
201
|
-
log
|
|
201
|
+
log('messages', { messages });
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
test('tool calls', async (ctx) => {
|
|
@@ -207,7 +207,7 @@ describe('Ollama Client', () => {
|
|
|
207
207
|
ctx.skip();
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
const client =
|
|
210
|
+
const client = createTestOllamaClient({
|
|
211
211
|
tools: [
|
|
212
212
|
defineTool('test', {
|
|
213
213
|
name: 'encrypt',
|
|
@@ -221,7 +221,7 @@ describe('Ollama Client', () => {
|
|
|
221
221
|
});
|
|
222
222
|
const parser = new MixedStreamParser();
|
|
223
223
|
parser.streamEvent.on((event) => {
|
|
224
|
-
// log
|
|
224
|
+
// log('event', { event });
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
const messages = await parser.parse(
|
|
@@ -233,7 +233,7 @@ describe('Ollama Client', () => {
|
|
|
233
233
|
}),
|
|
234
234
|
);
|
|
235
235
|
|
|
236
|
-
log
|
|
236
|
+
log('messages', { messages });
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
test('text-to-image', async (ctx) => {
|
|
@@ -242,7 +242,7 @@ describe('Ollama Client', () => {
|
|
|
242
242
|
ctx.skip();
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
const client =
|
|
245
|
+
const client = createTestOllamaClient();
|
|
246
246
|
const parser = new MixedStreamParser();
|
|
247
247
|
|
|
248
248
|
const messages = await parser.parse(
|
|
@@ -260,6 +260,6 @@ describe('Ollama Client', () => {
|
|
|
260
260
|
}),
|
|
261
261
|
);
|
|
262
262
|
|
|
263
|
-
log
|
|
263
|
+
log('messages', { messages });
|
|
264
264
|
});
|
|
265
265
|
});
|
package/src/ai-service/defs.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// TODO(burdon): Share with Edge.
|
|
6
6
|
// TODO(burdon): Move to service config retrieved by HTTP?
|
|
7
7
|
|
|
8
|
-
export const
|
|
8
|
+
export const DEFAULT_EDGE_MODEL = '@anthropic/claude-3-5-sonnet-20241022';
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const DEFAULT_EDGE_MODELS = [
|
|
11
11
|
// AI Gateway.
|
|
12
12
|
// https://developers.cloudflare.com/ai-gateway
|
|
13
13
|
'@anthropic/claude-3-5-haiku-20241022',
|
|
@@ -21,4 +21,19 @@ export const DEFAULT_LLM_MODELS = [
|
|
|
21
21
|
'@ollama/llama-3-1-nemotron-70b-instruct',
|
|
22
22
|
'@ollama/llama-3-1-nemotron-mini-4b-instruct',
|
|
23
23
|
'@ollama/llama-3-2-3b',
|
|
24
|
-
];
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
// TODO(burdon): Config.
|
|
27
|
+
export const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_OLLAMA_MODEL = 'llama3.2:1b';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* https://ollama.com/library
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_OLLAMA_MODELS = [
|
|
35
|
+
//
|
|
36
|
+
'llama3.2:1b',
|
|
37
|
+
'llama3:70b',
|
|
38
|
+
'deepseek-r1:latest',
|
|
39
|
+
] as const;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Schema as S } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { Message } from '@dxos/artifact';
|
|
8
|
-
import { invariant } from '@dxos/invariant';
|
|
8
|
+
import { assertArgument, invariant } from '@dxos/invariant';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
|
|
11
11
|
import type { AIServiceClient, GenerationStream } from './service';
|
|
@@ -47,7 +47,8 @@ export class AIServiceEdgeClient implements AIServiceClient {
|
|
|
47
47
|
* Process request and open message stream.
|
|
48
48
|
*/
|
|
49
49
|
async exec(request: GenerateRequest): Promise<GenerationStream> {
|
|
50
|
-
|
|
50
|
+
assertArgument(typeof request.model === 'string', 'model is required');
|
|
51
|
+
log('requesting', { endpoint: this._endpoint });
|
|
51
52
|
const controller = new AbortController();
|
|
52
53
|
const response = await fetch(`${this._endpoint}/generate`, {
|
|
53
54
|
signal: controller.signal,
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema as S } from 'effect';
|
|
6
|
+
|
|
5
7
|
import { type MessageContentBlock, defineTool, type Tool, ToolResult } from '@dxos/artifact';
|
|
6
|
-
import { ObjectId
|
|
8
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
7
9
|
import { invariant } from '@dxos/invariant';
|
|
8
10
|
import { log } from '@dxos/log';
|
|
9
11
|
|
|
12
|
+
import { DEFAULT_OLLAMA_ENDPOINT } from './defs';
|
|
10
13
|
import { MessageCollector, emitMessageAsEvents } from './message-collector';
|
|
11
14
|
import { type AIServiceClient, type GenerationStream } from './service';
|
|
12
15
|
import { GenerationStreamImpl } from './stream';
|
|
13
16
|
import { ToolTypes, type GenerateRequest, type GenerationStreamEvent } from './types';
|
|
14
17
|
import { isToolUse, runTools } from '../conversation';
|
|
15
18
|
|
|
16
|
-
// TODO(burdon): Config.
|
|
17
|
-
const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
|
|
18
|
-
const DEFAULT_OLLAMA_MODEL = 'llama3.2:1b';
|
|
19
|
-
|
|
20
19
|
export type OllamaClientParams = {
|
|
21
20
|
endpoint?: string;
|
|
22
21
|
|
|
@@ -42,23 +41,13 @@ export type OllamaClientParams = {
|
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
export class OllamaClient implements AIServiceClient {
|
|
45
|
-
/**
|
|
46
|
-
* Create a test client with small local model and no temperature for predictable results.
|
|
47
|
-
*/
|
|
48
|
-
static createClient(options?: Pick<OllamaClientParams, 'tools'>) {
|
|
49
|
-
return new OllamaClient({
|
|
50
|
-
tools: options?.tools,
|
|
51
|
-
overrides: { model: DEFAULT_OLLAMA_MODEL, temperature: 0 },
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
44
|
/**
|
|
56
45
|
* Check if Ollama server is running and accessible.
|
|
57
46
|
* @returns Promise that resolves to true if Ollama is running, false otherwise.
|
|
58
47
|
*/
|
|
59
48
|
static async isRunning(): Promise<boolean> {
|
|
60
49
|
try {
|
|
61
|
-
const response = await fetch(`${
|
|
50
|
+
const response = await fetch(`${DEFAULT_OLLAMA_ENDPOINT}/api/version`, {
|
|
62
51
|
method: 'GET',
|
|
63
52
|
headers: {
|
|
64
53
|
'Content-Type': 'application/json',
|
|
@@ -78,8 +67,8 @@ export class OllamaClient implements AIServiceClient {
|
|
|
78
67
|
private readonly _temperatureOverride?: number;
|
|
79
68
|
private readonly _maxToolInvocations: number;
|
|
80
69
|
|
|
81
|
-
constructor({ endpoint, tools, overrides, maxToolInvocations }: OllamaClientParams) {
|
|
82
|
-
this._endpoint = endpoint ??
|
|
70
|
+
constructor({ endpoint, tools, overrides, maxToolInvocations }: OllamaClientParams = {}) {
|
|
71
|
+
this._endpoint = endpoint ?? DEFAULT_OLLAMA_ENDPOINT;
|
|
83
72
|
this._tools = tools ?? [];
|
|
84
73
|
this._modelOverride = overrides?.model;
|
|
85
74
|
this._temperatureOverride = overrides?.temperature;
|
|
@@ -104,7 +93,7 @@ export class OllamaClient implements AIServiceClient {
|
|
|
104
93
|
messages: [],
|
|
105
94
|
stream: true,
|
|
106
95
|
options: {
|
|
107
|
-
temperature: this._temperatureOverride
|
|
96
|
+
temperature: this._temperatureOverride,
|
|
108
97
|
},
|
|
109
98
|
};
|
|
110
99
|
|
|
@@ -190,7 +179,7 @@ export class OllamaClient implements AIServiceClient {
|
|
|
190
179
|
}
|
|
191
180
|
}
|
|
192
181
|
|
|
193
|
-
log
|
|
182
|
+
log('generate request', { history: request.history, prompt: request.prompt, ollamaRequest });
|
|
194
183
|
|
|
195
184
|
const response = await fetch(`${this._endpoint}/api/chat`, {
|
|
196
185
|
method: 'POST',
|
|
@@ -201,10 +190,9 @@ export class OllamaClient implements AIServiceClient {
|
|
|
201
190
|
signal,
|
|
202
191
|
});
|
|
203
192
|
|
|
204
|
-
//
|
|
205
|
-
|
|
193
|
+
// TODO(burdon): Test if model is not found.
|
|
206
194
|
if (!response.ok) {
|
|
207
|
-
throw
|
|
195
|
+
throw await parseErrorResponse(response);
|
|
208
196
|
}
|
|
209
197
|
|
|
210
198
|
if (!response.body) {
|
|
@@ -253,7 +241,7 @@ export class OllamaClient implements AIServiceClient {
|
|
|
253
241
|
break;
|
|
254
242
|
}
|
|
255
243
|
|
|
256
|
-
log
|
|
244
|
+
log('ollama data', { data });
|
|
257
245
|
|
|
258
246
|
// Ollama sends chunks of text, we convert them to our event format.
|
|
259
247
|
if (data.message?.content) {
|
|
@@ -471,3 +459,41 @@ class OllamaDecoderStream extends TransformStream<string, OllamaResponseData> {
|
|
|
471
459
|
});
|
|
472
460
|
}
|
|
473
461
|
}
|
|
462
|
+
|
|
463
|
+
class ModelDoesNotSupportToolsError extends S.TaggedError<ModelDoesNotSupportToolsError>()(
|
|
464
|
+
'ModelDoesNotSupportToolsError',
|
|
465
|
+
{
|
|
466
|
+
model: S.String,
|
|
467
|
+
},
|
|
468
|
+
) {}
|
|
469
|
+
|
|
470
|
+
class ModelNotFoundError extends S.TaggedError<ModelNotFoundError>()('ModelNotFoundError', {
|
|
471
|
+
model: S.String,
|
|
472
|
+
}) {}
|
|
473
|
+
|
|
474
|
+
const parseOllamaError = (error: string): Error => {
|
|
475
|
+
{
|
|
476
|
+
const match = error.match(/^([^']+) does not support tools$/);
|
|
477
|
+
if (match) {
|
|
478
|
+
return new ModelDoesNotSupportToolsError({ model: match[1] });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
{
|
|
483
|
+
const match = error.match(/^model "([^"]+)" not found, try pulling it first$/);
|
|
484
|
+
if (match) {
|
|
485
|
+
return new ModelNotFoundError({ model: match[1] });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
throw new Error(`Ollama API error: ${error}`);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const parseErrorResponse = async (response: Response): Promise<Error> => {
|
|
493
|
+
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
494
|
+
const body = await response.json();
|
|
495
|
+
return parseOllamaError(body.error);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
throw new Error(`Ollama API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
499
|
+
};
|
package/src/ai-service/parser.ts
CHANGED
|
@@ -80,9 +80,10 @@ export class MixedStreamParser {
|
|
|
80
80
|
const messagesCollected: Message[] = [];
|
|
81
81
|
|
|
82
82
|
for await (const event of stream) {
|
|
83
|
+
log('streamEvent', { event });
|
|
83
84
|
this.streamEvent.emit(event);
|
|
84
85
|
|
|
85
|
-
// log
|
|
86
|
+
// log('event', { type: event.type, event });
|
|
86
87
|
switch (event.type) {
|
|
87
88
|
//
|
|
88
89
|
// Messages.
|
|
@@ -143,7 +144,7 @@ export class MixedStreamParser {
|
|
|
143
144
|
case 'text_delta': {
|
|
144
145
|
const chunks = transformer.transform(event.delta.text);
|
|
145
146
|
for (const chunk of chunks) {
|
|
146
|
-
|
|
147
|
+
log('text_delta', { chunk });
|
|
147
148
|
|
|
148
149
|
switch (streamBlock?.type) {
|
|
149
150
|
//
|
|
@@ -157,6 +158,7 @@ export class MixedStreamParser {
|
|
|
157
158
|
if (stack.length > 0) {
|
|
158
159
|
const top = stack.pop();
|
|
159
160
|
invariant(top && top.type === 'tag');
|
|
161
|
+
log('pop', { top });
|
|
160
162
|
top.content.push(streamBlock);
|
|
161
163
|
streamBlock = top;
|
|
162
164
|
} else {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe,
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { type Message } from '@dxos/artifact';
|
|
8
8
|
import { log } from '@dxos/log';
|
|
@@ -55,7 +55,7 @@ const TEST_BLOCKS = [
|
|
|
55
55
|
];
|
|
56
56
|
|
|
57
57
|
describe('GenerationStream', () => {
|
|
58
|
-
|
|
58
|
+
test('should generate a stream of blocks', async ({ expect }) => {
|
|
59
59
|
const stream = createGenerationStream(new Response(createTestSSEStream(TEST_BLOCKS)));
|
|
60
60
|
const events: GenerationStreamEvent[] = [];
|
|
61
61
|
for await (const event of stream) {
|
|
@@ -66,7 +66,7 @@ describe('GenerationStream', () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
for (const splitBy of ['word'] as const) {
|
|
69
|
-
|
|
69
|
+
test(`should emit xml blocks (splitBy: ${splitBy})`, async ({ expect }) => {
|
|
70
70
|
const stream = createGenerationStream(new Response(createTestSSEStream(TEST_BLOCKS, { splitBy })));
|
|
71
71
|
const parser = new MixedStreamParser();
|
|
72
72
|
|
|
@@ -78,7 +78,7 @@ describe('GenerationStream', () => {
|
|
|
78
78
|
await parser.parse(stream);
|
|
79
79
|
|
|
80
80
|
for (const block of message?.content ?? []) {
|
|
81
|
-
log
|
|
81
|
+
log('block', { block });
|
|
82
82
|
}
|
|
83
83
|
expect(message?.content.map((block) => block.type)).to.deep.eq([
|
|
84
84
|
//
|
|
@@ -98,4 +98,28 @@ describe('GenerationStream', () => {
|
|
|
98
98
|
]);
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
test.skip('invalid xml', async ({ expect }) => {
|
|
103
|
+
const BLOCKS = [
|
|
104
|
+
//
|
|
105
|
+
'<cot> thoughts <tool-list> more thoughts </cot> <tool-list/>',
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const stream = createGenerationStream(new Response(createTestSSEStream(BLOCKS, { splitBy: 'word' })));
|
|
109
|
+
const parser = new MixedStreamParser();
|
|
110
|
+
|
|
111
|
+
const result = await parser.parse(stream);
|
|
112
|
+
expect(result[0].content).to.deep.eq([
|
|
113
|
+
{
|
|
114
|
+
type: 'text',
|
|
115
|
+
disposition: 'cot',
|
|
116
|
+
text: 'thoughts more thoughts',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'json',
|
|
120
|
+
disposition: 'tool_list',
|
|
121
|
+
json: '{}',
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
101
125
|
});
|
package/src/ai-service/types.ts
CHANGED
|
@@ -7,11 +7,11 @@ import { Schema as S } from 'effect';
|
|
|
7
7
|
import { Tool, Message, type MessageContentBlock, SpaceIdSchema } from '@dxos/artifact';
|
|
8
8
|
import { ObjectId } from '@dxos/echo-schema';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { DEFAULT_EDGE_MODELS, DEFAULT_OLLAMA_MODELS } from './defs';
|
|
11
11
|
|
|
12
12
|
export const createArtifactElement = (id: ObjectId) => `<artifact id=${id} />`;
|
|
13
13
|
|
|
14
|
-
export const LLMModel = S.Literal(...
|
|
14
|
+
export const LLMModel = S.Literal(...DEFAULT_EDGE_MODELS, ...DEFAULT_OLLAMA_MODELS);
|
|
15
15
|
|
|
16
16
|
export type LLMModel = S.Schema.Type<typeof LLMModel>;
|
|
17
17
|
|
package/src/bin/chat.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { ObjectId } from '@dxos/echo-schema';
|
|
|
10
10
|
import { SpaceId } from '@dxos/keys';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
|
|
13
|
-
import { AIServiceEdgeClient, ToolTypes,
|
|
13
|
+
import { AIServiceEdgeClient, ToolTypes, DEFAULT_EDGE_MODEL } from '../ai-service';
|
|
14
14
|
import { runLLM } from '../conversation';
|
|
15
15
|
import {
|
|
16
16
|
AI_SERVICE_ENDPOINT,
|
|
@@ -49,7 +49,7 @@ while (true) {
|
|
|
49
49
|
await client.appendMessages([createUserMessage(spaceId, threadId, prompt.message)]);
|
|
50
50
|
|
|
51
51
|
await runLLM({
|
|
52
|
-
model:
|
|
52
|
+
model: DEFAULT_EDGE_MODEL,
|
|
53
53
|
spaceId,
|
|
54
54
|
threadId,
|
|
55
55
|
system: createSystemPrompt(schemaTypes),
|
|
@@ -69,7 +69,7 @@ while (true) {
|
|
|
69
69
|
onImage: (img) => {
|
|
70
70
|
const path = `/tmp/image-${img.id}.jpeg`;
|
|
71
71
|
writeFileSync(path, Buffer.from(img.source.data, 'base64'));
|
|
72
|
-
log
|
|
72
|
+
log('Saved image', { path });
|
|
73
73
|
// Print image in iTerm using ANSI escape sequence
|
|
74
74
|
const imageData = img.source.data;
|
|
75
75
|
process.stdout.write('\x1b]1337;File=inline=1:' + imageData + '\x07');
|
|
@@ -11,7 +11,7 @@ import { SpaceId } from '@dxos/keys';
|
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
|
|
13
13
|
import { runLLM, type ConversationEvent } from './conversation';
|
|
14
|
-
import { AIServiceEdgeClient,
|
|
14
|
+
import { AIServiceEdgeClient, DEFAULT_EDGE_MODEL } from '../ai-service';
|
|
15
15
|
import { AI_SERVICE_ENDPOINT } from '../testing';
|
|
16
16
|
|
|
17
17
|
// TODO(burdon): Local live LLM test.
|
|
@@ -25,7 +25,7 @@ describe.skip('Conversation tests', () => {
|
|
|
25
25
|
const threadId = ObjectId.random();
|
|
26
26
|
|
|
27
27
|
const result = await runLLM({
|
|
28
|
-
model:
|
|
28
|
+
model: DEFAULT_EDGE_MODEL,
|
|
29
29
|
history: [createUserMessage(spaceId, threadId, 'Hello, how are you?')],
|
|
30
30
|
tools: [],
|
|
31
31
|
client,
|
|
@@ -56,7 +56,7 @@ describe.skip('Conversation tests', () => {
|
|
|
56
56
|
const threadId = ObjectId.random();
|
|
57
57
|
|
|
58
58
|
const result = await runLLM({
|
|
59
|
-
model:
|
|
59
|
+
model: DEFAULT_EDGE_MODEL,
|
|
60
60
|
history: [createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')],
|
|
61
61
|
tools: [custodian],
|
|
62
62
|
client,
|
|
@@ -175,7 +175,23 @@ export const runTools = async ({ message, tools, extensions }: RunToolsOptions):
|
|
|
175
175
|
const toolCall = toolCalls[0];
|
|
176
176
|
const tool = tools.find((tool) => tool.name === toolCall.name);
|
|
177
177
|
if (!tool) {
|
|
178
|
-
|
|
178
|
+
const resultMessage = createStatic(Message, {
|
|
179
|
+
role: 'user',
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: 'tool_result',
|
|
183
|
+
toolUseId: toolCall.id,
|
|
184
|
+
content: `Tool not found: ${toolCall.name}`,
|
|
185
|
+
isError: true,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// TODO(burdon): Retry count?
|
|
191
|
+
return {
|
|
192
|
+
type: 'continue',
|
|
193
|
+
message: resultMessage,
|
|
194
|
+
};
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
let toolResult: ToolResult;
|
|
@@ -12,10 +12,10 @@ import { createTestData } from '../testing';
|
|
|
12
12
|
test.skip('run', async ({ expect }) => {
|
|
13
13
|
const dataSource = createTestData();
|
|
14
14
|
// for (const node of dataSource.nodes) {
|
|
15
|
-
// log
|
|
15
|
+
// log('node', { id: node.id, label: node.label, name: node.properties.name });
|
|
16
16
|
// }
|
|
17
17
|
// for (const relationship of dataSource.relationships) {
|
|
18
|
-
// log
|
|
18
|
+
// log('relationship', {
|
|
19
19
|
// source: relationship.source.id,
|
|
20
20
|
// target: relationship.target.id,
|
|
21
21
|
// label: relationship.label,
|
|
@@ -43,5 +43,5 @@ test.skip('run', async ({ expect }) => {
|
|
|
43
43
|
},
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
|
-
log
|
|
46
|
+
log('result', { result });
|
|
47
47
|
});
|
|
@@ -12,6 +12,6 @@ import { Contact, Org, Project, Task } from '../testing';
|
|
|
12
12
|
test('Org-Project-Task-Contact schema', async ({ expect }) => {
|
|
13
13
|
const jsonSchema = [Org, Project, Task, Contact].map((schema) => toJsonSchema(schema));
|
|
14
14
|
const result = formatJsonSchemaForLLM(jsonSchema);
|
|
15
|
-
// log
|
|
15
|
+
// log('result', { result });
|
|
16
16
|
expect(result).toHaveLength(8);
|
|
17
17
|
});
|
package/src/index.ts
CHANGED