@dxos/assistant 0.8.3 → 0.8.4-main.28f8d3d
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-6WRGDEMH.mjs +89 -0
- package/dist/lib/browser/chunk-6WRGDEMH.mjs.map +7 -0
- package/dist/lib/browser/chunk-NZGGCEF3.mjs +341 -0
- package/dist/lib/browser/chunk-NZGGCEF3.mjs.map +7 -0
- package/dist/lib/browser/extraction/index.mjs +269 -0
- package/dist/lib/browser/extraction/index.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +306 -2110
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/named-entity-recognition-O46UTCLR.mjs +13 -0
- package/dist/lib/browser/named-entity-recognition-O46UTCLR.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-3UU5V4FS.mjs +91 -0
- package/dist/lib/node-esm/chunk-3UU5V4FS.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-XOOPGSGW.mjs +343 -0
- package/dist/lib/node-esm/chunk-XOOPGSGW.mjs.map +7 -0
- package/dist/lib/node-esm/extraction/index.mjs +270 -0
- package/dist/lib/node-esm/extraction/index.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +305 -2110
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/named-entity-recognition-W4VMRCH6.mjs +14 -0
- package/dist/lib/node-esm/named-entity-recognition-W4VMRCH6.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +90 -0
- package/dist/types/src/conversation/context.d.ts.map +1 -0
- package/dist/types/src/conversation/conversation.d.ts +50 -0
- package/dist/types/src/conversation/conversation.d.ts.map +1 -0
- package/dist/types/src/conversation/index.d.ts +3 -0
- package/dist/types/src/conversation/index.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +39 -0
- package/dist/types/src/errors.d.ts.map +1 -0
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +151 -53
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts +1 -1
- package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +106 -44
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/functions/index.d.ts +2 -0
- package/dist/types/src/functions/index.d.ts.map +1 -0
- package/dist/types/src/functions/services.d.ts +7 -0
- package/dist/types/src/functions/services.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/session/artifact-diff.d.ts +27 -0
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -0
- package/dist/types/src/session/format.d.ts +168 -0
- package/dist/types/src/session/format.d.ts.map +1 -0
- package/dist/types/src/session/format.test.d.ts +2 -0
- package/dist/types/src/session/format.test.d.ts.map +1 -0
- package/dist/types/src/session/index.d.ts +1 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +125 -219
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +4 -4
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/templates/index.d.ts +2 -0
- package/dist/types/src/templates/index.d.ts.map +1 -0
- package/dist/types/src/templates/system.d.ts +22 -0
- package/dist/types/src/templates/system.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +13 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +46 -31
- package/src/conversation/context.ts +133 -0
- package/src/conversation/conversation.ts +125 -0
- package/src/conversation/index.ts +6 -0
- package/src/errors.ts +10 -0
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +7 -18
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +23 -17
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +4 -3
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +7 -4
- package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +2 -2
- package/src/extraction/extraction-ner-function/named-entity-recognition.ts +1 -1
- package/src/extraction/quotes.test.ts +1 -1
- package/src/extraction/quotes.ts +1 -1
- package/src/functions/index.ts +5 -0
- package/src/functions/services.ts +114 -0
- package/src/index.ts +4 -3
- package/src/session/__snapshots__/session.test.ts.snap +32 -0
- package/src/session/artifact-diff.ts +37 -0
- package/src/session/format.test.ts +60 -0
- package/src/session/format.ts +139 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.test.ts +95 -19
- package/src/session/session.ts +224 -494
- package/src/session/version-pin.test.ts +2 -21
- package/src/session/version-pin.ts +9 -9
- package/src/summarization/summarization.test.ts +82 -106
- package/src/templates/format.tpl +55 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/system.tpl +47 -0
- package/src/templates/system.ts +32 -0
- package/src/types.ts +45 -1
- package/src/typings.d.ts +5 -0
- package/dist/lib/node/index.cjs +0 -2178
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/types/bin/chat.d.ts +0 -2
- package/dist/types/bin/chat.d.ts.map +0 -1
- package/dist/types/src/blueprint/ai.test.d.ts +0 -2
- package/dist/types/src/blueprint/ai.test.d.ts.map +0 -1
- package/dist/types/src/blueprint/blueprint.d.ts +0 -55
- package/dist/types/src/blueprint/blueprint.d.ts.map +0 -1
- package/dist/types/src/blueprint/blueprint.test.d.ts +0 -2
- package/dist/types/src/blueprint/blueprint.test.d.ts.map +0 -1
- package/dist/types/src/blueprint/index.d.ts +0 -4
- package/dist/types/src/blueprint/index.d.ts.map +0 -1
- package/dist/types/src/blueprint/logger.d.ts +0 -25
- package/dist/types/src/blueprint/logger.d.ts.map +0 -1
- package/dist/types/src/blueprint/machine.d.ts +0 -143
- package/dist/types/src/blueprint/machine.d.ts.map +0 -1
- package/dist/types/src/blueprint/test-data.d.ts +0 -3
- package/dist/types/src/blueprint/test-data.d.ts.map +0 -1
- package/dist/types/src/research/exa.d.ts +0 -7
- package/dist/types/src/research/exa.d.ts.map +0 -1
- package/dist/types/src/research/graph.d.ts +0 -41
- package/dist/types/src/research/graph.d.ts.map +0 -1
- package/dist/types/src/research/graph.test.d.ts +0 -2
- package/dist/types/src/research/graph.test.d.ts.map +0 -1
- package/dist/types/src/research/index.d.ts +0 -4
- package/dist/types/src/research/index.d.ts.map +0 -1
- package/dist/types/src/research/research.d.ts +0 -10
- package/dist/types/src/research/research.d.ts.map +0 -1
- package/dist/types/src/research/research.test.d.ts +0 -2
- package/dist/types/src/research/research.test.d.ts.map +0 -1
- package/dist/types/src/testing/exa-search-1748337321991.d.ts +0 -38
- package/dist/types/src/testing/exa-search-1748337321991.d.ts.map +0 -1
- package/dist/types/src/testing/exa-search-1748337331526.d.ts +0 -37
- package/dist/types/src/testing/exa-search-1748337331526.d.ts.map +0 -1
- package/dist/types/src/testing/exa-search-1748337344119.d.ts +0 -58
- package/dist/types/src/testing/exa-search-1748337344119.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -3
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/src/blueprint/ai.test.ts +0 -156
- package/src/blueprint/blueprint.test.ts +0 -151
- package/src/blueprint/blueprint.ts +0 -75
- package/src/blueprint/index.ts +0 -7
- package/src/blueprint/logger.ts +0 -155
- package/src/blueprint/machine.ts +0 -251
- package/src/blueprint/test-data.ts +0 -55
- package/src/research/exa.ts +0 -100
- package/src/research/graph.test.ts +0 -15
- package/src/research/graph.ts +0 -337
- package/src/research/index.ts +0 -7
- package/src/research/instructions-research.tpl +0 -97
- package/src/research/research.test.ts +0 -190
- package/src/research/research.ts +0 -89
- package/src/testing/exa-search-1748337321991.ts +0 -131
- package/src/testing/exa-search-1748337331526.ts +0 -144
- package/src/testing/exa-search-1748337344119.ts +0 -133
- package/src/testing/index.ts +0 -11
- package/src/typedefs.d.ts +0 -8
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { AiTool, type AiToolkit } from '@effect/ai';
|
|
6
|
+
import { Context, Effect, Layer, Match, Predicate, Record, Schema } from 'effect';
|
|
7
|
+
|
|
8
|
+
import { AiToolNotFoundError, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
9
|
+
import { type FunctionDefinition, LocalFunctionExecutionService } from '@dxos/functions';
|
|
10
|
+
import { invariant } from '@dxos/invariant';
|
|
11
|
+
|
|
12
|
+
export const makeToolResolverFromFunctions = (
|
|
13
|
+
functions: FunctionDefinition<any, any>[],
|
|
14
|
+
toolkit: AiToolkit.Any,
|
|
15
|
+
): Layer.Layer<ToolResolverService> => {
|
|
16
|
+
return Layer.succeed(ToolResolverService, {
|
|
17
|
+
resolve: Effect.fn('resolveTool')(function* (id) {
|
|
18
|
+
const tool = toolkit.tools[id];
|
|
19
|
+
if (tool) {
|
|
20
|
+
return tool;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fn = functions.find((f) => f.name === id);
|
|
24
|
+
if (!fn) {
|
|
25
|
+
return yield* Effect.fail(new AiToolNotFoundError(id));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return projectFunctionToTool(fn);
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const makeToolExecutionServiceFromFunctions = (
|
|
34
|
+
functions: FunctionDefinition<any, any>[],
|
|
35
|
+
toolkit: AiToolkit.AiToolkit<AiTool.Any>,
|
|
36
|
+
handlersLayer: Layer.Layer<AiTool.ToHandler<AiTool.AiTool<any>>, never, never>,
|
|
37
|
+
): Layer.Layer<ToolExecutionService, never, LocalFunctionExecutionService> => {
|
|
38
|
+
return Layer.effect(
|
|
39
|
+
ToolExecutionService,
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const toolkitHandler = yield* toolkit.pipe(Effect.provide(handlersLayer));
|
|
42
|
+
|
|
43
|
+
const localFunctionExecutionService = yield* LocalFunctionExecutionService;
|
|
44
|
+
return {
|
|
45
|
+
handlersFor: (toolkit) => {
|
|
46
|
+
const makeHandler = (tool: AiTool.Any): ((params: unknown) => Effect.Effect<unknown, any, any>) => {
|
|
47
|
+
return Effect.fn('toolFunctionHandler')(function* (input: any) {
|
|
48
|
+
if (toolkitHandler.tools.find((t: AiTool.Any) => t.name === tool.name)) {
|
|
49
|
+
// TODO(wittjosiah): Everything is `never` here.
|
|
50
|
+
return yield* (toolkitHandler.handle as any)(tool.name, input);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { functionName } = Context.get(FunctionToolAnnotation)(tool.annotations as any);
|
|
54
|
+
const fnDef = functions.find((f) => f.name === functionName);
|
|
55
|
+
if (!fnDef) {
|
|
56
|
+
return yield* Effect.fail(new AiToolNotFoundError(tool.name));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return yield* localFunctionExecutionService
|
|
60
|
+
.invokeFunction(fnDef, input)
|
|
61
|
+
.pipe(Effect.catchAllDefect((defect) => Effect.fail(defect)));
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return toolkit.of(Record.map(toolkit.tools, (tool, _name) => makeHandler(tool)) as any) as any;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Tools that are projected from functions have this annotation.
|
|
74
|
+
*/
|
|
75
|
+
class FunctionToolAnnotation extends Context.Tag('@dxos/assisatnt/FunctionToolAnnotation')<
|
|
76
|
+
FunctionToolAnnotation,
|
|
77
|
+
{
|
|
78
|
+
functionName: string;
|
|
79
|
+
}
|
|
80
|
+
>() {}
|
|
81
|
+
|
|
82
|
+
const toolCache = new WeakMap<FunctionDefinition<any, any>, AiTool.Any>();
|
|
83
|
+
const projectFunctionToTool = (fn: FunctionDefinition<any, any>): AiTool.Any => {
|
|
84
|
+
if (toolCache.has(fn)) {
|
|
85
|
+
return toolCache.get(fn)!;
|
|
86
|
+
}
|
|
87
|
+
const tool = AiTool.make(makeToolName(fn.name), {
|
|
88
|
+
description: fn.description,
|
|
89
|
+
parameters: createStructFieldsFromSchema(fn.inputSchema),
|
|
90
|
+
// TODO(dmaretskyi): Include output schema.
|
|
91
|
+
failure: Schema.Any, // TODO(dmaretskyi): Better type for the failure?
|
|
92
|
+
}).annotate(FunctionToolAnnotation, { functionName: fn.name });
|
|
93
|
+
toolCache.set(fn, tool);
|
|
94
|
+
return tool;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @returns Tool name produced from function name by escaping invalid characters.
|
|
99
|
+
*/
|
|
100
|
+
const makeToolName = (name: string) => {
|
|
101
|
+
const toolName = name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
102
|
+
invariant(toolName.match(/^[a-zA-Z_][a-zA-Z0-9-_]*$/));
|
|
103
|
+
return toolName;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// TODO(dmaretskyi): Factor out.
|
|
107
|
+
const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<string, Schema.Schema<any, any>> => {
|
|
108
|
+
return Match.value(schema.ast).pipe(
|
|
109
|
+
Match.when(Predicate.isTagged('TypeLiteral'), (ast) => {
|
|
110
|
+
return Object.fromEntries(ast.propertySignatures.map((prop) => [prop.name, Schema.make(prop.type)]));
|
|
111
|
+
}),
|
|
112
|
+
Match.orElseAbsurd,
|
|
113
|
+
);
|
|
114
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
5
|
+
export * from './conversation';
|
|
6
|
+
export * from './functions';
|
|
7
7
|
export * from './session';
|
|
8
|
+
export * from './templates';
|
|
8
9
|
export * from './types';
|
|
9
|
-
export * from './
|
|
10
|
+
export * from './errors';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`AiSession > no tools 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"blocks": ReactiveArray [
|
|
7
|
+
{
|
|
8
|
+
"_tag": "text",
|
|
9
|
+
"text": "Hello world!",
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
"created": "Sun, 20 Jul 2025 10:54:08 GMT",
|
|
13
|
+
"id": "01K0KQGFTFV3GBKGEWZW67K8HK",
|
|
14
|
+
"sender": {
|
|
15
|
+
"role": "user",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"blocks": ReactiveArray [
|
|
20
|
+
{
|
|
21
|
+
"_tag": "text",
|
|
22
|
+
"text": "Hi there! How can I help you today?",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
"created": "2025-07-20T10:54:10.801Z",
|
|
26
|
+
"id": "01K0KQGHVHJ37D4YG45WG090WV",
|
|
27
|
+
"sender": {
|
|
28
|
+
"role": "assistant",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
]
|
|
32
|
+
`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Context } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { type ObjectVersion } from '@dxos/echo-db';
|
|
8
|
+
import { type ObjectId } from '@dxos/echo-schema';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves artifact ids to their versions.
|
|
12
|
+
* Used to give the model a sense of the changes to the artifacts made by users during the conversation.
|
|
13
|
+
* The artifacts versions are pinned in the history, and whenever the artifact changes in-between assistant's steps,
|
|
14
|
+
* a diff is inserted into the conversation.
|
|
15
|
+
*
|
|
16
|
+
* Can be optionally provided to the session run call.
|
|
17
|
+
*/
|
|
18
|
+
// TODO(burdon): Move.
|
|
19
|
+
// TODO(dmaretskyi): Convert to Context.Reference
|
|
20
|
+
export class ArtifactDiffResolver extends Context.Tag('@dxos/assistant/ArtifactDiffResolver')<
|
|
21
|
+
ArtifactDiffResolver,
|
|
22
|
+
ArtifactDiffResolver.Service
|
|
23
|
+
>() {}
|
|
24
|
+
|
|
25
|
+
export namespace ArtifactDiffResolver {
|
|
26
|
+
export type Service = {
|
|
27
|
+
resolve: (artifacts: { id: ObjectId; lastVersion: ObjectVersion }[]) => Promise<
|
|
28
|
+
Map<
|
|
29
|
+
ObjectId,
|
|
30
|
+
{
|
|
31
|
+
version: ObjectVersion;
|
|
32
|
+
diff?: string;
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { beforeAll, describe, it } from '@effect/vitest';
|
|
6
|
+
import { Effect } from 'effect';
|
|
7
|
+
|
|
8
|
+
import { Blueprint, Template } from '@dxos/blueprints';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { type EchoDatabase } from '@dxos/echo-db';
|
|
11
|
+
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
12
|
+
import { DataType } from '@dxos/schema';
|
|
13
|
+
import { trim } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { createSystemPrompt } from '../templates/system';
|
|
16
|
+
|
|
17
|
+
import { formatSystemPrompt } from './format';
|
|
18
|
+
|
|
19
|
+
describe('format', () => {
|
|
20
|
+
let db: EchoDatabase;
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
const builder = await new EchoTestBuilder().open();
|
|
23
|
+
({ db } = await builder.createDatabase({
|
|
24
|
+
types: [DataType.Text, DataType.Organization, Blueprint.Blueprint],
|
|
25
|
+
}));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it.effect(
|
|
29
|
+
'should format',
|
|
30
|
+
Effect.fn(function* () {
|
|
31
|
+
const object = db.add(
|
|
32
|
+
Obj.make(DataType.Organization, {
|
|
33
|
+
name: 'Test',
|
|
34
|
+
website: 'https://www.test.com',
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const blueprint = db.add(
|
|
39
|
+
Blueprint.make({
|
|
40
|
+
key: 'example.com/blueprint/test',
|
|
41
|
+
name: 'Test',
|
|
42
|
+
instructions: Template.make({
|
|
43
|
+
source: trim`
|
|
44
|
+
Test
|
|
45
|
+
This is the test blueprint.
|
|
46
|
+
`,
|
|
47
|
+
}),
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const output = yield* formatSystemPrompt({
|
|
52
|
+
system: createSystemPrompt({}),
|
|
53
|
+
blueprints: [blueprint, blueprint],
|
|
54
|
+
objects: [object, object],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(output);
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Array, Effect, Option, pipe } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { Template } from '@dxos/blueprints';
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
import { ObjectVersion } from '@dxos/echo-db';
|
|
10
|
+
import { type ObjectId } from '@dxos/echo-schema';
|
|
11
|
+
import { DatabaseService } from '@dxos/functions';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
import { type ContentBlock, DataType } from '@dxos/schema';
|
|
14
|
+
import { trim } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { AiAssistantError } from '../errors';
|
|
17
|
+
|
|
18
|
+
import { ArtifactDiffResolver } from './artifact-diff';
|
|
19
|
+
import { type SessionRunParams } from './session';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Formats the system prompt.
|
|
23
|
+
*/
|
|
24
|
+
// TODO(burdon): Move to AiPreprocessor.
|
|
25
|
+
export const formatSystemPrompt = ({
|
|
26
|
+
system,
|
|
27
|
+
blueprints = [],
|
|
28
|
+
objects = [],
|
|
29
|
+
}: Pick<SessionRunParams<any>, 'system' | 'blueprints' | 'objects'>) =>
|
|
30
|
+
Effect.gen(function* () {
|
|
31
|
+
// TOOD(burdon): Should process templates.
|
|
32
|
+
const blueprintDefs = yield* pipe(
|
|
33
|
+
blueprints,
|
|
34
|
+
Effect.forEach((blueprint) => Effect.succeed(blueprint.instructions)),
|
|
35
|
+
Effect.flatMap(Effect.forEach((template) => DatabaseService.load(template.source))),
|
|
36
|
+
Effect.map(
|
|
37
|
+
Array.map(
|
|
38
|
+
(template) => trim`
|
|
39
|
+
<blueprint>
|
|
40
|
+
${Template.process(template.content)}
|
|
41
|
+
</blueprint>
|
|
42
|
+
`,
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
Effect.map((blueprints) =>
|
|
46
|
+
blueprints.length > 0 ? ['## Blueprints Definitions', ...blueprints].join('\n\n') : undefined,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const objectDefs = yield* pipe(
|
|
51
|
+
objects,
|
|
52
|
+
Effect.forEach((object) =>
|
|
53
|
+
Effect.succeed(trim`
|
|
54
|
+
<object>
|
|
55
|
+
<dxn>${Obj.getDXN(object)}</dxn>
|
|
56
|
+
<typename>${Obj.getTypename(object)}</typename>
|
|
57
|
+
</object>
|
|
58
|
+
`),
|
|
59
|
+
),
|
|
60
|
+
Effect.map((objects) => (objects.length > 0 ? ['## Context Objects', ...objects].join('\n\n') : undefined)),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return yield* pipe(
|
|
64
|
+
Effect.succeed([system, blueprintDefs, objectDefs].filter((def): def is string => def !== undefined)),
|
|
65
|
+
Effect.map((parts) => parts.join('\n\n')),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Formats the user prompt.
|
|
71
|
+
*/
|
|
72
|
+
// TODO(burdon): Move to AiPreprocessor.
|
|
73
|
+
// TODO(burdon): Convert util below to `Effect.fn` (to preserve stack info)
|
|
74
|
+
export const formatUserPrompt = ({ prompt, history = [] }: Pick<SessionRunParams<any>, 'prompt' | 'history'>) =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
const prelude: ContentBlock.Any[] = [];
|
|
77
|
+
|
|
78
|
+
// TODO(dmaretskyi): Evaluate other approaches as `serviceOption` isn't represented in the type system.
|
|
79
|
+
const artifactDiffResolver = yield* Effect.serviceOption(ArtifactDiffResolver);
|
|
80
|
+
if (Option.isSome(artifactDiffResolver)) {
|
|
81
|
+
const versions = gatherObjectVersions(history);
|
|
82
|
+
const artifactDiff = yield* Effect.tryPromise({
|
|
83
|
+
try: () =>
|
|
84
|
+
artifactDiffResolver.value.resolve(
|
|
85
|
+
[...versions.entries()].map(([id, version]) => ({ id, lastVersion: version })),
|
|
86
|
+
),
|
|
87
|
+
catch: AiAssistantError.wrap('Artifact diff resolution error'),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
log.info('version', { artifactDiff, versions });
|
|
91
|
+
for (const [id, { version }] of [...artifactDiff.entries()]) {
|
|
92
|
+
if (ObjectVersion.equals(version, versions.get(id)!)) {
|
|
93
|
+
artifactDiff.delete(id);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
prelude.push({ _tag: 'anchor', objectId: id, version });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (artifactDiff.size > 0) {
|
|
101
|
+
prelude.push(createArtifactUpdateBlock(artifactDiff));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return Obj.make(DataType.Message, {
|
|
106
|
+
created: new Date().toISOString(),
|
|
107
|
+
sender: { role: 'user' },
|
|
108
|
+
blocks: [...prelude, { _tag: 'text', text: prompt }],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const gatherObjectVersions = (messages: DataType.Message[]): Map<ObjectId, ObjectVersion> => {
|
|
113
|
+
const artifactIds = new Map<ObjectId, ObjectVersion>();
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
for (const block of message.blocks) {
|
|
116
|
+
if (block._tag === 'anchor') {
|
|
117
|
+
artifactIds.set(block.objectId, block.version as ObjectVersion);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return artifactIds;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const createArtifactUpdateBlock = (
|
|
126
|
+
artifactDiff: Map<ObjectId, { version: ObjectVersion; diff?: string }>,
|
|
127
|
+
): ContentBlock.Any => {
|
|
128
|
+
return {
|
|
129
|
+
_tag: 'text',
|
|
130
|
+
// TODO(dmaretskyi): Does this need to be a special content-block?
|
|
131
|
+
disposition: 'artifact-update',
|
|
132
|
+
text: trim`
|
|
133
|
+
The following artifacts have been updated since the last message:
|
|
134
|
+
${[...artifactDiff.entries()]
|
|
135
|
+
.map(([id, { diff }]) => `<changed-artifact id="${id}">${diff ? `\n${diff}` : ''}</changed-artifact>`)
|
|
136
|
+
.join('\n')}
|
|
137
|
+
`,
|
|
138
|
+
};
|
|
139
|
+
};
|
package/src/session/index.ts
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { describe,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { ObjectId } from '@dxos/echo-schema';
|
|
13
|
-
import { DXN } from '@dxos/keys';
|
|
5
|
+
import { AiTool, AiToolkit } from '@effect/ai';
|
|
6
|
+
import { describe, it } from '@effect/vitest';
|
|
7
|
+
import { Effect, Layer, Schema } from 'effect';
|
|
8
|
+
|
|
9
|
+
import { AiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
10
|
+
import { AiServiceTestingPreset } from '@dxos/ai/testing';
|
|
11
|
+
import { Obj, Type } from '@dxos/echo';
|
|
14
12
|
import { log } from '@dxos/log';
|
|
15
13
|
|
|
16
|
-
import {
|
|
14
|
+
import { AiSession } from './session';
|
|
17
15
|
|
|
18
16
|
// Define a calendar event artifact schema.
|
|
19
17
|
const CalendarEventSchema = Schema.Struct({
|
|
@@ -30,15 +28,90 @@ const CalendarEventSchema = Schema.Struct({
|
|
|
30
28
|
|
|
31
29
|
type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
class TestToolkit extends AiToolkit.make(
|
|
32
|
+
AiTool.make('Calculator', {
|
|
33
|
+
description: 'Basic calculator tool',
|
|
34
|
+
parameters: {
|
|
35
|
+
input: Schema.String.annotations({
|
|
36
|
+
description: 'The calculation to perform.',
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
success: Schema.Struct({
|
|
40
|
+
result: Schema.Number,
|
|
41
|
+
}),
|
|
42
|
+
failure: Schema.Never,
|
|
43
|
+
}),
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
// Tool handlers.
|
|
47
|
+
const toolkitLayer = TestToolkit.toLayer({
|
|
48
|
+
Calculator: ({ input }) =>
|
|
49
|
+
Effect.gen(function* () {
|
|
50
|
+
const result = (() => {
|
|
51
|
+
// Restrict to basic arithmetic operations for safety.
|
|
52
|
+
const sanitizedInput = input.replace(/[^0-9+\-*/().\s]/g, '');
|
|
53
|
+
log.info('calculate', { sanitizedInput });
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
56
|
+
return Function(`"use strict"; return (${sanitizedInput})`)();
|
|
57
|
+
})();
|
|
58
|
+
|
|
59
|
+
return { result };
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe.runIf(process.env.DX_RUN_SLOW_TESTS)('AiSession', () => {
|
|
64
|
+
it.effect(
|
|
65
|
+
'no tools',
|
|
66
|
+
Effect.fn(
|
|
67
|
+
function* ({ expect: _ }) {
|
|
68
|
+
const session = new AiSession({ operationModel: 'configured' });
|
|
69
|
+
const response = yield* session.run({
|
|
70
|
+
prompt: 'Hello world!',
|
|
71
|
+
history: [],
|
|
72
|
+
});
|
|
73
|
+
log.info('response', { response });
|
|
74
|
+
},
|
|
75
|
+
Effect.provide(
|
|
76
|
+
AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
|
|
77
|
+
Layer.provideMerge(ToolResolverService.layerEmpty),
|
|
78
|
+
Layer.provideMerge(ToolExecutionService.layerEmpty),
|
|
79
|
+
Layer.provideMerge(AiServiceTestingPreset('direct')),
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
41
84
|
|
|
85
|
+
it.effect(
|
|
86
|
+
'calculator',
|
|
87
|
+
Effect.fn(
|
|
88
|
+
function* ({ expect: _ }) {
|
|
89
|
+
const session = new AiSession({ operationModel: 'configured' });
|
|
90
|
+
const response = yield* session.run({
|
|
91
|
+
prompt: 'What is 10 + 20?',
|
|
92
|
+
history: [],
|
|
93
|
+
toolkit: TestToolkit,
|
|
94
|
+
});
|
|
95
|
+
log.info('response', { response });
|
|
96
|
+
},
|
|
97
|
+
Effect.provide(
|
|
98
|
+
Layer.mergeAll(
|
|
99
|
+
toolkitLayer,
|
|
100
|
+
ToolResolverService.layerEmpty,
|
|
101
|
+
ToolExecutionService.layerEmpty,
|
|
102
|
+
AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
|
|
103
|
+
Layer.provideMerge(AiServiceTestingPreset('direct')),
|
|
104
|
+
),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// TODO(dmaretskyi): Revive test.
|
|
111
|
+
/*
|
|
112
|
+
it.skip('create calendar itinerary', { timeout: 60_000 }, async () => {
|
|
113
|
+
// overrides: { model: 'llama3.1:8b' },
|
|
114
|
+
const session = new AiSession({ operationModel: 'configured' });
|
|
42
115
|
const objects = new Set<string>();
|
|
43
116
|
|
|
44
117
|
// Define calendar artifact.
|
|
@@ -145,6 +218,7 @@ describe.skip('AISession with Ollama', () => {
|
|
|
145
218
|
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
146
219
|
},
|
|
147
220
|
prompt: 'create a table and map for a travel itinerary based on events in my calendar',
|
|
221
|
+
toolResolver: new ToolRegistry([]),
|
|
148
222
|
});
|
|
149
223
|
|
|
150
224
|
log('result', {
|
|
@@ -152,10 +226,12 @@ describe.skip('AISession with Ollama', () => {
|
|
|
152
226
|
finalMessage: response.at(-1),
|
|
153
227
|
});
|
|
154
228
|
});
|
|
229
|
+
*/
|
|
155
230
|
});
|
|
156
231
|
|
|
157
232
|
// Travel to rome, florence, livorno, siena, madrid for conferences
|
|
158
|
-
|
|
233
|
+
|
|
234
|
+
const _CALENDAR_EVENTS: CalendarEvent[] = [
|
|
159
235
|
Obj.make(CalendarEventSchema, {
|
|
160
236
|
title: 'Exploring Ancient Ruins in Rome',
|
|
161
237
|
startTime: '2024-01-01T10:00:00Z',
|