@dxos/assistant 0.8.4-main.84f28bd → 0.8.4-main.b97322e
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/index.mjs +623 -811
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +623 -811
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/context/binder.d.ts +4 -4
- package/dist/types/src/context/binder.d.ts.map +1 -1
- package/dist/types/src/context/binding.d.ts +24 -6
- package/dist/types/src/context/binding.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.d.ts +12 -18
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts +38 -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 +127 -33
- 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 +100 -42
- 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 +6 -0
- package/dist/types/src/functions/services.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/research/exa.d.ts +10 -5
- package/dist/types/src/research/exa.d.ts.map +1 -1
- package/dist/types/src/research/graph.d.ts +18 -8
- package/dist/types/src/research/graph.d.ts.map +1 -1
- package/dist/types/src/research/research.d.ts +1 -1
- package/dist/types/src/research/research.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +170 -193
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +2 -2
- 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/prompt.d.ts +30 -0
- package/dist/types/src/templates/prompt.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +10 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +29 -21
- package/src/context/binder.ts +6 -4
- package/src/context/binding.ts +4 -3
- package/src/conversation/conversation.ts +54 -61
- package/src/errors.ts +10 -0
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +6 -18
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +18 -14
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +6 -3
- 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/functions/index.ts +5 -0
- package/src/functions/services.ts +139 -0
- package/src/index.ts +2 -2
- package/src/research/exa.ts +56 -59
- package/src/research/graph.test.ts +50 -1
- package/src/research/graph.ts +85 -53
- package/src/research/instructions-research.tpl +1 -0
- package/src/research/research.test.ts +27 -27
- package/src/research/research.ts +50 -53
- package/src/session/__snapshots__/session.test.ts.snap +32 -0
- package/src/session/session.test.ts +90 -55
- package/src/session/session.ts +215 -461
- package/src/session/version-pin.test.ts +2 -21
- package/src/session/version-pin.ts +5 -5
- package/src/summarization/summarization.test.ts +75 -88
- package/src/templates/assistant.tpl +111 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/prompt.ts +45 -0
- package/src/types.ts +40 -1
- package/dist/types/src/blueprint/blueprint.d.ts +0 -41
- package/dist/types/src/blueprint/blueprint.d.ts.map +0 -1
- package/dist/types/src/blueprint/index.d.ts +0 -3
- package/dist/types/src/blueprint/index.d.ts.map +0 -1
- package/dist/types/src/blueprint/registry.d.ts +0 -7
- package/dist/types/src/blueprint/registry.d.ts.map +0 -1
- package/src/blueprint/blueprint.ts +0 -75
- package/src/blueprint/index.ts +0 -6
- package/src/blueprint/registry.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/assistant",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.b97322e",
|
|
4
4
|
"description": "Natural language interface to DXOS",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -21,32 +21,40 @@
|
|
|
21
21
|
"src"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@effect/ai": "0.23.0",
|
|
25
|
+
"@effect/ai-anthropic": "^0.13.0",
|
|
26
|
+
"@effect/platform": "^0.89.0",
|
|
27
|
+
"@effect/platform-node": "^0.92.0",
|
|
24
28
|
"@preact/signals-core": "^1.9.0",
|
|
25
29
|
"@xenova/transformers": "^2.17.2",
|
|
26
30
|
"chalk": "^4.1.0",
|
|
27
31
|
"exa-js": "^1.5.12",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/echo
|
|
36
|
-
"@dxos/echo-
|
|
37
|
-
"@dxos/effect": "0.8.4-main.
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
32
|
+
"handlebars": "4.7.8",
|
|
33
|
+
"lodash.defaultsdeep": "^4.6.1",
|
|
34
|
+
"@dxos/ai": "0.8.4-main.b97322e",
|
|
35
|
+
"@dxos/async": "0.8.4-main.b97322e",
|
|
36
|
+
"@dxos/context": "0.8.4-main.b97322e",
|
|
37
|
+
"@dxos/blueprints": "0.8.4-main.b97322e",
|
|
38
|
+
"@dxos/debug": "0.8.4-main.b97322e",
|
|
39
|
+
"@dxos/echo": "0.8.4-main.b97322e",
|
|
40
|
+
"@dxos/echo-db": "0.8.4-main.b97322e",
|
|
41
|
+
"@dxos/effect": "0.8.4-main.b97322e",
|
|
42
|
+
"@dxos/echo-schema": "0.8.4-main.b97322e",
|
|
43
|
+
"@dxos/errors": "0.8.4-main.b97322e",
|
|
44
|
+
"@dxos/functions": "0.8.4-main.b97322e",
|
|
45
|
+
"@dxos/invariant": "0.8.4-main.b97322e",
|
|
46
|
+
"@dxos/graph": "0.8.4-main.b97322e",
|
|
47
|
+
"@dxos/keys": "0.8.4-main.b97322e",
|
|
48
|
+
"@dxos/log": "0.8.4-main.b97322e",
|
|
49
|
+
"@dxos/live-object": "0.8.4-main.b97322e",
|
|
50
|
+
"@dxos/node-std": "0.8.4-main.b97322e",
|
|
51
|
+
"@dxos/schema": "0.8.4-main.b97322e",
|
|
52
|
+
"@dxos/util": "0.8.4-main.b97322e",
|
|
53
|
+
"@dxos/echo-protocol": "0.8.4-main.b97322e"
|
|
47
54
|
},
|
|
48
55
|
"devDependencies": {
|
|
49
|
-
"
|
|
56
|
+
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
57
|
+
"effect": "3.17.0",
|
|
50
58
|
"inquirer": "^12.0.0"
|
|
51
59
|
},
|
|
52
60
|
"peerDependencies": {
|
package/src/context/binder.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
|
6
6
|
import { Array, pipe } from 'effect';
|
|
7
7
|
|
|
8
|
+
import { type Blueprint } from '@dxos/blueprints';
|
|
8
9
|
import { Obj, type Type, type Ref, type Relation } from '@dxos/echo';
|
|
9
10
|
import { type Queue } from '@dxos/echo-db';
|
|
10
11
|
import { ComplexSet } from '@dxos/util';
|
|
11
12
|
|
|
12
13
|
import { ContextBinding } from './binding';
|
|
13
|
-
import { type Blueprint } from '../blueprint';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Manages bindings of blueprints and objects to a conversation.
|
|
@@ -25,7 +25,9 @@ export class ContextBinder {
|
|
|
25
25
|
readonly bindings: ReadonlySignal<Bindings> = computed(() => this._reduce(this._queue.objects));
|
|
26
26
|
|
|
27
27
|
// TODO(burdon): load refs?
|
|
28
|
-
readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint>[]> = computed(() => [
|
|
28
|
+
readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]> = computed(() => [
|
|
29
|
+
...this.bindings.value.blueprints,
|
|
30
|
+
]);
|
|
29
31
|
readonly objects: ReadonlySignal<Ref.Ref<Type.Expando>[]> = computed(() => [...this.bindings.value.objects]);
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -91,11 +93,11 @@ export class ContextBinder {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export type BindingProps = Partial<{
|
|
94
|
-
blueprints: Ref.Ref<Blueprint>[];
|
|
96
|
+
blueprints: Ref.Ref<Blueprint.Blueprint>[];
|
|
95
97
|
objects: Ref.Ref<Type.Expando>[];
|
|
96
98
|
}>;
|
|
97
99
|
|
|
98
100
|
export class Bindings {
|
|
99
|
-
blueprints = new ComplexSet<Ref.Ref<Blueprint>>((ref) => ref.dxn.toString());
|
|
101
|
+
blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.dxn.toString());
|
|
100
102
|
objects = new ComplexSet<Ref.Ref<Type.Expando>>((ref) => ref.dxn.toString());
|
|
101
103
|
}
|
package/src/context/binding.ts
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
|
+
import { Blueprint } from '@dxos/blueprints';
|
|
7
8
|
import { Type } from '@dxos/echo';
|
|
8
9
|
|
|
9
|
-
import { Blueprint } from '../blueprint';
|
|
10
10
|
/**
|
|
11
11
|
* Thread message that binds or unbinds contextual objects to a conversation.
|
|
12
12
|
*/
|
|
13
|
+
// TODO(burdon): Make ContentBlock.
|
|
13
14
|
export const ContextBinding = Schema.Struct({
|
|
14
15
|
blueprints: Schema.Struct({
|
|
15
|
-
added: Schema.Array(Type.Ref(Blueprint)),
|
|
16
|
-
removed: Schema.Array(Type.Ref(Blueprint)),
|
|
16
|
+
added: Schema.Array(Type.Ref(Blueprint.Blueprint)),
|
|
17
|
+
removed: Schema.Array(Type.Ref(Blueprint.Blueprint)),
|
|
17
18
|
}),
|
|
18
19
|
|
|
19
20
|
// TODO(burdon): Type.Expando => Type.Obj (or Obj.Any?)
|
|
@@ -2,37 +2,34 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { type AiError, type AiLanguageModel, type AiTool, type AiToolkit } from '@effect/ai';
|
|
6
|
+
import { Effect } from 'effect';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ToolExecutionService,
|
|
10
|
+
type ToolResolverService,
|
|
11
|
+
type AiInputPreprocessingError,
|
|
12
|
+
type AiToolNotFoundError,
|
|
13
|
+
} from '@dxos/ai';
|
|
7
14
|
import { Event } from '@dxos/async';
|
|
8
|
-
import { Obj
|
|
15
|
+
import { Obj } from '@dxos/echo';
|
|
9
16
|
import { type Queue } from '@dxos/echo-db';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { isNonNullable } from '@dxos/util';
|
|
17
|
+
import { DatabaseService } from '@dxos/functions';
|
|
18
|
+
import { DataType } from '@dxos/schema';
|
|
13
19
|
|
|
14
20
|
import { ContextBinder, type ContextBinding } from '../context';
|
|
15
|
-
import {
|
|
21
|
+
import type { AiAssistantError } from '../errors';
|
|
22
|
+
import { AiSession } from '../session';
|
|
16
23
|
|
|
17
|
-
export interface ConversationRunOptions {
|
|
24
|
+
export interface ConversationRunOptions<Tools extends AiTool.Any> {
|
|
18
25
|
systemPrompt?: string;
|
|
19
26
|
prompt: string;
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
artifacts?: ArtifactDefinition[];
|
|
23
|
-
extensions?: ToolContextExtensions;
|
|
24
|
-
generationOptions?: SessionRunOptions['generationOptions'];
|
|
25
|
-
|
|
26
|
-
/** @depreacated Should be managed by the conversation. */
|
|
27
|
-
requiredArtifactIds?: string[];
|
|
28
|
-
|
|
29
|
-
// TODO(dmaretskyi): Move into conversation.
|
|
30
|
-
artifactDiffResolver?: SessionRunOptions['artifactDiffResolver'];
|
|
28
|
+
toolkit?: AiToolkit.AiToolkit<Tools>;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
export type ConversationOptions = {
|
|
34
|
-
|
|
35
|
-
queue: Queue<Message | ContextBinding>;
|
|
32
|
+
queue: Queue<DataType.Message | ContextBinding>;
|
|
36
33
|
};
|
|
37
34
|
|
|
38
35
|
/**
|
|
@@ -41,14 +38,13 @@ export type ConversationOptions = {
|
|
|
41
38
|
* Backed by a Queue.
|
|
42
39
|
*/
|
|
43
40
|
export class Conversation {
|
|
44
|
-
private readonly
|
|
45
|
-
private readonly _queue: Queue<Message | ContextBinding>;
|
|
41
|
+
private readonly _queue: Queue<DataType.Message | ContextBinding>;
|
|
46
42
|
|
|
47
43
|
/**
|
|
48
44
|
* Fired when the execution loop begins.
|
|
49
45
|
* This is called before the first message is sent.
|
|
50
46
|
*/
|
|
51
|
-
public readonly onBegin = new Event<
|
|
47
|
+
public readonly onBegin = new Event<AiSession>();
|
|
52
48
|
|
|
53
49
|
/**
|
|
54
50
|
* Blueprints bound to the conversation.
|
|
@@ -56,48 +52,45 @@ export class Conversation {
|
|
|
56
52
|
public readonly context: ContextBinder;
|
|
57
53
|
|
|
58
54
|
constructor(options: ConversationOptions) {
|
|
59
|
-
this._serviceContainer = options.serviceContainer;
|
|
60
55
|
this._queue = options.queue;
|
|
61
56
|
this.context = new ContextBinder(this._queue);
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
async
|
|
65
|
-
const session = new AISession({ operationModel: 'configured' });
|
|
66
|
-
this.onBegin.emit(session);
|
|
67
|
-
|
|
68
|
-
const history = await this.getHistory();
|
|
69
|
-
const context = await this.context.query();
|
|
70
|
-
const blueprints = (await Ref.Array.loadAll([...context.blueprints])).filter(isNonNullable);
|
|
71
|
-
if (blueprints.length > 1) {
|
|
72
|
-
log.warn('multiple blueprints are not yet supported');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const messages = await session.run({
|
|
76
|
-
history,
|
|
77
|
-
prompt: options.prompt,
|
|
78
|
-
systemPrompt: (options.systemPrompt ?? '') + (blueprints.at(0)?.instructions ?? ''),
|
|
79
|
-
|
|
80
|
-
// TODO(dmaretskyi): Artifacts come from the blueprint?
|
|
81
|
-
artifacts: options.artifacts ?? [],
|
|
82
|
-
tools: blueprints.at(0)?.tools ?? [],
|
|
83
|
-
executableTools: options.tools,
|
|
84
|
-
requiredArtifactIds: options.requiredArtifactIds,
|
|
85
|
-
client: this._serviceContainer.getService(AiService).client,
|
|
86
|
-
toolResolver: this._serviceContainer.getService(ToolResolverService).toolResolver,
|
|
87
|
-
extensions: {
|
|
88
|
-
serviceContainer: this._serviceContainer,
|
|
89
|
-
...options.extensions,
|
|
90
|
-
},
|
|
91
|
-
generationOptions: options.generationOptions,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
await this._queue.append(messages);
|
|
95
|
-
|
|
96
|
-
return messages;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async getHistory(): Promise<Message[]> {
|
|
59
|
+
async getHistory(): Promise<DataType.Message[]> {
|
|
100
60
|
const queueItems = await this._queue.queryObjects();
|
|
101
|
-
return queueItems.filter(Obj.instanceOf(Message));
|
|
61
|
+
return queueItems.filter(Obj.instanceOf(DataType.Message));
|
|
102
62
|
}
|
|
63
|
+
|
|
64
|
+
run = <Tools extends AiTool.Any>(
|
|
65
|
+
options: ConversationRunOptions<Tools>,
|
|
66
|
+
): Effect.Effect<
|
|
67
|
+
DataType.Message[],
|
|
68
|
+
AiAssistantError | AiInputPreprocessingError | AiError.AiError | AiToolNotFoundError,
|
|
69
|
+
AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
|
|
70
|
+
> =>
|
|
71
|
+
Effect.gen(this, function* () {
|
|
72
|
+
const session = new AiSession();
|
|
73
|
+
this.onBegin.emit(session);
|
|
74
|
+
|
|
75
|
+
const history = yield* Effect.promise(() => this.getHistory());
|
|
76
|
+
const context = yield* Effect.promise(() => this.context.query());
|
|
77
|
+
const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.loadRef);
|
|
78
|
+
|
|
79
|
+
const contextObjects = yield* Effect.forEach(context.objects.values(), DatabaseService.loadRef);
|
|
80
|
+
|
|
81
|
+
const systemPrompt =
|
|
82
|
+
(options.systemPrompt ?? '') +
|
|
83
|
+
'\n\n' +
|
|
84
|
+
contextObjects.map((obj) => `<object>${Obj.getDXN(obj)}</object>`).join('\n');
|
|
85
|
+
|
|
86
|
+
const messages = yield* session.run({
|
|
87
|
+
prompt: options.prompt,
|
|
88
|
+
history,
|
|
89
|
+
systemPrompt,
|
|
90
|
+
toolkit: options.toolkit,
|
|
91
|
+
blueprints,
|
|
92
|
+
});
|
|
93
|
+
yield* Effect.promise(() => this._queue.append(messages));
|
|
94
|
+
return messages;
|
|
95
|
+
}).pipe(Effect.withSpan('Conversation.run'));
|
|
103
96
|
}
|
package/src/errors.ts
ADDED
|
@@ -2,35 +2,23 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe, test
|
|
5
|
+
import { beforeAll, describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
|
|
7
|
+
import { todo } from '@dxos/debug';
|
|
8
|
+
import { FunctionExecutor } from '@dxos/functions';
|
|
10
9
|
import { log } from '@dxos/log';
|
|
11
10
|
import { createTestData } from '@dxos/schema/testing';
|
|
12
11
|
import { range } from '@dxos/util';
|
|
13
12
|
|
|
14
|
-
import { extractionAnthropicFn } from './extraction-llm-function';
|
|
15
13
|
import { processTranscriptMessage } from '../extraction';
|
|
14
|
+
import { extractionAnthropicFn } from './extraction-llm-function';
|
|
16
15
|
|
|
16
|
+
// TODO(burdon): Rewrite test.
|
|
17
17
|
describe.skip('LLM EntityExtraction', () => {
|
|
18
18
|
let executor: FunctionExecutor;
|
|
19
19
|
|
|
20
20
|
beforeAll(async () => {
|
|
21
|
-
executor = new FunctionExecutor(
|
|
22
|
-
new ServiceContainer().setServices({
|
|
23
|
-
ai: {
|
|
24
|
-
client: new EdgeAiServiceClient({
|
|
25
|
-
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
26
|
-
defaultGenerationOptions: {
|
|
27
|
-
// model: '@anthropic/claude-sonnet-4-20250514',
|
|
28
|
-
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
},
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
21
|
+
executor = new FunctionExecutor(todo());
|
|
34
22
|
});
|
|
35
23
|
|
|
36
24
|
test('should process a transcript block', async () => {
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// TODO(burdon): Fix.
|
|
6
|
+
// @ts-nocheck
|
|
7
|
+
|
|
8
|
+
import { ToolRegistry, AiService } from '@dxos/ai';
|
|
6
9
|
import { Obj } from '@dxos/echo';
|
|
7
10
|
import { create } from '@dxos/echo-schema';
|
|
8
|
-
import {
|
|
9
|
-
import { DataType } from '@dxos/schema';
|
|
11
|
+
import { defineFunction, type FunctionDefinition } from '@dxos/functions';
|
|
12
|
+
import { type ContentBlock, DataType } from '@dxos/schema';
|
|
10
13
|
|
|
11
14
|
import PROMPT from './instructions.tpl?raw';
|
|
12
|
-
import {
|
|
15
|
+
import { AiSession } from '../../session';
|
|
13
16
|
import { ExtractionInput, ExtractionOutput } from '../extraction';
|
|
14
17
|
import { insertReferences, ReferencedQuotes } from '../quotes';
|
|
15
18
|
|
|
@@ -20,7 +23,7 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
|
|
|
20
23
|
handler: async ({ data: { message, objects }, context }) => {
|
|
21
24
|
const startTime = performance.now();
|
|
22
25
|
const ai = context.getService(AiService);
|
|
23
|
-
const session = new
|
|
26
|
+
const session = new AiSession({ operationModel: 'configured' });
|
|
24
27
|
const result = await session.runStructured(ReferencedQuotes, {
|
|
25
28
|
generationOptions: {
|
|
26
29
|
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
@@ -28,17 +31,18 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
|
|
|
28
31
|
client: ai.client,
|
|
29
32
|
systemPrompt: PROMPT,
|
|
30
33
|
history: [
|
|
31
|
-
Obj.make(Message, {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
Obj.make(DataType.Message, {
|
|
35
|
+
created: new Date().toISOString(),
|
|
36
|
+
sender: { role: 'user' },
|
|
37
|
+
blocks: [
|
|
34
38
|
{
|
|
35
|
-
|
|
39
|
+
_tag: 'text',
|
|
36
40
|
text: `<context>${JSON.stringify(objects)}</context>`,
|
|
37
|
-
},
|
|
41
|
+
} satisfies ContentBlock.Text,
|
|
38
42
|
{
|
|
39
|
-
|
|
43
|
+
_tag: 'text',
|
|
40
44
|
text: `<transcript>${JSON.stringify(message.blocks)}</transcript>`,
|
|
41
|
-
},
|
|
45
|
+
} satisfies ContentBlock.Text,
|
|
42
46
|
],
|
|
43
47
|
}),
|
|
44
48
|
],
|
|
@@ -46,13 +50,13 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
|
|
|
46
50
|
prompt: '',
|
|
47
51
|
tools: [],
|
|
48
52
|
toolResolver: new ToolRegistry([]),
|
|
49
|
-
});
|
|
53
|
+
} as any); // TODO(burdon): Rewrite test.
|
|
50
54
|
|
|
51
55
|
return {
|
|
52
56
|
message: create(DataType.Message, {
|
|
53
57
|
...message,
|
|
54
58
|
blocks: message.blocks.map((block, i) =>
|
|
55
|
-
block.
|
|
59
|
+
block._tag !== 'transcript'
|
|
56
60
|
? block
|
|
57
61
|
: {
|
|
58
62
|
...block,
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
// TODO(burdon): Fix.
|
|
6
|
+
// @ts-nocheck
|
|
7
|
+
|
|
5
8
|
import { asyncTimeout } from '@dxos/async';
|
|
6
9
|
import { create } from '@dxos/echo-schema';
|
|
7
10
|
import { DatabaseService, defineFunction } from '@dxos/functions';
|
|
@@ -25,7 +28,7 @@ export const extractionNerFn = defineFunction({
|
|
|
25
28
|
|
|
26
29
|
const entitiesPromise = Promise.all(
|
|
27
30
|
message.blocks.map(async (block) => {
|
|
28
|
-
invariant(block.
|
|
31
|
+
invariant(block._tag === 'transcript' || block._tag === 'text', 'Block must have text');
|
|
29
32
|
return extractFullEntities(block.text);
|
|
30
33
|
}),
|
|
31
34
|
).then((entities) => entities.flat());
|
|
@@ -36,8 +39,8 @@ export const extractionNerFn = defineFunction({
|
|
|
36
39
|
db,
|
|
37
40
|
);
|
|
38
41
|
const blocksWithReferences = message.blocks.map((block, i) => {
|
|
39
|
-
invariant(block.
|
|
40
|
-
return { ...block, text: insertReferences(block.
|
|
42
|
+
invariant(block._tag === 'transcript' || block._tag === 'text', 'Block must have text');
|
|
43
|
+
return { ...block, text: insertReferences(block._tag, quoteReferences) };
|
|
41
44
|
});
|
|
42
45
|
const messageWithReferences = create(DataType.Message, {
|
|
43
46
|
...message,
|
|
@@ -57,7 +57,7 @@ describe('NamedEntityRecognition', () => {
|
|
|
57
57
|
const { transcriptWoflram, transcriptJosiah } = await createTestData();
|
|
58
58
|
|
|
59
59
|
const blocks = [...transcriptWoflram, ...transcriptJosiah].flatMap((message) =>
|
|
60
|
-
message.blocks.flatMap((block) => (block.
|
|
60
|
+
message.blocks.flatMap((block) => (block._tag === 'transcript' ? [block.text] : [])),
|
|
61
61
|
);
|
|
62
62
|
|
|
63
63
|
for (const block of blocks) {
|
|
@@ -70,7 +70,7 @@ describe('NamedEntityRecognition', () => {
|
|
|
70
70
|
const { transcriptMessages } = await createTestData();
|
|
71
71
|
|
|
72
72
|
const blocks = transcriptMessages.flatMap((message) =>
|
|
73
|
-
message.blocks.flatMap((block) => (block.
|
|
73
|
+
message.blocks.flatMap((block) => (block._tag === 'transcript' ? [block.text] : [])),
|
|
74
74
|
);
|
|
75
75
|
|
|
76
76
|
for (const block of blocks) {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { AiTool } 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 { todo } from '@dxos/debug';
|
|
10
|
+
import { FunctionError, type FunctionContext, type FunctionDefinition, type Services } from '@dxos/functions';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
|
|
13
|
+
export const makeToolResolverFromFunctions = (
|
|
14
|
+
functions: FunctionDefinition<any, any>[],
|
|
15
|
+
): Layer.Layer<ToolResolverService> => {
|
|
16
|
+
return Layer.succeed(ToolResolverService, {
|
|
17
|
+
resolve: Effect.fn('resolveTool')(function* (id) {
|
|
18
|
+
const fn = functions.find((f) => f.name === id);
|
|
19
|
+
if (!fn) {
|
|
20
|
+
return yield* Effect.fail(new AiToolNotFoundError(id));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return projectFunctionToTool(fn);
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const makeToolExecutionServiceFromFunctions = (
|
|
29
|
+
functions: FunctionDefinition<any, any>[],
|
|
30
|
+
): Layer.Layer<ToolExecutionService> => {
|
|
31
|
+
return Layer.succeed(ToolExecutionService, {
|
|
32
|
+
handlersFor: (toolkit) => {
|
|
33
|
+
const makeHandler = (tool: AiTool.Any): ((params: unknown) => Effect.Effect<unknown, any, any>) => {
|
|
34
|
+
return Effect.fn('toolFunctionHandler')(function* (input: any) {
|
|
35
|
+
const { functionName } = Context.get(FunctionToolAnnotation)(tool.annotations as any);
|
|
36
|
+
const fnDef = functions.find((f) => f.name === functionName);
|
|
37
|
+
if (!fnDef) {
|
|
38
|
+
return yield* Effect.fail(new AiToolNotFoundError(tool.name));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return yield* invokeFunction(fnDef, input).pipe(Effect.catchAllDefect((defect) => Effect.fail(defect)));
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return toolkit.of(Record.map(toolkit.tools, (tool, name) => makeHandler(tool)) as any) as any;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Tools that are projected from functions have this annotation.
|
|
52
|
+
*/
|
|
53
|
+
class FunctionToolAnnotation extends Context.Tag('@dxos/assisatnt/FunctionToolAnnotation')<
|
|
54
|
+
FunctionToolAnnotation,
|
|
55
|
+
{
|
|
56
|
+
functionName: string;
|
|
57
|
+
}
|
|
58
|
+
>() {}
|
|
59
|
+
|
|
60
|
+
const toolCache = new WeakMap<FunctionDefinition<any, any>, AiTool.Any>();
|
|
61
|
+
const projectFunctionToTool = (fn: FunctionDefinition<any, any>): AiTool.Any => {
|
|
62
|
+
if (toolCache.has(fn)) {
|
|
63
|
+
return toolCache.get(fn)!;
|
|
64
|
+
}
|
|
65
|
+
const tool = AiTool.make(makeToolName(fn.name), {
|
|
66
|
+
description: fn.description,
|
|
67
|
+
parameters: createStructFieldsFromSchema(fn.inputSchema),
|
|
68
|
+
// TODO(dmaretskyi): Include output schema.
|
|
69
|
+
failure: Schema.Any, // TODO(dmaretskyi): Better type for the failure?
|
|
70
|
+
}).annotate(FunctionToolAnnotation, { functionName: fn.name });
|
|
71
|
+
toolCache.set(fn, tool);
|
|
72
|
+
return tool;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @returns Tool name produced from function name by escaping invalid characters.
|
|
77
|
+
*/
|
|
78
|
+
const makeToolName = (name: string) => {
|
|
79
|
+
const toolName = name.replace(/[^a-zA-Z0-9]/g, '');
|
|
80
|
+
invariant(toolName.match(/^[a-zA-Z_][a-zA-Z0-9-_]*$/));
|
|
81
|
+
return toolName;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// TODO(dmaretskyi): Factor out.
|
|
85
|
+
const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<string, Schema.Schema<any, any>> => {
|
|
86
|
+
return Match.value(schema.ast).pipe(
|
|
87
|
+
Match.when(Predicate.isTagged('TypeLiteral'), (ast) => {
|
|
88
|
+
return Object.fromEntries(ast.propertySignatures.map((prop) => [prop.name, Schema.make(prop.type)]));
|
|
89
|
+
}),
|
|
90
|
+
Match.orElseAbsurd,
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// TODO(dmaretskyi): Factor out.
|
|
95
|
+
const invokeFunction = (fnDef: FunctionDefinition<any, any>, input: any): Effect.Effect<unknown, never, Services> =>
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
// Assert input matches schema
|
|
98
|
+
const assertInput = fnDef.inputSchema.pipe(Schema.asserts);
|
|
99
|
+
(assertInput as any)(input);
|
|
100
|
+
|
|
101
|
+
const context: FunctionContext = {
|
|
102
|
+
getService: () => todo(),
|
|
103
|
+
getSpace: async (_spaceId: any) => {
|
|
104
|
+
throw new Error('Not available. Use the database service instead.');
|
|
105
|
+
},
|
|
106
|
+
space: undefined,
|
|
107
|
+
get ai(): never {
|
|
108
|
+
throw new Error('Not available. Use the ai service instead.');
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// TODO(dmaretskyi): This should be delegated to a function invoker service.
|
|
113
|
+
const data = yield* Effect.gen(function* () {
|
|
114
|
+
const result = fnDef.handler({ context, data: input });
|
|
115
|
+
if (Effect.isEffect(result)) {
|
|
116
|
+
return yield* (result as Effect.Effect<unknown, unknown, Services>).pipe(Effect.orDie);
|
|
117
|
+
} else if (
|
|
118
|
+
typeof result === 'object' &&
|
|
119
|
+
result !== null &&
|
|
120
|
+
'then' in result &&
|
|
121
|
+
typeof result.then === 'function'
|
|
122
|
+
) {
|
|
123
|
+
return yield* Effect.promise(() => result);
|
|
124
|
+
} else {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
}).pipe(
|
|
128
|
+
Effect.orDie,
|
|
129
|
+
Effect.catchAllDefect((defect) =>
|
|
130
|
+
Effect.die(new FunctionError('Error running function', { context: { name: fnDef.name }, cause: defect })),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Assert output matches schema
|
|
135
|
+
const assertOutput = fnDef.outputSchema?.pipe(Schema.asserts);
|
|
136
|
+
(assertOutput as any)(data);
|
|
137
|
+
|
|
138
|
+
return data;
|
|
139
|
+
}).pipe(Effect.withSpan('invokeFunction', { attributes: { name: fnDef.name } }));
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * from './blueprint';
|
|
6
5
|
export * from './context';
|
|
7
6
|
export * from './conversation';
|
|
8
7
|
export * from './extraction';
|
|
8
|
+
export * from './functions';
|
|
9
9
|
export * from './research';
|
|
10
10
|
export * from './session';
|
|
11
|
+
export * from './templates';
|
|
11
12
|
export * from './types';
|
|
12
|
-
export * from './conversation';
|