@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
7
|
+
|
|
8
|
+
import { type GenerationStreamEvent } from './types';
|
|
9
|
+
import { iterSSEMessages } from './util';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a stream from an SSE response.
|
|
13
|
+
*/
|
|
14
|
+
export const createGenerationStream = (response: Response, controller = new AbortController()) => {
|
|
15
|
+
const iterator = async function* () {
|
|
16
|
+
for await (const sse of iterSSEMessages(response, controller)) {
|
|
17
|
+
if (sse.event === 'completion') {
|
|
18
|
+
try {
|
|
19
|
+
yield JSON.parse(sse.data);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
log.error('could not parse message into JSON:', { data: sse.data, raw: sse.raw });
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
sse.event === 'message_start' ||
|
|
28
|
+
sse.event === 'message_delta' ||
|
|
29
|
+
sse.event === 'message_stop' ||
|
|
30
|
+
sse.event === 'content_block_start' ||
|
|
31
|
+
sse.event === 'content_block_delta' ||
|
|
32
|
+
sse.event === 'content_block_stop'
|
|
33
|
+
) {
|
|
34
|
+
try {
|
|
35
|
+
yield JSON.parse(sse.data);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log.error('could not parse message into JSON:', { data: sse.data, raw: sse.raw });
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (sse.event === 'ping') {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (sse.event === 'error') {
|
|
47
|
+
throw new Error(`Message generation error: ${sse.data}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return new GenerationStream(controller, iterator);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Server-Sent Events (SSE) stream from the AI service.
|
|
57
|
+
* https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
|
58
|
+
*/
|
|
59
|
+
export class GenerationStream implements AsyncIterable<GenerationStreamEvent> {
|
|
60
|
+
/**
|
|
61
|
+
* Trigger event when the stream is done.
|
|
62
|
+
*/
|
|
63
|
+
private readonly _done = new Trigger();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Iterator over the stream.
|
|
67
|
+
*/
|
|
68
|
+
private _iterator?: AsyncIterator<GenerationStreamEvent> = undefined;
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
private readonly _controller: AbortController,
|
|
72
|
+
private readonly _getIterator: () => AsyncIterableIterator<GenerationStreamEvent>,
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns an iterator over the stream.
|
|
77
|
+
*/
|
|
78
|
+
[Symbol.asyncIterator](): AsyncIterator<GenerationStreamEvent> {
|
|
79
|
+
return this._createIterator();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Aborts the stream.
|
|
84
|
+
*/
|
|
85
|
+
abort() {
|
|
86
|
+
this._controller.abort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns the complete message list generated by the AI service.
|
|
91
|
+
*/
|
|
92
|
+
async complete() {
|
|
93
|
+
await this._done.wait();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates an iterator over the stream.
|
|
98
|
+
*/
|
|
99
|
+
private _createIterator(): AsyncIterator<GenerationStreamEvent> {
|
|
100
|
+
const self = this;
|
|
101
|
+
return (this._iterator ??= (() => {
|
|
102
|
+
const generator = async function* (this: GenerationStream) {
|
|
103
|
+
try {
|
|
104
|
+
for await (const event of self._getIterator()) {
|
|
105
|
+
yield event;
|
|
106
|
+
}
|
|
107
|
+
this._done.wake();
|
|
108
|
+
} catch (err: any) {
|
|
109
|
+
this._done.throw(err);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return generator.call(this);
|
|
114
|
+
})());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
6
|
+
|
|
7
|
+
import { type GenerationStreamEvent } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mock server-side events (SSE) stream.
|
|
11
|
+
*/
|
|
12
|
+
export const createTestSSEStream = (
|
|
13
|
+
pars: (string | object)[],
|
|
14
|
+
{ splitBy = 'word' }: { splitBy?: 'word' | 'character' } = {},
|
|
15
|
+
): ReadableStream => {
|
|
16
|
+
const encoder = new TextEncoder();
|
|
17
|
+
|
|
18
|
+
return new ReadableStream({
|
|
19
|
+
start: (controller) => {
|
|
20
|
+
const push = (event: GenerationStreamEvent) => {
|
|
21
|
+
controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
push({
|
|
25
|
+
type: 'message_start',
|
|
26
|
+
message: {
|
|
27
|
+
id: ObjectId.random(),
|
|
28
|
+
role: 'assistant',
|
|
29
|
+
content: [],
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
for (const block of pars) {
|
|
34
|
+
let index = 0;
|
|
35
|
+
if (typeof block === 'string') {
|
|
36
|
+
push({
|
|
37
|
+
type: 'content_block_start',
|
|
38
|
+
index,
|
|
39
|
+
content: {
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: '',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
for (const word of block.split(splitBy === 'word' ? /([ \t\n]+)/ : '')) {
|
|
46
|
+
push({
|
|
47
|
+
type: 'content_block_delta',
|
|
48
|
+
index,
|
|
49
|
+
delta: {
|
|
50
|
+
type: 'text_delta',
|
|
51
|
+
text: word,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
index += word.length + 1;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
push({
|
|
58
|
+
type: 'content_block_start',
|
|
59
|
+
index,
|
|
60
|
+
content: {
|
|
61
|
+
type: 'json',
|
|
62
|
+
json: '',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const text = JSON.stringify(block, null, 2);
|
|
67
|
+
for (const line of text.split('\n')) {
|
|
68
|
+
push({
|
|
69
|
+
type: 'content_block_delta',
|
|
70
|
+
index,
|
|
71
|
+
delta: {
|
|
72
|
+
type: 'input_json_delta',
|
|
73
|
+
partial_json: line,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
index += line.length + 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
push({
|
|
81
|
+
type: 'content_block_stop',
|
|
82
|
+
index,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
push({
|
|
87
|
+
type: 'message_stop',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
controller.close();
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as P from 'parsimmon';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parsed block.
|
|
9
|
+
*/
|
|
10
|
+
export type StreamBlock =
|
|
11
|
+
| {
|
|
12
|
+
type: 'tag';
|
|
13
|
+
tag: string;
|
|
14
|
+
attributes?: Record<string, string>;
|
|
15
|
+
content: StreamBlock[];
|
|
16
|
+
closing?: boolean;
|
|
17
|
+
selfClosing?: boolean;
|
|
18
|
+
}
|
|
19
|
+
| { type: 'text'; content: string }
|
|
20
|
+
| { type: 'json'; disposition?: string; content: string };
|
|
21
|
+
|
|
22
|
+
const textChunk: P.Parser<StreamBlock> = P.regexp(/[^<]+/).map((content) => ({
|
|
23
|
+
type: 'text',
|
|
24
|
+
content,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const attribute: P.Parser<[string, string]> = P.seq(
|
|
28
|
+
P.regexp(/[a-zA-Z_][a-zA-Z0-9_-]*/),
|
|
29
|
+
P.string('=').trim(P.optWhitespace),
|
|
30
|
+
P.alt(
|
|
31
|
+
P.regexp(/"[^"]*"/).map((s) => s.slice(1, -1)),
|
|
32
|
+
P.regexp(/'[^']*'/).map((s) => s.slice(1, -1)),
|
|
33
|
+
),
|
|
34
|
+
).map(([name, _, value]) => [name, value]);
|
|
35
|
+
|
|
36
|
+
const tagName = P.regexp(/[a-zA-Z_][a-zA-Z0-9_-]*/);
|
|
37
|
+
|
|
38
|
+
const selfClosingTag: P.Parser<StreamBlock> = P.seqMap(
|
|
39
|
+
//
|
|
40
|
+
P.string('<'),
|
|
41
|
+
tagName,
|
|
42
|
+
P.optWhitespace.then(attribute).many(),
|
|
43
|
+
P.optWhitespace.then(P.string('/>')),
|
|
44
|
+
P.optWhitespace,
|
|
45
|
+
(_, tag, attributes, __) => ({
|
|
46
|
+
type: 'tag',
|
|
47
|
+
tag,
|
|
48
|
+
attributes: Object.fromEntries(attributes),
|
|
49
|
+
content: [],
|
|
50
|
+
selfClosing: true,
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const openTag: P.Parser<StreamBlock> = P.seqMap(
|
|
55
|
+
//
|
|
56
|
+
P.string('<'),
|
|
57
|
+
tagName,
|
|
58
|
+
P.optWhitespace.then(attribute).many(),
|
|
59
|
+
P.string('>'),
|
|
60
|
+
P.regexp(/[^<]*/),
|
|
61
|
+
(_, tag, attributes, __, content) => ({
|
|
62
|
+
type: 'tag',
|
|
63
|
+
tag,
|
|
64
|
+
attributes: Object.fromEntries(attributes),
|
|
65
|
+
content: content.length ? [{ type: 'text', content }] : [],
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const closeTag: P.Parser<StreamBlock> = P.seqMap(
|
|
70
|
+
//
|
|
71
|
+
P.string('</'),
|
|
72
|
+
tagName,
|
|
73
|
+
P.string('>'),
|
|
74
|
+
P.optWhitespace,
|
|
75
|
+
(_, tag) => ({
|
|
76
|
+
type: 'tag',
|
|
77
|
+
tag,
|
|
78
|
+
content: [],
|
|
79
|
+
closing: true,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const mixedChunk: P.Parser<StreamBlock> = P.alt(selfClosingTag, openTag, closeTag, textChunk);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Permissive streaming transformer that processes mixed content (plain text and XML fragments) from the AI service.
|
|
87
|
+
*/
|
|
88
|
+
export class StreamTransform {
|
|
89
|
+
private _buffer = '';
|
|
90
|
+
|
|
91
|
+
transform(chunk: string): StreamBlock[] {
|
|
92
|
+
this._buffer += chunk;
|
|
93
|
+
|
|
94
|
+
const results: StreamBlock[] = [];
|
|
95
|
+
const parser = mixedChunk.many().skip(P.optWhitespace);
|
|
96
|
+
const result = parser.parse(this._buffer);
|
|
97
|
+
if (result.status) {
|
|
98
|
+
for (const chunk of result.value) {
|
|
99
|
+
results.push(chunk);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this._buffer = '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// TODO(burdon): Process remaining buffer?
|
|
109
|
+
flush(): StreamBlock[] {
|
|
110
|
+
const remaining = this._buffer;
|
|
111
|
+
this._buffer = '';
|
|
112
|
+
return remaining.length > 0
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
type: 'text',
|
|
116
|
+
content: remaining,
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
: [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema as S } from '@effect/schema';
|
|
6
|
+
|
|
7
|
+
import { Tool, Message, type MessageContentBlock, SpaceIdSchema } from '@dxos/artifact';
|
|
8
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
9
|
+
|
|
10
|
+
export const createArtifactElement = (id: ObjectId) => `<artifact id=${id} />`;
|
|
11
|
+
|
|
12
|
+
export const LLMModel = S.Literal(
|
|
13
|
+
'@anthropic/claude-3-5-haiku-20241022',
|
|
14
|
+
'@anthropic/claude-3-5-sonnet-20241022',
|
|
15
|
+
'@cf/deepseek-ai/deepseek-r1-distill-qwen-32b',
|
|
16
|
+
'@hf/nousresearch/hermes-2-pro-mistral-7b',
|
|
17
|
+
'@ollama/llama-3-2-3b',
|
|
18
|
+
'@ollama/llama-3-1-nemotron-70b-instruct',
|
|
19
|
+
'@ollama/llama-3-1-nemotron-mini-4b-instruct',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export type LLMModel = S.Schema.Type<typeof LLMModel>;
|
|
23
|
+
|
|
24
|
+
export const ToolTypes = Object.freeze({
|
|
25
|
+
// TODO(dmaretskyi): Not implemented yet.
|
|
26
|
+
// DatabaseQuery: 'database_query',
|
|
27
|
+
TextToImage: 'text_to_image',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Client GPT request.
|
|
32
|
+
*/
|
|
33
|
+
export const GenerateRequest = S.Struct({
|
|
34
|
+
spaceId: S.optional(SpaceIdSchema),
|
|
35
|
+
threadId: S.optional(ObjectId),
|
|
36
|
+
|
|
37
|
+
// TODO(burdon): Make optional with default.
|
|
38
|
+
model: LLMModel,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Current request.
|
|
42
|
+
*/
|
|
43
|
+
// TODO(burdon): Remove from history.
|
|
44
|
+
// prompt: Message,
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* System instructions to the LLM.
|
|
48
|
+
*/
|
|
49
|
+
systemPrompt: S.optional(S.String),
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* History of messages to include in the context window.
|
|
53
|
+
*/
|
|
54
|
+
history: S.optional(S.Array(Message)),
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Tools available for the LLM.
|
|
58
|
+
*/
|
|
59
|
+
tools: S.optional(S.Array(Tool).pipe(S.mutable)),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export type GenerateRequest = S.Schema.Type<typeof GenerateRequest>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Server-Sent Events (SSE) stream from the AI service.
|
|
66
|
+
* https://docs.anthropic.com/en/api/streaming
|
|
67
|
+
* https://platform.openai.com/docs/api-reference/streaming
|
|
68
|
+
* https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
|
69
|
+
*/
|
|
70
|
+
// TODO(dmaretskyi): Effect schema.
|
|
71
|
+
export type GenerationStreamEvent =
|
|
72
|
+
| {
|
|
73
|
+
// TODO(dmaretskyi): Normalize types to our schema.
|
|
74
|
+
type: 'message_start';
|
|
75
|
+
message: Omit<Message, 'spaceId' | 'threadId'>;
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
type: 'message_delta';
|
|
79
|
+
delta: {
|
|
80
|
+
stopReason: 'tool_use' | 'end_turn';
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
| {
|
|
84
|
+
type: 'message_stop';
|
|
85
|
+
}
|
|
86
|
+
| {
|
|
87
|
+
type: 'content_block_start';
|
|
88
|
+
index: number;
|
|
89
|
+
content: MessageContentBlock;
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
type: 'content_block_delta';
|
|
93
|
+
index: number;
|
|
94
|
+
delta:
|
|
95
|
+
| {
|
|
96
|
+
type: 'text_delta';
|
|
97
|
+
text: string;
|
|
98
|
+
}
|
|
99
|
+
| {
|
|
100
|
+
type: 'input_json_delta';
|
|
101
|
+
partial_json: string;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
| {
|
|
105
|
+
type: 'content_block_stop';
|
|
106
|
+
index: number;
|
|
107
|
+
};
|
package/src/bin/chat.ts
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { writeFileSync } from 'fs';
|
|
5
6
|
import inquirer from 'inquirer';
|
|
6
7
|
|
|
8
|
+
import { createUserMessage } from '@dxos/artifact';
|
|
9
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
7
10
|
import { SpaceId } from '@dxos/keys';
|
|
11
|
+
import { log } from '@dxos/log';
|
|
8
12
|
|
|
9
|
-
import { AIServiceClientImpl,
|
|
10
|
-
import { runLLM
|
|
13
|
+
import { AIServiceClientImpl, ToolTypes } from '../ai-service';
|
|
14
|
+
import { runLLM } from '../conversation';
|
|
11
15
|
import {
|
|
16
|
+
AI_SERVICE_ENDPOINT,
|
|
12
17
|
createLogger,
|
|
13
18
|
createCypherTool,
|
|
14
19
|
createSystemPrompt,
|
|
@@ -18,15 +23,9 @@ import {
|
|
|
18
23
|
Project,
|
|
19
24
|
Task,
|
|
20
25
|
} from '../testing';
|
|
21
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
22
|
-
|
|
23
|
-
// TODO(burdon): Move out of src?
|
|
24
|
-
|
|
25
|
-
const ENDPOINT = 'http://localhost:8787';
|
|
26
|
-
// const ENDPOINT = 'https://ai-service.dxos.workers.dev';
|
|
27
26
|
|
|
28
27
|
const client = new AIServiceClientImpl({
|
|
29
|
-
endpoint:
|
|
28
|
+
endpoint: AI_SERVICE_ENDPOINT.LOCAL,
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
const dataSource = createTestData();
|
|
@@ -46,7 +45,7 @@ while (true) {
|
|
|
46
45
|
message: 'Enter a message:',
|
|
47
46
|
},
|
|
48
47
|
]);
|
|
49
|
-
await client.
|
|
48
|
+
await client.appendMessages([createUserMessage(spaceId, threadId, prompt.message)]);
|
|
50
49
|
|
|
51
50
|
await runLLM({
|
|
52
51
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
@@ -69,7 +68,7 @@ while (true) {
|
|
|
69
68
|
onImage: (img) => {
|
|
70
69
|
const path = `/tmp/image-${img.id}.jpeg`;
|
|
71
70
|
writeFileSync(path, Buffer.from(img.source.data, 'base64'));
|
|
72
|
-
|
|
71
|
+
log.info('Saved image', { path });
|
|
73
72
|
// Print image in iTerm using ANSI escape sequence
|
|
74
73
|
const imageData = img.source.data;
|
|
75
74
|
process.stdout.write('\x1b]1337;File=inline=1:' + imageData + '\x07');
|
|
@@ -5,34 +5,35 @@
|
|
|
5
5
|
import { Schema as S } from '@effect/schema';
|
|
6
6
|
import { describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
+
import { createUserMessage, defineTool, ToolResult } from '@dxos/artifact';
|
|
9
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
8
10
|
import { SpaceId } from '@dxos/keys';
|
|
9
11
|
import { log } from '@dxos/log';
|
|
10
12
|
|
|
11
13
|
import { runLLM, type ConversationEvent } from './conversation';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
const ENDPOINT = 'http://localhost:8787';
|
|
14
|
+
import { AIServiceClientImpl } from '../ai-service';
|
|
15
|
+
import { AI_SERVICE_ENDPOINT } from '../testing';
|
|
16
16
|
|
|
17
|
+
// TODO(burdon): Local live LLM test.
|
|
17
18
|
describe.skip('Conversation tests', () => {
|
|
18
19
|
const client = new AIServiceClientImpl({
|
|
19
|
-
endpoint:
|
|
20
|
+
endpoint: AI_SERVICE_ENDPOINT.LOCAL,
|
|
20
21
|
});
|
|
21
22
|
|
|
22
|
-
test('
|
|
23
|
+
test('basic', async ({ expect }) => {
|
|
23
24
|
const spaceId = SpaceId.random();
|
|
24
25
|
const threadId = ObjectId.random();
|
|
25
26
|
|
|
26
|
-
await client.insertMessages([createUserMessage(spaceId, threadId, 'Hello, how are you?')]);
|
|
27
27
|
const result = await runLLM({
|
|
28
28
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
29
|
-
spaceId,
|
|
30
|
-
threadId,
|
|
29
|
+
history: [createUserMessage(spaceId, threadId, 'Hello, how are you?')],
|
|
31
30
|
tools: [],
|
|
32
31
|
client,
|
|
33
32
|
logger: messageLogger,
|
|
34
33
|
});
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
log('result', { result });
|
|
36
|
+
expect(result.history.length).to.equal(2);
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
test('tool call', async ({ expect }) => {
|
|
@@ -44,9 +45,9 @@ describe.skip('Conversation tests', () => {
|
|
|
44
45
|
}),
|
|
45
46
|
execute: async ({ magicWord }) => {
|
|
46
47
|
if (magicWord === 'pretty please') {
|
|
47
|
-
return
|
|
48
|
+
return ToolResult.Success('The password is: "The sky is gray"');
|
|
48
49
|
} else {
|
|
49
|
-
return
|
|
50
|
+
return ToolResult.Error('Wrong magic word');
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
53
|
});
|
|
@@ -54,21 +55,19 @@ describe.skip('Conversation tests', () => {
|
|
|
54
55
|
const spaceId = SpaceId.random();
|
|
55
56
|
const threadId = ObjectId.random();
|
|
56
57
|
|
|
57
|
-
await client.insertMessages([createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')]);
|
|
58
58
|
const result = await runLLM({
|
|
59
59
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
60
|
-
spaceId,
|
|
61
|
-
threadId,
|
|
60
|
+
history: [createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')],
|
|
62
61
|
tools: [custodian],
|
|
63
62
|
client,
|
|
64
63
|
logger: messageLogger,
|
|
65
64
|
});
|
|
66
|
-
log
|
|
65
|
+
log('result', { result });
|
|
67
66
|
});
|
|
68
67
|
|
|
69
68
|
const messageLogger = (event: ConversationEvent) => {
|
|
70
69
|
if (event.type === 'message') {
|
|
71
|
-
log
|
|
70
|
+
log('message', { message: event.message });
|
|
72
71
|
}
|
|
73
72
|
};
|
|
74
73
|
});
|