@dxos/assistant 0.8.4-main.937b3ca → 0.8.4-main.9be5663bfe
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/README.md +1 -0
- package/dist/lib/neutral/chunk-UAWIXKF6.mjs +1875 -0
- package/dist/lib/neutral/chunk-UAWIXKF6.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/extraction/index.mjs +26 -32
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/index.mjs +73 -372
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/testing/index.mjs +114 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +46 -20
- package/dist/types/src/conversation/context.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.d.ts +37 -10
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/diff.d.ts +3 -1
- package/dist/types/src/diff.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/index.d.ts +0 -1
- package/dist/types/src/extraction/extraction-ner-function/index.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +933 -221
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/index.d.ts +0 -1
- package/dist/types/src/extraction/index.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +27 -8
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/service/AgentService.d.ts +61 -0
- package/dist/types/src/service/AgentService.d.ts.map +1 -0
- package/dist/types/src/service/AgentService.test.d.ts +2 -0
- package/dist/types/src/service/AgentService.test.d.ts.map +1 -0
- package/dist/types/src/service/agent-process.d.ts +17 -0
- package/dist/types/src/service/agent-process.d.ts.map +1 -0
- package/dist/types/src/service/index.d.ts +3 -0
- package/dist/types/src/service/index.d.ts.map +1 -0
- package/dist/types/src/session/format.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +1 -1
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +64 -8
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +6 -2
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/templates/system.d.ts +0 -1
- package/dist/types/src/templates/system.d.ts.map +1 -1
- package/dist/types/src/testing/layer.d.ts +23 -9
- package/dist/types/src/testing/layer.d.ts.map +1 -1
- package/dist/types/src/tracing.d.ts +264 -0
- package/dist/types/src/tracing.d.ts.map +1 -0
- package/dist/types/src/types/artifact.d.ts +5 -5
- package/dist/types/src/types/artifact.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -33
- package/src/conversation/context.test.ts +19 -13
- package/src/conversation/context.ts +150 -50
- package/src/conversation/conversation.test.ts +25 -32
- package/src/conversation/conversation.ts +197 -47
- package/src/diff.test.ts +36 -5
- package/src/diff.ts +13 -4
- package/src/execution-graph.test.ts +2 -2
- package/src/execution-graph.ts +3 -3
- package/src/extraction/extraction-ner-function/index.ts +0 -1
- package/src/extraction/extraction.ts +38 -10
- package/src/extraction/index.ts +0 -1
- package/src/functions/services.ts +199 -50
- package/src/functions.conversations.json +1 -1
- package/src/functions.test.ts +28 -20
- package/src/index.ts +2 -0
- package/src/service/AgentService.conversations.json +1 -0
- package/src/service/AgentService.test.ts +169 -0
- package/src/service/AgentService.ts +165 -0
- package/src/service/agent-process.ts +359 -0
- package/src/service/index.ts +6 -0
- package/src/session/format.test.ts +23 -15
- package/src/session/format.ts +0 -1
- package/src/session/index.ts +1 -2
- package/src/session/observer.ts +3 -2
- package/src/session/session.conversations.json +1 -1
- package/src/session/session.test.ts +52 -5
- package/src/session/session.ts +232 -117
- package/src/session/toolkit.ts +12 -7
- package/src/templates/instructions/format.tpl +0 -4
- package/src/templates/instructions/system.tpl +39 -2
- package/src/templates/system.ts +6 -5
- package/src/testing/layer.ts +205 -24
- package/src/tracing.ts +113 -0
- package/src/types/artifact.ts +17 -10
- package/dist/lib/browser/chunk-7CDDG6LL.mjs +0 -578
- package/dist/lib/browser/chunk-7CDDG6LL.mjs.map +0 -7
- package/dist/lib/browser/extraction/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs +0 -32
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CC7ER6Q3.mjs +0 -579
- package/dist/lib/node-esm/chunk-CC7ER6Q3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/extraction/index.mjs +0 -226
- package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -851
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -33
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +0 -4
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts +0 -2
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-llm-function/index.d.ts +0 -2
- package/dist/types/src/extraction/extraction-llm-function/index.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +0 -347
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts +0 -2
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts.map +0 -1
- package/dist/types/src/session/GenericToolkit.d.ts +0 -85
- package/dist/types/src/session/GenericToolkit.d.ts.map +0 -1
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -84
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -62
- package/src/extraction/extraction-llm-function/index.ts +0 -5
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +0 -116
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +0 -56
- package/src/session/GenericToolkit.ts +0 -131
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Tool from '@effect/ai/Tool';
|
|
6
6
|
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
|
-
import { describe, it } from '@effect/vitest';
|
|
7
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
8
8
|
import * as Effect from 'effect/Effect';
|
|
9
9
|
import * as Layer from 'effect/Layer';
|
|
10
10
|
import * as Schema from 'effect/Schema';
|
|
@@ -12,9 +12,9 @@ import * as Schema from 'effect/Schema';
|
|
|
12
12
|
import { Obj, Type } from '@dxos/echo';
|
|
13
13
|
import { TestHelpers } from '@dxos/effect/testing';
|
|
14
14
|
import { log } from '@dxos/log';
|
|
15
|
+
import { Message } from '@dxos/types';
|
|
15
16
|
|
|
16
17
|
import { AssistantTestLayer } from '../testing';
|
|
17
|
-
|
|
18
18
|
import { AiSession } from './session';
|
|
19
19
|
|
|
20
20
|
// Define a calendar event artifact schema.
|
|
@@ -25,7 +25,7 @@ const CalendarEventSchema = Schema.Struct({
|
|
|
25
25
|
description: Schema.String,
|
|
26
26
|
}).pipe(
|
|
27
27
|
Type.object({
|
|
28
|
-
typename: 'example.
|
|
28
|
+
typename: 'com.example.type.calendar-event',
|
|
29
29
|
version: '0.1.0',
|
|
30
30
|
}),
|
|
31
31
|
);
|
|
@@ -67,6 +67,7 @@ const toolkitLayer = TestToolkit.toLayer({
|
|
|
67
67
|
const TestLayer = Layer.mergeAll(
|
|
68
68
|
AssistantTestLayer({
|
|
69
69
|
types: [CalendarEventSchema],
|
|
70
|
+
tracing: 'pretty',
|
|
70
71
|
}),
|
|
71
72
|
toolkitLayer,
|
|
72
73
|
);
|
|
@@ -76,7 +77,7 @@ describe('AiSession', () => {
|
|
|
76
77
|
'no tools',
|
|
77
78
|
Effect.fnUntraced(
|
|
78
79
|
function* (_) {
|
|
79
|
-
const session = new AiSession(
|
|
80
|
+
const session = new AiSession();
|
|
80
81
|
const response = yield* session.run({
|
|
81
82
|
prompt: 'Hello world!',
|
|
82
83
|
history: [],
|
|
@@ -92,7 +93,7 @@ describe('AiSession', () => {
|
|
|
92
93
|
'calculator',
|
|
93
94
|
Effect.fnUntraced(
|
|
94
95
|
function* (_) {
|
|
95
|
-
const session = new AiSession(
|
|
96
|
+
const session = new AiSession();
|
|
96
97
|
const toolkit = yield* TestToolkit;
|
|
97
98
|
const response = yield* session.run({
|
|
98
99
|
toolkit,
|
|
@@ -105,6 +106,52 @@ describe('AiSession', () => {
|
|
|
105
106
|
TestHelpers.provideTestContext,
|
|
106
107
|
),
|
|
107
108
|
);
|
|
109
|
+
|
|
110
|
+
it.effect(
|
|
111
|
+
'tool schema error',
|
|
112
|
+
Effect.fnUntraced(
|
|
113
|
+
function* (_) {
|
|
114
|
+
const session = new AiSession();
|
|
115
|
+
const toolkit = yield* TestToolkit;
|
|
116
|
+
const response = yield* session.run({
|
|
117
|
+
toolkit,
|
|
118
|
+
prompt:
|
|
119
|
+
'I am testing error handling in tool paramter parsing. I want you to call the calculator tool but omit the input parameter to intentionally differ from the tool schema.',
|
|
120
|
+
history: [],
|
|
121
|
+
});
|
|
122
|
+
log.info('response', { response });
|
|
123
|
+
},
|
|
124
|
+
Effect.provide(TestLayer),
|
|
125
|
+
TestHelpers.provideTestContext,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
it.effect(
|
|
130
|
+
'summarization',
|
|
131
|
+
Effect.fnUntraced(
|
|
132
|
+
function* (_) {
|
|
133
|
+
const session = new AiSession({ summarizationThreshold: 0 }); // Force summarization.
|
|
134
|
+
const response = yield* session.run({
|
|
135
|
+
prompt: 'What did we talk about?',
|
|
136
|
+
history: [
|
|
137
|
+
Obj.make(Message.Message, {
|
|
138
|
+
created: '2024-01-01T10:00:00Z',
|
|
139
|
+
sender: { role: 'user' },
|
|
140
|
+
blocks: [{ _tag: 'text', text: 'How many apples are in the basket?' }],
|
|
141
|
+
}),
|
|
142
|
+
Obj.make(Message.Message, {
|
|
143
|
+
created: '2024-01-01T11:00:00Z',
|
|
144
|
+
sender: { role: 'assistant' },
|
|
145
|
+
blocks: [{ _tag: 'text', text: 'There are 10 apples in the basket.' }],
|
|
146
|
+
}),
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
expect(response).toBeDefined();
|
|
150
|
+
},
|
|
151
|
+
Effect.provide(TestLayer),
|
|
152
|
+
TestHelpers.provideTestContext,
|
|
153
|
+
),
|
|
154
|
+
);
|
|
108
155
|
});
|
|
109
156
|
|
|
110
157
|
// Travel to rome, florence, livorno, siena, madrid for conferences
|
package/src/session/session.ts
CHANGED
|
@@ -6,30 +6,32 @@ import type * as AiError from '@effect/ai/AiError';
|
|
|
6
6
|
import * as LanguageModel from '@effect/ai/LanguageModel';
|
|
7
7
|
import type * as Tool from '@effect/ai/Tool';
|
|
8
8
|
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
9
|
+
import * as Array from 'effect/Array';
|
|
9
10
|
import * as Chunk from 'effect/Chunk';
|
|
10
11
|
import * as Effect from 'effect/Effect';
|
|
11
|
-
import
|
|
12
|
+
import { pipe } from 'effect/Function';
|
|
13
|
+
import * as Option from 'effect/Option';
|
|
12
14
|
import * as Stream from 'effect/Stream';
|
|
13
15
|
|
|
14
16
|
import {
|
|
15
17
|
AiParser,
|
|
16
18
|
AiPreprocessor,
|
|
19
|
+
AiSummarizer,
|
|
17
20
|
type AiToolNotFoundError,
|
|
21
|
+
callTool,
|
|
18
22
|
type PromptPreprocessingError,
|
|
19
23
|
type ToolExecutionService,
|
|
20
24
|
type ToolResolverService,
|
|
21
|
-
|
|
22
|
-
getToolCalls,
|
|
25
|
+
withoutToolCallParising,
|
|
23
26
|
} from '@dxos/ai';
|
|
24
27
|
import { type Blueprint } from '@dxos/blueprints';
|
|
25
|
-
import { todo } from '@dxos/debug';
|
|
26
28
|
import { Obj } from '@dxos/echo';
|
|
27
|
-
import { type FunctionInvocationService, TracingService } from '@dxos/functions';
|
|
29
|
+
import { type FunctionInvocationService, Trace, TracingService } from '@dxos/functions';
|
|
28
30
|
import { log } from '@dxos/log';
|
|
29
|
-
import { Message } from '@dxos/types';
|
|
31
|
+
import { ContentBlock, Message } from '@dxos/types';
|
|
30
32
|
|
|
31
33
|
import { type AiAssistantError } from '../errors';
|
|
32
|
-
|
|
34
|
+
import { CompleteBlock, PartialBlock } from '../tracing';
|
|
33
35
|
import { formatSystemPrompt, formatUserPrompt } from './format';
|
|
34
36
|
import { GenerationObserver } from './observer';
|
|
35
37
|
|
|
@@ -39,10 +41,28 @@ export type AiSessionRunRequirements =
|
|
|
39
41
|
| LanguageModel.LanguageModel
|
|
40
42
|
| ToolExecutionService
|
|
41
43
|
| ToolResolverService
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
+
| FunctionInvocationService
|
|
45
|
+
| Trace.TraceService
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Retained for backward compatibility with tool handlers that use TracingService.emitStatus().
|
|
48
|
+
* New code should use Trace.TraceService instead.
|
|
49
|
+
*/
|
|
50
|
+
| TracingService;
|
|
44
51
|
|
|
45
|
-
export type AiSessionOptions = {
|
|
52
|
+
export type AiSessionOptions = {
|
|
53
|
+
/**
|
|
54
|
+
* Summarize before executing the prompt if the existing history exceeds this threshold.
|
|
55
|
+
*/
|
|
56
|
+
summarizationThreshold?: number;
|
|
57
|
+
|
|
58
|
+
// TODO(dmaretskyi): Plan to phase out in favor of TracingService and the return type being a stream.
|
|
59
|
+
observer?: GenerationObserver;
|
|
60
|
+
/**
|
|
61
|
+
* Callback for when a message is received from the user, model, or tool.
|
|
62
|
+
* This is useful for streaming the output to a queue.
|
|
63
|
+
*/
|
|
64
|
+
onOutput?: (message: Message.Message) => Effect.Effect<void, never, never>;
|
|
65
|
+
};
|
|
46
66
|
|
|
47
67
|
export type AiSessionRunProps<Tools extends Record<string, Tool.Any>> = {
|
|
48
68
|
prompt: string;
|
|
@@ -52,7 +72,24 @@ export type AiSessionRunProps<Tools extends Record<string, Tool.Any>> = {
|
|
|
52
72
|
objects?: Obj.Unknown[];
|
|
53
73
|
blueprints?: readonly Blueprint.Blueprint[];
|
|
54
74
|
toolkit?: Toolkit.WithHandler<Tools>;
|
|
55
|
-
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type AiSessionBeginProps = {
|
|
78
|
+
prompt: string;
|
|
79
|
+
system?: string;
|
|
80
|
+
history?: Message.Message[];
|
|
81
|
+
objects?: Obj.Unknown[];
|
|
82
|
+
blueprints?: readonly Blueprint.Blueprint[];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type AiSessionTurnProps<Tools extends Record<string, Tool.Any>> = {
|
|
86
|
+
system: string;
|
|
87
|
+
toolkit?: Toolkit.WithHandler<Tools>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type AiSessionTurnResult = {
|
|
91
|
+
messages: Message.Message[];
|
|
92
|
+
done: boolean;
|
|
56
93
|
};
|
|
57
94
|
|
|
58
95
|
/**
|
|
@@ -65,10 +102,14 @@ export type AiSessionRunProps<Tools extends Record<string, Tool.Any>> = {
|
|
|
65
102
|
* Could be run locally in the app or remotely.
|
|
66
103
|
* Could be personal or shared.
|
|
67
104
|
*/
|
|
105
|
+
// TODO(dmaretskyi): Rename AiSessionRequest
|
|
68
106
|
export class AiSession {
|
|
69
107
|
/** Prevents concurrent execution of session. */
|
|
70
108
|
private readonly _semaphore = Effect.runSync(Effect.makeSemaphore(1));
|
|
71
109
|
|
|
110
|
+
private readonly _observer: GenerationObserver;
|
|
111
|
+
private readonly _onOutput: (message: Message.Message) => Effect.Effect<void, never, never>;
|
|
112
|
+
|
|
72
113
|
/** Message history from queue. */
|
|
73
114
|
// TODO(burdon): Evolve into supporting a git-like graph of messages.
|
|
74
115
|
private _history: Message.Message[] = [];
|
|
@@ -80,7 +121,10 @@ export class AiSession {
|
|
|
80
121
|
private _ended = 0;
|
|
81
122
|
private _toolCalls = 0;
|
|
82
123
|
|
|
83
|
-
constructor(private readonly _options: AiSessionOptions = {}) {
|
|
124
|
+
constructor(private readonly _options: AiSessionOptions = {}) {
|
|
125
|
+
this._observer = _options.observer ?? GenerationObserver.noop();
|
|
126
|
+
this._onOutput = _options.onOutput ?? (() => Effect.void);
|
|
127
|
+
}
|
|
84
128
|
|
|
85
129
|
get duration(): number {
|
|
86
130
|
return this._ended - this._started;
|
|
@@ -90,135 +134,206 @@ export class AiSession {
|
|
|
90
134
|
return this._toolCalls;
|
|
91
135
|
}
|
|
92
136
|
|
|
93
|
-
|
|
137
|
+
get pending(): readonly Message.Message[] {
|
|
138
|
+
return this._pending;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private _submitMessage = (message: Message.Message): Effect.Effect<Message.Message, never, Trace.TraceService> =>
|
|
142
|
+
Effect.gen(this, function* () {
|
|
143
|
+
this._pending.push(message);
|
|
144
|
+
yield* this._observer.onMessage(message);
|
|
145
|
+
for (const block of message.blocks) {
|
|
146
|
+
yield* Trace.write(CompleteBlock, {
|
|
147
|
+
messageId: message.id,
|
|
148
|
+
role: message.sender.role!,
|
|
149
|
+
block,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
yield* this._onOutput(message);
|
|
153
|
+
return message;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
getToolCalls = () =>
|
|
157
|
+
pipe(
|
|
158
|
+
[...this._history, ...this._pending],
|
|
159
|
+
Array.reverse,
|
|
160
|
+
Array.takeWhile((_) => _.sender.role === 'assistant'),
|
|
161
|
+
Array.flatMap((_) => _.blocks.filter(ContentBlock.is('toolCall')).map((block) => ({ block, message: _ }))),
|
|
162
|
+
Array.filter((_) => !_.block.providerExecuted),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Initialize a session: set up history, perform summarization if needed, and submit the user prompt.
|
|
167
|
+
* Must be called before `runTurn()`.
|
|
168
|
+
*/
|
|
169
|
+
begin = ({
|
|
94
170
|
prompt,
|
|
95
|
-
system
|
|
171
|
+
system,
|
|
96
172
|
history = [],
|
|
97
|
-
objects = [],
|
|
98
173
|
blueprints = [],
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}: AiSessionRunProps<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
|
|
174
|
+
objects = [],
|
|
175
|
+
}: AiSessionBeginProps): Effect.Effect<void, AiSessionRunError, AiSessionRunRequirements> =>
|
|
102
176
|
Effect.gen(this, function* () {
|
|
103
177
|
this._started = Date.now();
|
|
104
178
|
this._history = [...history];
|
|
105
179
|
this._pending = [];
|
|
106
|
-
const pending = this._pending;
|
|
107
180
|
|
|
108
|
-
const
|
|
109
|
-
pending.push(message);
|
|
110
|
-
yield* observer.onMessage(message);
|
|
111
|
-
yield* TracingService.emitConverationMessage(message);
|
|
112
|
-
return message;
|
|
113
|
-
});
|
|
181
|
+
const systemPrompt = yield* formatSystemPrompt({ system, blueprints, objects }).pipe(Effect.orDie);
|
|
114
182
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
183
|
+
if (this._options.summarizationThreshold !== undefined) {
|
|
184
|
+
const tokenCount = yield* AiPreprocessor.estimateTokens(
|
|
185
|
+
yield* AiPreprocessor.preprocessPrompt([...this._history], {
|
|
186
|
+
system: systemPrompt,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
if (tokenCount > this._options.summarizationThreshold) {
|
|
190
|
+
const summary = yield* AiSummarizer.summarize([...this._history]);
|
|
191
|
+
yield* this._submitMessage(summary);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
118
194
|
|
|
119
|
-
|
|
120
|
-
|
|
195
|
+
yield* this._submitMessage(yield* formatUserPrompt({ prompt, history }));
|
|
196
|
+
});
|
|
121
197
|
|
|
122
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Execute a single turn: one LLM generation followed by tool execution.
|
|
200
|
+
* The toolkit and system prompt can be updated between turns to reflect context changes (e.g. dynamically enabled blueprints).
|
|
201
|
+
*/
|
|
202
|
+
runAgentTurn = <Tools extends Record<string, Tool.Any>>({
|
|
203
|
+
system,
|
|
204
|
+
toolkit,
|
|
205
|
+
}: AiSessionTurnProps<Tools>): Effect.Effect<AiSessionTurnResult, AiSessionRunError, AiSessionRunRequirements> =>
|
|
206
|
+
Effect.gen(this, function* () {
|
|
207
|
+
log('request', {
|
|
208
|
+
system: { snippet: createSnippet(system), length: system.length },
|
|
209
|
+
pending: this._pending.length,
|
|
210
|
+
history: this._history.length,
|
|
211
|
+
});
|
|
123
212
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
system: { snippet: createSnippet(system), length: system.length },
|
|
129
|
-
pending: this._pending.length,
|
|
130
|
-
history: this._history.length,
|
|
131
|
-
objects: objects?.length ?? 0,
|
|
132
|
-
});
|
|
213
|
+
const prompt = yield* AiPreprocessor.preprocessPrompt([...this._history, ...this._pending], {
|
|
214
|
+
system,
|
|
215
|
+
cacheControl: 'ephemeral',
|
|
216
|
+
});
|
|
133
217
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
218
|
+
const observer = this._observer;
|
|
219
|
+
let currentMessageId: Obj.ID | null = null; // Consistent IDs for pending blocks preceding the complete one.
|
|
220
|
+
|
|
221
|
+
const messages = yield* LanguageModel.streamText({
|
|
222
|
+
prompt,
|
|
223
|
+
toolkit,
|
|
224
|
+
disableToolCallResolution: true,
|
|
225
|
+
}).pipe(
|
|
226
|
+
withoutToolCallParising,
|
|
227
|
+
AiParser.parseResponse({
|
|
228
|
+
emitPartial: true,
|
|
229
|
+
onBegin: () => observer.onBegin(),
|
|
230
|
+
onBlock: (block) => observer.onBlock(block),
|
|
231
|
+
onPart: (part) => observer.onPart(part as any),
|
|
232
|
+
onEnd: (summary) => observer.onEnd(summary),
|
|
233
|
+
}),
|
|
234
|
+
Stream.mapEffect(
|
|
235
|
+
(block) =>
|
|
236
|
+
Effect.gen(this, function* () {
|
|
237
|
+
if (block.pending) {
|
|
238
|
+
currentMessageId ??= Obj.ID.random();
|
|
239
|
+
log('emit ephemeral message', { id: currentMessageId, type: block._tag });
|
|
240
|
+
yield* Trace.write(PartialBlock, {
|
|
241
|
+
messageId: currentMessageId,
|
|
242
|
+
role: 'assistant',
|
|
243
|
+
block,
|
|
244
|
+
});
|
|
245
|
+
return Option.none();
|
|
246
|
+
} else {
|
|
247
|
+
currentMessageId ??= Obj.ID.random();
|
|
248
|
+
const id = currentMessageId;
|
|
249
|
+
currentMessageId = null;
|
|
250
|
+
log('emit complete message', { id, type: block._tag });
|
|
251
|
+
const message = Obj.make(Message.Message, {
|
|
252
|
+
id,
|
|
253
|
+
created: new Date().toISOString(),
|
|
254
|
+
sender: { role: 'assistant' },
|
|
255
|
+
blocks: [block],
|
|
256
|
+
});
|
|
257
|
+
return Option.some(yield* this._submitMessage(message));
|
|
258
|
+
}
|
|
259
|
+
}),
|
|
260
|
+
{ concurrency: 1, unordered: false },
|
|
261
|
+
),
|
|
262
|
+
Stream.filterMap((_) => _),
|
|
263
|
+
Stream.runCollect,
|
|
264
|
+
Effect.map(Chunk.toArray),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const toolCalls = this.getToolCalls();
|
|
268
|
+
|
|
269
|
+
if (toolCalls.length === 0) {
|
|
270
|
+
this._ended = Date.now();
|
|
271
|
+
return { messages, done: true };
|
|
272
|
+
} else if (!toolkit) {
|
|
273
|
+
throw new Error('No toolkit provided');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { messages, done: false };
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
runTools = <Tools extends Record<string, Tool.Any>>({ toolkit }: { toolkit?: Toolkit.WithHandler<Tools> }) =>
|
|
280
|
+
Effect.gen(this, function* () {
|
|
281
|
+
const toolCalls = this.getToolCalls();
|
|
282
|
+
// TODO(burdon): Retry errors? Write result when each completes individually?
|
|
283
|
+
const toolResults = yield* Effect.forEach(toolCalls, ({ block, message }) => {
|
|
284
|
+
if (!toolkit) {
|
|
285
|
+
throw new Error('No toolkit provided');
|
|
286
|
+
}
|
|
287
|
+
return callTool(toolkit, block).pipe(
|
|
288
|
+
Effect.provide(
|
|
289
|
+
TracingService.layerSubframe((context) => ({
|
|
290
|
+
...context,
|
|
291
|
+
parentMessage: message.id,
|
|
292
|
+
toolCallId: block.toolCallId,
|
|
293
|
+
})),
|
|
294
|
+
),
|
|
152
295
|
);
|
|
296
|
+
});
|
|
153
297
|
|
|
154
|
-
|
|
298
|
+
// TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
|
|
299
|
+
// TODO(dmaretskyi): Stream tool results one by one.
|
|
300
|
+
yield* this._submitMessage(
|
|
301
|
+
Obj.make(Message.Message, {
|
|
302
|
+
created: new Date().toISOString(),
|
|
303
|
+
sender: { role: 'tool' },
|
|
304
|
+
blocks: toolResults,
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
this._toolCalls += toolResults.length;
|
|
309
|
+
});
|
|
310
|
+
/**
|
|
311
|
+
* Run a full conversation turn loop. Equivalent to calling `begin()` then `runTurn()` in a loop.
|
|
312
|
+
*/
|
|
313
|
+
run = <Tools extends Record<string, Tool.Any>>({
|
|
314
|
+
prompt,
|
|
315
|
+
system: systemTemplate,
|
|
316
|
+
history = [],
|
|
317
|
+
objects = [],
|
|
318
|
+
blueprints = [],
|
|
319
|
+
toolkit,
|
|
320
|
+
}: AiSessionRunProps<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
|
|
321
|
+
Effect.gen(this, function* () {
|
|
322
|
+
yield* this.begin({ prompt, system: systemTemplate, history, objects, blueprints });
|
|
155
323
|
|
|
156
|
-
|
|
157
|
-
const response = yield* submitMessage(
|
|
158
|
-
Obj.make(Message.Message, {
|
|
159
|
-
created: new Date().toISOString(),
|
|
160
|
-
sender: { role: 'assistant' },
|
|
161
|
-
blocks,
|
|
162
|
-
}),
|
|
163
|
-
);
|
|
324
|
+
const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects }).pipe(Effect.orDie);
|
|
164
325
|
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
326
|
+
do {
|
|
327
|
+
const { done } = yield* this.runAgentTurn({ system, toolkit });
|
|
328
|
+
if (done) {
|
|
168
329
|
break;
|
|
169
|
-
} else if (!toolkit) {
|
|
170
|
-
throw new Error('No toolkit provided'); // TODO(burdon): Throw user error?
|
|
171
330
|
}
|
|
172
|
-
|
|
173
|
-
// Execute the tool calls.
|
|
174
|
-
// TODO(burdon): Retry errors? Write result when each completes individually?
|
|
175
|
-
const toolResults = yield* Effect.forEach(toolCalls, (toolCall) => {
|
|
176
|
-
return callTool(toolkit, toolCall).pipe(
|
|
177
|
-
Effect.provide(
|
|
178
|
-
TracingService.layerSubframe((context) => ({
|
|
179
|
-
...context,
|
|
180
|
-
parentMessage: response.id,
|
|
181
|
-
toolCallId: toolCall.toolCallId,
|
|
182
|
-
})),
|
|
183
|
-
),
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// Add to queue and continue loop.
|
|
188
|
-
// TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
|
|
189
|
-
// This results in a broken execution graph.
|
|
190
|
-
yield* submitMessage(
|
|
191
|
-
Obj.make(Message.Message, {
|
|
192
|
-
created: new Date().toISOString(),
|
|
193
|
-
sender: { role: 'tool' },
|
|
194
|
-
blocks: toolResults,
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
this._toolCalls++;
|
|
331
|
+
yield* this.runTools({ toolkit });
|
|
199
332
|
} while (true);
|
|
200
333
|
|
|
201
|
-
this._ended = Date.now();
|
|
202
334
|
log('done', { pending: this._pending.length, duration: this.duration, tools: this._toolCalls });
|
|
203
335
|
return this._pending;
|
|
204
336
|
}).pipe(this._semaphore.withPermits(1), Effect.withSpan('AiSession.run'));
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* @deprecated
|
|
208
|
-
*/
|
|
209
|
-
// TODO(burdon): Implement or remove.
|
|
210
|
-
async runStructured<S extends Schema.Schema.AnyNoContext>(
|
|
211
|
-
_schema: S,
|
|
212
|
-
_options: AiSessionRunProps<any>,
|
|
213
|
-
): Promise<Schema.Schema.Type<S>> {
|
|
214
|
-
return todo();
|
|
215
|
-
// const parser = structuredOutputParser(schema);
|
|
216
|
-
// const result = await this.run({
|
|
217
|
-
// ...options,
|
|
218
|
-
// executableTools: [...(options.executableTools ?? []), parser.tool],
|
|
219
|
-
// });
|
|
220
|
-
// return parser.getResult(result);
|
|
221
|
-
}
|
|
222
337
|
}
|
|
223
338
|
|
|
224
339
|
const createSnippet = (text: string, len = 32) =>
|
package/src/session/toolkit.ts
CHANGED
|
@@ -6,13 +6,17 @@ import type * as Tool from '@effect/ai/Tool';
|
|
|
6
6
|
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
8
|
|
|
9
|
-
import { type AiToolNotFoundError, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
9
|
+
import { type AiToolNotFoundError, GenericToolkit, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
10
10
|
import { type Blueprint } from '@dxos/blueprints';
|
|
11
11
|
import { isTruthy } from '@dxos/util';
|
|
12
12
|
|
|
13
13
|
export type CreateToolkitProps = {
|
|
14
14
|
toolkit?: Toolkit.Any;
|
|
15
15
|
blueprints?: readonly Blueprint.Blueprint[];
|
|
16
|
+
/**
|
|
17
|
+
* Self-contained with handlers toolkits.
|
|
18
|
+
*/
|
|
19
|
+
genericToolkits?: readonly GenericToolkit.GenericToolkit[];
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -21,6 +25,7 @@ export type CreateToolkitProps = {
|
|
|
21
25
|
export const createToolkit = ({
|
|
22
26
|
toolkit: toolkitProp,
|
|
23
27
|
blueprints = [],
|
|
28
|
+
genericToolkits = [],
|
|
24
29
|
}: CreateToolkitProps): Effect.Effect<
|
|
25
30
|
Toolkit.WithHandler<any>,
|
|
26
31
|
AiToolNotFoundError,
|
|
@@ -29,11 +34,11 @@ export const createToolkit = ({
|
|
|
29
34
|
Effect.gen(function* () {
|
|
30
35
|
const blueprintToolkit = yield* ToolResolverService.resolveToolkit(blueprints.flatMap(({ tools }) => tools));
|
|
31
36
|
const blueprintToolHandler = yield* blueprintToolkit.toContext(ToolExecutionService.handlersFor(blueprintToolkit));
|
|
37
|
+
const genericToolkit = GenericToolkit.merge(...genericToolkits);
|
|
32
38
|
|
|
33
|
-
const toolkit = Toolkit.merge(...[toolkitProp, blueprintToolkit].filter(isTruthy));
|
|
34
|
-
return yield* toolkit.pipe(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
>;
|
|
39
|
+
const toolkit = Toolkit.merge(...[toolkitProp, blueprintToolkit, genericToolkit.toolkit].filter(isTruthy));
|
|
40
|
+
return yield* toolkit.pipe(
|
|
41
|
+
Effect.provide(blueprintToolHandler),
|
|
42
|
+
Effect.provide(genericToolkit.layer),
|
|
43
|
+
) as any as Effect.Effect<Toolkit.WithHandler<any>, never, Tool.HandlersFor<any>>;
|
|
39
44
|
});
|
|
@@ -52,10 +52,6 @@ Do not use bullets when listing suggestions.
|
|
|
52
52
|
You may ask multiple choice questions by wrapping consise options inside <option> tags inside a well formed <select> tag.
|
|
53
53
|
Example: <select><option>Yes</option><option>No</option></select>
|
|
54
54
|
|
|
55
|
-
## Toolkit
|
|
56
|
-
|
|
57
|
-
If the user asks for a list of tools, then just emit a single self-closing <toolkit/> tag instead of listing the tools.
|
|
58
|
-
|
|
59
55
|
## Context
|
|
60
56
|
|
|
61
57
|
The current date and time is {{DATETIME}}.
|
|
@@ -43,6 +43,43 @@
|
|
|
43
43
|
- Suggested actions could include actions that create artifacts.
|
|
44
44
|
- After creating an artifact use a tool to add it to the chat context.
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Navigation
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
- When the user asks to open, go to, navigate to, or show a specific document, page, or object, use the navigation tools instead of loading content inline.
|
|
49
|
+
- First, call the Resolve navigation targets tool to find the navigation path for the requested item.
|
|
50
|
+
- Without arguments, it returns available pages that can be navigated to.
|
|
51
|
+
- With a DXN in the query, it resolves that specific object to a navigation path.
|
|
52
|
+
- If you know the object's DXN (e.g. from a database query or context objects), pass it in the query to get the exact navigation path.
|
|
53
|
+
- Then, call the Open tool with the resolved navigation path(s) in the subject array to navigate the user to that item.
|
|
54
|
+
- Do not respond with the document's content when the user asks to open it — open it in the main content area instead.
|
|
55
|
+
- If multiple targets match, present the options to the user and let them choose which to open.
|
|
56
|
+
|
|
57
|
+
## Response format
|
|
58
|
+
|
|
59
|
+
- When updating the user about the progress of the work you are doing, put the update in a <status> XML tag.
|
|
60
|
+
- Only when you need to show the result to the user or ask a question use a text block without status tags.
|
|
61
|
+
|
|
62
|
+
<example>
|
|
63
|
+
// reasoning...
|
|
64
|
+
<status>Looking for emails from Depot</status>
|
|
65
|
+
// tool call..
|
|
66
|
+
<status>Found 2 emails from Depot.</status>
|
|
67
|
+
<status>Loading email details</status>
|
|
68
|
+
// tool call
|
|
69
|
+
<status>Located mailbox</status>
|
|
70
|
+
I found **2 emails from Depot**:
|
|
71
|
+
</example>
|
|
72
|
+
|
|
73
|
+
Do not do (no status tags):
|
|
74
|
+
|
|
75
|
+
<incorrect_example>
|
|
76
|
+
// reasoning...
|
|
77
|
+
// tool call..
|
|
78
|
+
I found 2 emails from Depot. Let me load their details:
|
|
79
|
+
// tool call
|
|
80
|
+
I found **2 emails from Depot**:
|
|
81
|
+
</incorrect_example>
|
|
82
|
+
|
|
83
|
+
- Avoid using plain text to tell the user what you are about to do; instead, use status blocks.
|
|
84
|
+
- WRONG: Let me get...
|
|
85
|
+
- CORRECT: <status>Getting...</status>
|
package/src/templates/system.ts
CHANGED
|
@@ -29,13 +29,14 @@ export const createSystemPrompt = (options: Partial<SystemPromptOptions> = {}) =
|
|
|
29
29
|
}),
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* NOTE: We are forbidden from accessing randomness at model level in cloudflare, so we have to have pre-generated ids.
|
|
34
|
+
*/
|
|
35
|
+
const SYSTEM_ID = '01K2J2FZWH39F55CS7B26GP1Q3';
|
|
36
|
+
|
|
32
37
|
export const templates = {
|
|
33
|
-
/** Editable system prompt.*/
|
|
34
38
|
system: Template.make({
|
|
35
|
-
|
|
36
|
-
* NOTE: We are forbidden from accessing randomness at model level in cloudflare, so we have to have pre-generated ids.
|
|
37
|
-
*/
|
|
38
|
-
id: '01K2J2FZWH39F55CS7B26GP1Q3',
|
|
39
|
+
id: SYSTEM_ID,
|
|
39
40
|
source: SYSTEM,
|
|
40
41
|
}),
|
|
41
42
|
};
|