@dxos/assistant 0.7.4-df37807 → 0.7.5-feature-compute.4d9d99a
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-AKP45LQH.mjs → chunk-LVLSJQB3.mjs} +506 -357
- package/dist/lib/browser/chunk-LVLSJQB3.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +17 -17
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +10 -3
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/{chunk-G6CGWEMO.cjs → chunk-SHOSAIOD.cjs} +507 -362
- package/dist/lib/node/chunk-SHOSAIOD.cjs.map +7 -0
- package/dist/lib/node/index.cjs +42 -42
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +18 -12
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/{chunk-WUPUPEFB.mjs → chunk-ZLBTRSUY.mjs} +506 -357
- package/dist/lib/node-esm/chunk-ZLBTRSUY.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +17 -17
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +10 -3
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/ai-service/client.d.ts +15 -32
- package/dist/types/src/ai-service/client.d.ts.map +1 -1
- package/dist/types/src/ai-service/index.d.ts +2 -1
- package/dist/types/src/ai-service/index.d.ts.map +1 -1
- package/dist/types/src/ai-service/stream.d.ts +34 -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/types/index.d.ts +3 -0
- package/dist/types/src/ai-service/types/index.d.ts.map +1 -0
- package/dist/types/src/ai-service/types/message.d.ts +191 -0
- package/dist/types/src/ai-service/types/message.d.ts.map +1 -0
- package/dist/types/src/ai-service/types/schema.d.ts +175 -0
- package/dist/types/src/ai-service/types/schema.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 +54 -4
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/conversation/types.d.ts +35 -4
- 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/test-data.d.ts +1 -1
- package/dist/types/src/testing/test-data.d.ts.map +1 -1
- package/package.json +20 -18
- package/src/ai-service/client.test.ts +25 -26
- package/src/ai-service/client.ts +29 -177
- package/src/ai-service/index.ts +2 -1
- package/src/ai-service/stream.test.ts +186 -0
- package/src/ai-service/stream.ts +181 -0
- package/src/ai-service/types/index.ts +6 -0
- package/src/ai-service/types/message.ts +147 -0
- package/src/ai-service/types/schema.ts +128 -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 +6 -9
- package/src/conversation/conversation.test.ts +10 -14
- package/src/conversation/conversation.ts +109 -14
- package/src/conversation/types.ts +47 -6
- package/src/cypher/combinators.test.ts +3 -3
- 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 +0 -1
- 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/query.test.ts +9 -9
- package/src/testing/test-data.ts +1 -1
- package/vendor/parjs-browser.mjs +4 -0
- package/vendor/parjs-default.mjs +8 -0
- package/dist/lib/browser/chunk-AKP45LQH.mjs.map +0 -7
- package/dist/lib/node/chunk-G6CGWEMO.cjs.map +0 -7
- 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,147 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema as S } from '@effect/schema';
|
|
6
|
+
|
|
7
|
+
import { EchoObject, ObjectId } from '@dxos/echo-schema';
|
|
8
|
+
import { SpaceId } from '@dxos/keys';
|
|
9
|
+
|
|
10
|
+
// TODO(dmaretskyi): Dedupe package with dxos/edge.
|
|
11
|
+
// TODO(dmaretskyi): Extract IDs to protocols.
|
|
12
|
+
|
|
13
|
+
export const SpaceIdSchema: S.Schema<SpaceId, string> = S.String.pipe(S.filter(SpaceId.isValid));
|
|
14
|
+
|
|
15
|
+
export const Space = S.Struct({
|
|
16
|
+
id: SpaceIdSchema,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export interface Space extends S.Schema.Type<typeof Space> {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Thread.
|
|
23
|
+
*/
|
|
24
|
+
export const Thread = S.Struct({
|
|
25
|
+
id: ObjectId,
|
|
26
|
+
spaceId: SpaceIdSchema,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export interface Thread extends S.Schema.Type<typeof Thread> {}
|
|
30
|
+
|
|
31
|
+
export const MessageRole = S.String.pipe(S.filter((role) => role === 'user' || role === 'assistant'));
|
|
32
|
+
export type MessageRole = S.Schema.Type<typeof MessageRole>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tagged block.
|
|
36
|
+
*/
|
|
37
|
+
export const TagContentBlock = S.Struct({
|
|
38
|
+
type: S.Literal('tag'),
|
|
39
|
+
tag: S.String,
|
|
40
|
+
text: S.String,
|
|
41
|
+
}).pipe(S.mutable);
|
|
42
|
+
|
|
43
|
+
export type TagContentBlock = S.Schema.Type<typeof TagContentBlock>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Text.
|
|
47
|
+
*/
|
|
48
|
+
export const TextContentBlock = S.Struct({
|
|
49
|
+
type: S.Literal('text'),
|
|
50
|
+
text: S.String,
|
|
51
|
+
}).pipe(S.mutable);
|
|
52
|
+
|
|
53
|
+
export type TextContentBlock = S.Schema.Type<typeof TextContentBlock>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Image.
|
|
57
|
+
*/
|
|
58
|
+
export const ImageSource = S.Struct({
|
|
59
|
+
type: S.Literal('base64'),
|
|
60
|
+
media_type: S.Literal('image/jpeg', 'image/png', 'image/gif', 'image/webp'),
|
|
61
|
+
data: S.String,
|
|
62
|
+
}).pipe(S.mutable);
|
|
63
|
+
|
|
64
|
+
export type ImageSource = S.Schema.Type<typeof ImageSource>;
|
|
65
|
+
|
|
66
|
+
export const ImageContentBlock = S.Struct({
|
|
67
|
+
type: S.Literal('image'),
|
|
68
|
+
id: S.optional(S.String),
|
|
69
|
+
source: S.optional(ImageSource),
|
|
70
|
+
}).pipe(S.mutable);
|
|
71
|
+
|
|
72
|
+
export type ImageContentBlock = S.Schema.Type<typeof ImageContentBlock>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Tool use.
|
|
76
|
+
*/
|
|
77
|
+
export const ToolUseContentBlock = S.Struct({
|
|
78
|
+
type: S.Literal('tool_use'),
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Opaque service-defined ID of the tool invocation.
|
|
82
|
+
* Used to match the tool result block.
|
|
83
|
+
*/
|
|
84
|
+
id: S.String,
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tool name.
|
|
88
|
+
*/
|
|
89
|
+
name: S.String,
|
|
90
|
+
|
|
91
|
+
input: S.Unknown,
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Used to accumulate the partial tool input JSON in streaming mode.
|
|
95
|
+
*/
|
|
96
|
+
// TODO(burdon): Remove?
|
|
97
|
+
inputJson: S.optional(S.String),
|
|
98
|
+
}).pipe(S.mutable);
|
|
99
|
+
|
|
100
|
+
export const ToolResultContentBlock = S.Struct({
|
|
101
|
+
type: S.Literal('tool_result'),
|
|
102
|
+
toolUseId: S.String,
|
|
103
|
+
content: S.String,
|
|
104
|
+
isError: S.optional(S.Boolean),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Content union.
|
|
109
|
+
*/
|
|
110
|
+
export const MessageContentBlock = S.Union(
|
|
111
|
+
TagContentBlock,
|
|
112
|
+
TextContentBlock,
|
|
113
|
+
ImageContentBlock,
|
|
114
|
+
ToolUseContentBlock,
|
|
115
|
+
ToolResultContentBlock,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
export type MessageContentBlock = S.Schema.Type<typeof MessageContentBlock>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Message.
|
|
122
|
+
*/
|
|
123
|
+
const MessageSchema = S.Struct({
|
|
124
|
+
id: ObjectId,
|
|
125
|
+
threadId: S.optional(ObjectId),
|
|
126
|
+
spaceId: S.optional(SpaceIdSchema),
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* ID of the message from the foreign provider.
|
|
130
|
+
*/
|
|
131
|
+
// TODO(dmaretskyi): Should be in meta/keys.
|
|
132
|
+
foreignId: S.optional(S.String),
|
|
133
|
+
|
|
134
|
+
// TODO(dmaretskyi): Figure out how to deal with those.
|
|
135
|
+
// created: S.optional(S.DateFromString),
|
|
136
|
+
// updated: S.optional(S.DateFromString),
|
|
137
|
+
|
|
138
|
+
role: MessageRole,
|
|
139
|
+
|
|
140
|
+
// TODO(burdon): Rename blocks?
|
|
141
|
+
content: S.Array(MessageContentBlock).pipe(S.mutable),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// TODO(burdon): Reconcile with Chat/Message types.
|
|
145
|
+
export const Message = MessageSchema.pipe(EchoObject('dxos.org/type/Message', '0.1.0'));
|
|
146
|
+
|
|
147
|
+
export type Message = S.Schema.Type<typeof Message>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema as S } from '@effect/schema';
|
|
6
|
+
|
|
7
|
+
import { JsonSchemaType, ObjectId } from '@dxos/echo-schema';
|
|
8
|
+
|
|
9
|
+
import { Message, type MessageContentBlock, SpaceIdSchema } from './message';
|
|
10
|
+
|
|
11
|
+
export const LLMModel = S.Literal(
|
|
12
|
+
'@hf/nousresearch/hermes-2-pro-mistral-7b',
|
|
13
|
+
'@anthropic/claude-3-5-sonnet-20241022',
|
|
14
|
+
'@anthropic/claude-3-5-haiku-20241022',
|
|
15
|
+
'@ollama/llama-3-2-3b',
|
|
16
|
+
'@ollama/llama-3-1-nemotron-70b-instruct',
|
|
17
|
+
'@ollama/llama-3-1-nemotron-mini-4b-instruct',
|
|
18
|
+
'@cf/deepseek-ai/deepseek-r1-distill-qwen-32b',
|
|
19
|
+
);
|
|
20
|
+
export type LLMModel = S.Schema.Type<typeof LLMModel>;
|
|
21
|
+
|
|
22
|
+
export const ToolTypes = Object.freeze({
|
|
23
|
+
// TODO(dmaretskyi): Not implemented yet.
|
|
24
|
+
// DatabaseQuery: 'database_query',
|
|
25
|
+
TextToImage: 'text_to_image',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const LLMTool = S.Struct({
|
|
29
|
+
name: S.String,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* If the tool is implemented by the service a type should be provided.
|
|
33
|
+
* For user-implemented tools, this field should be omitted.
|
|
34
|
+
* See {@link ToolTypes} for the list of supported types.
|
|
35
|
+
*/
|
|
36
|
+
type: S.optional(S.String),
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Required for user-implemented tools.
|
|
40
|
+
*/
|
|
41
|
+
description: S.optional(S.String),
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Input schema for the tool in the JSON Schema format.
|
|
45
|
+
* Required for user-implemented tools.
|
|
46
|
+
*/
|
|
47
|
+
parameters: S.optional(JsonSchemaType),
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tool-specific options.
|
|
51
|
+
*/
|
|
52
|
+
options: S.optional(S.Any),
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Javascript function to execute the tool.
|
|
56
|
+
*/
|
|
57
|
+
execute: S.optional(S.Any),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export interface LLMTool extends S.Schema.Type<typeof LLMTool> {}
|
|
61
|
+
|
|
62
|
+
export const GenerateRequest = S.Struct({
|
|
63
|
+
model: LLMModel,
|
|
64
|
+
|
|
65
|
+
spaceId: S.optional(SpaceIdSchema),
|
|
66
|
+
threadId: S.optional(ObjectId),
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* History of messages to include in the context window.
|
|
70
|
+
*/
|
|
71
|
+
history: S.optional(S.Array(Message)),
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* System instructions to the LLM.
|
|
75
|
+
*/
|
|
76
|
+
// TODO(dmaretskyi): Should this be part of the thread?.
|
|
77
|
+
systemPrompt: S.optional(S.String),
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Tools available for the LLM.
|
|
81
|
+
*/
|
|
82
|
+
tools: S.optional(S.Array(LLMTool).pipe(S.mutable)),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export type GenerateRequest = S.Schema.Type<typeof GenerateRequest>;
|
|
86
|
+
|
|
87
|
+
export type LLMStopReason = 'tool_use' | 'end_turn';
|
|
88
|
+
|
|
89
|
+
// TODO(dmaretskyi): Effect schema.
|
|
90
|
+
// TODO(dmaretskyi): Rename GenerationStreamEvent.
|
|
91
|
+
export type ResultStreamEvent =
|
|
92
|
+
| {
|
|
93
|
+
// TODO(dmaretskyi): Normalize types to our schema.
|
|
94
|
+
type: 'message_start';
|
|
95
|
+
message: Omit<Message, 'spaceId' | 'threadId'>;
|
|
96
|
+
}
|
|
97
|
+
| {
|
|
98
|
+
type: 'message_delta';
|
|
99
|
+
delta: {
|
|
100
|
+
stopReason: LLMStopReason;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
| {
|
|
104
|
+
type: 'message_stop';
|
|
105
|
+
}
|
|
106
|
+
| {
|
|
107
|
+
type: 'content_block_start';
|
|
108
|
+
index: number;
|
|
109
|
+
content: MessageContentBlock;
|
|
110
|
+
}
|
|
111
|
+
| {
|
|
112
|
+
type: 'content_block_delta';
|
|
113
|
+
index: number;
|
|
114
|
+
delta:
|
|
115
|
+
| {
|
|
116
|
+
type: 'text_delta';
|
|
117
|
+
text: string;
|
|
118
|
+
}
|
|
119
|
+
| {
|
|
120
|
+
partial_json: string;
|
|
121
|
+
|
|
122
|
+
type: 'input_json_delta';
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
| {
|
|
126
|
+
type: 'content_block_stop';
|
|
127
|
+
index: number;
|
|
128
|
+
};
|
package/src/bin/chat.ts
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
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 { ObjectId } from '@dxos/echo-schema';
|
|
7
9
|
import { SpaceId } from '@dxos/keys';
|
|
8
10
|
|
|
9
|
-
import { AIServiceClientImpl,
|
|
11
|
+
import { AIServiceClientImpl, ToolTypes } from '../ai-service';
|
|
10
12
|
import { runLLM, createUserMessage } from '../conversation';
|
|
11
13
|
import {
|
|
12
14
|
createLogger,
|
|
@@ -17,16 +19,11 @@ import {
|
|
|
17
19
|
Org,
|
|
18
20
|
Project,
|
|
19
21
|
Task,
|
|
22
|
+
AI_SERVICE_ENDPOINT,
|
|
20
23
|
} 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
24
|
|
|
28
25
|
const client = new AIServiceClientImpl({
|
|
29
|
-
endpoint:
|
|
26
|
+
endpoint: AI_SERVICE_ENDPOINT.LOCAL,
|
|
30
27
|
});
|
|
31
28
|
|
|
32
29
|
const dataSource = createTestData();
|
|
@@ -46,7 +43,7 @@ while (true) {
|
|
|
46
43
|
message: 'Enter a message:',
|
|
47
44
|
},
|
|
48
45
|
]);
|
|
49
|
-
await client.
|
|
46
|
+
await client.appendMessages([createUserMessage(spaceId, threadId, prompt.message)]);
|
|
50
47
|
|
|
51
48
|
await runLLM({
|
|
52
49
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
@@ -5,37 +5,35 @@
|
|
|
5
5
|
import { Schema as S } from '@effect/schema';
|
|
6
6
|
import { describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
8
9
|
import { SpaceId } from '@dxos/keys';
|
|
9
10
|
import { log } from '@dxos/log';
|
|
10
11
|
|
|
11
12
|
import { runLLM, type ConversationEvent } from './conversation';
|
|
12
13
|
import { createUserMessage, defineTool, LLMToolResult } from './types';
|
|
13
|
-
import { AIServiceClientImpl
|
|
14
|
-
|
|
15
|
-
const ENDPOINT = 'http://localhost:8787';
|
|
14
|
+
import { AIServiceClientImpl } from '../ai-service';
|
|
15
|
+
import { AI_SERVICE_ENDPOINT } from '../testing';
|
|
16
16
|
|
|
17
17
|
describe.skip('Conversation tests', () => {
|
|
18
18
|
const client = new AIServiceClientImpl({
|
|
19
|
-
endpoint:
|
|
19
|
+
endpoint: AI_SERVICE_ENDPOINT.LOCAL,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
test('hello', async ({ expect }) => {
|
|
23
23
|
const spaceId = SpaceId.random();
|
|
24
24
|
const threadId = ObjectId.random();
|
|
25
25
|
|
|
26
|
-
await client.insertMessages([createUserMessage(spaceId, threadId, 'Hello, how are you?')]);
|
|
27
26
|
const result = await runLLM({
|
|
28
27
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
29
|
-
spaceId,
|
|
30
|
-
threadId,
|
|
28
|
+
history: [createUserMessage(spaceId, threadId, 'Hello, how are you?')],
|
|
31
29
|
tools: [],
|
|
32
30
|
client,
|
|
33
31
|
logger: messageLogger,
|
|
34
32
|
});
|
|
35
|
-
log
|
|
33
|
+
log('result', { result });
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
test('tool call', async ({ expect }) => {
|
|
36
|
+
test.only('tool call', async ({ expect }) => {
|
|
39
37
|
const custodian = defineTool({
|
|
40
38
|
name: 'custodian',
|
|
41
39
|
description: 'Custodian can tell you the password if you say the magic word',
|
|
@@ -54,21 +52,19 @@ describe.skip('Conversation tests', () => {
|
|
|
54
52
|
const spaceId = SpaceId.random();
|
|
55
53
|
const threadId = ObjectId.random();
|
|
56
54
|
|
|
57
|
-
await client.insertMessages([createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')]);
|
|
58
55
|
const result = await runLLM({
|
|
59
56
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
60
|
-
spaceId,
|
|
61
|
-
threadId,
|
|
57
|
+
history: [createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')],
|
|
62
58
|
tools: [custodian],
|
|
63
59
|
client,
|
|
64
60
|
logger: messageLogger,
|
|
65
61
|
});
|
|
66
|
-
log
|
|
62
|
+
log('result', { result });
|
|
67
63
|
});
|
|
68
64
|
|
|
69
65
|
const messageLogger = (event: ConversationEvent) => {
|
|
70
66
|
if (event.type === 'message') {
|
|
71
|
-
log
|
|
67
|
+
log('message', { message: event.message });
|
|
72
68
|
}
|
|
73
69
|
};
|
|
74
70
|
});
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { createStatic, ObjectId } from '@dxos/echo-schema';
|
|
5
6
|
import { invariant } from '@dxos/invariant';
|
|
6
7
|
import { type SpaceId } from '@dxos/keys';
|
|
7
8
|
import { log } from '@dxos/log';
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { type AIServiceClient, type Message, type ResultStreamEvent, type LLMModel } from '../ai-service';
|
|
10
|
+
import { LLMToolResult } from './types';
|
|
11
|
+
import { type LLMTool, Message, type AIServiceClient, type ResultStreamEvent, type LLMModel } from '../ai-service';
|
|
12
12
|
|
|
13
13
|
export type CreateLLMConversationParams = {
|
|
14
14
|
model: LLMModel;
|
|
@@ -18,11 +18,13 @@ export type CreateLLMConversationParams = {
|
|
|
18
18
|
*/
|
|
19
19
|
system?: string;
|
|
20
20
|
|
|
21
|
-
spaceId
|
|
22
|
-
threadId
|
|
21
|
+
spaceId?: SpaceId;
|
|
22
|
+
threadId?: ObjectId;
|
|
23
23
|
|
|
24
24
|
tools: LLMTool[];
|
|
25
25
|
|
|
26
|
+
history?: Message[];
|
|
27
|
+
|
|
26
28
|
client: AIServiceClient;
|
|
27
29
|
|
|
28
30
|
logger?: (event: ConversationEvent) => void;
|
|
@@ -37,29 +39,33 @@ export type ConversationEvent =
|
|
|
37
39
|
|
|
38
40
|
export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
39
41
|
let conversationResult: any = null;
|
|
42
|
+
const history = params.history ?? [];
|
|
40
43
|
|
|
41
44
|
const generate = async () => {
|
|
42
45
|
log('llm generate', { tools: params.tools });
|
|
43
46
|
const beginTs = Date.now();
|
|
44
|
-
const
|
|
47
|
+
const stream = await params.client.generate({
|
|
45
48
|
model: params.model,
|
|
46
49
|
spaceId: params.spaceId,
|
|
47
50
|
threadId: params.threadId,
|
|
51
|
+
history,
|
|
48
52
|
systemPrompt: params.system,
|
|
49
53
|
tools: params.tools as any,
|
|
50
54
|
});
|
|
51
55
|
|
|
52
|
-
for await (const event of
|
|
56
|
+
for await (const event of stream) {
|
|
53
57
|
params.logger?.(event);
|
|
54
58
|
}
|
|
55
|
-
const messages = await
|
|
59
|
+
const messages = await stream.complete();
|
|
56
60
|
const message = messages.at(-1);
|
|
57
61
|
|
|
58
62
|
log('llm result', { time: Date.now() - beginTs, message });
|
|
59
63
|
invariant(message);
|
|
60
64
|
params.logger?.({ type: 'message', message });
|
|
61
|
-
|
|
62
|
-
messages.map(
|
|
65
|
+
history.push(
|
|
66
|
+
...messages.map(
|
|
67
|
+
(message): Message => ({ ...message, content: message.content.filter((block) => block.type !== 'image') }),
|
|
68
|
+
),
|
|
63
69
|
);
|
|
64
70
|
|
|
65
71
|
const isToolUse = message.content.at(-1)?.type === 'tool_use';
|
|
@@ -67,7 +73,7 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
67
73
|
const toolCalls = message.content.filter((c) => c.type === 'tool_use');
|
|
68
74
|
invariant(toolCalls.length === 1);
|
|
69
75
|
const toolCall = toolCalls[0];
|
|
70
|
-
const tool = params.tools.find((
|
|
76
|
+
const tool = params.tools.find((tool) => tool.name === toolCall.name);
|
|
71
77
|
if (!tool) {
|
|
72
78
|
throw new Error(`Tool not found: ${toolCall.name}`);
|
|
73
79
|
}
|
|
@@ -75,7 +81,7 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
75
81
|
const toolResult = await tool.execute(toolCall.input, {});
|
|
76
82
|
switch (toolResult.kind) {
|
|
77
83
|
case 'error': {
|
|
78
|
-
log('tool error', { message: toolResult.message });
|
|
84
|
+
log.warn('tool error', { message: toolResult.message });
|
|
79
85
|
const resultMessage: Message = {
|
|
80
86
|
id: ObjectId.random(),
|
|
81
87
|
spaceId: params.spaceId,
|
|
@@ -91,10 +97,11 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
91
97
|
],
|
|
92
98
|
};
|
|
93
99
|
params.logger?.({ type: 'message', message: resultMessage });
|
|
94
|
-
|
|
100
|
+
history.push(resultMessage);
|
|
95
101
|
|
|
96
102
|
return true;
|
|
97
103
|
}
|
|
104
|
+
|
|
98
105
|
case 'success': {
|
|
99
106
|
log('tool success', { result: toolResult.result });
|
|
100
107
|
const resultMessage: Message = {
|
|
@@ -111,10 +118,11 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
111
118
|
],
|
|
112
119
|
};
|
|
113
120
|
params.logger?.({ type: 'message', message: resultMessage });
|
|
114
|
-
|
|
121
|
+
history.push(resultMessage);
|
|
115
122
|
|
|
116
123
|
return true;
|
|
117
124
|
}
|
|
125
|
+
|
|
118
126
|
case 'break': {
|
|
119
127
|
log('tool break', { result: toolResult.result });
|
|
120
128
|
conversationResult = toolResult.result;
|
|
@@ -130,5 +138,92 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
|
|
|
130
138
|
|
|
131
139
|
return {
|
|
132
140
|
result: conversationResult,
|
|
141
|
+
history,
|
|
133
142
|
};
|
|
134
143
|
};
|
|
144
|
+
|
|
145
|
+
export const isToolUse = (message: Message) => message.content.at(-1)?.type === 'tool_use';
|
|
146
|
+
|
|
147
|
+
type RunToolsOptions = {
|
|
148
|
+
message: Message;
|
|
149
|
+
tools: LLMTool[];
|
|
150
|
+
extensions?: LLMToolContextExtensions;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export type RunToolsResult =
|
|
154
|
+
| {
|
|
155
|
+
type: 'continue';
|
|
156
|
+
message: Message;
|
|
157
|
+
}
|
|
158
|
+
| {
|
|
159
|
+
type: 'break';
|
|
160
|
+
result: unknown;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const runTools = async ({ message, tools, extensions }: RunToolsOptions): Promise<RunToolsResult> => {
|
|
164
|
+
const toolCalls = message.content.filter((block) => block.type === 'tool_use');
|
|
165
|
+
invariant(toolCalls.length === 1);
|
|
166
|
+
const toolCall = toolCalls[0];
|
|
167
|
+
const tool = tools.find((tool) => tool.name === toolCall.name);
|
|
168
|
+
if (!tool) {
|
|
169
|
+
throw new Error(`Tool not found: ${toolCall.name}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let toolResult: LLMToolResult;
|
|
173
|
+
try {
|
|
174
|
+
toolResult = await tool.execute(toolCall.input, { extensions });
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
log('tool error', { error });
|
|
177
|
+
toolResult = LLMToolResult.Error(error.message);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
switch (toolResult.kind) {
|
|
181
|
+
case 'error': {
|
|
182
|
+
log('tool error', { message: toolResult.message });
|
|
183
|
+
const resultMessage = createStatic(Message, {
|
|
184
|
+
role: 'user',
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: 'tool_result',
|
|
188
|
+
toolUseId: toolCall.id,
|
|
189
|
+
content: toolResult.message,
|
|
190
|
+
isError: true,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// TODO(burdon): Retry count?
|
|
196
|
+
return {
|
|
197
|
+
type: 'continue',
|
|
198
|
+
message: resultMessage,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case 'success': {
|
|
203
|
+
log('tool success', { result: toolResult.result });
|
|
204
|
+
const resultMessage = createStatic(Message, {
|
|
205
|
+
role: 'user',
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'tool_result',
|
|
209
|
+
toolUseId: toolCall.id,
|
|
210
|
+
content: typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
type: 'continue',
|
|
217
|
+
message: resultMessage,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case 'break': {
|
|
222
|
+
log('tool break', { result: toolResult.result });
|
|
223
|
+
return {
|
|
224
|
+
type: 'break',
|
|
225
|
+
result: toolResult.result,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|