@dxos/assistant 0.8.3-main.672df60 → 0.8.3-staging.0fa589b
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 +149 -60
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +155 -65
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +149 -60
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/blueprint/ai.test.d.ts +2 -0
- package/dist/types/src/blueprint/ai.test.d.ts.map +1 -0
- package/dist/types/src/blueprint/blueprint.d.ts +16 -12
- package/dist/types/src/blueprint/blueprint.d.ts.map +1 -1
- package/dist/types/src/blueprint/logger.d.ts +16 -3
- package/dist/types/src/blueprint/logger.d.ts.map +1 -1
- package/dist/types/src/blueprint/machine.d.ts +37 -16
- package/dist/types/src/blueprint/machine.d.ts.map +1 -1
- 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 +31 -33
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +104 -100
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/quotes.d.ts.map +1 -1
- package/dist/types/src/research/graph.d.ts +5 -4
- package/dist/types/src/research/graph.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +6 -6
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/package.json +29 -20
- package/src/blueprint/ai.test.ts +156 -0
- package/src/blueprint/blueprint.test.ts +22 -20
- package/src/blueprint/blueprint.ts +18 -14
- package/src/blueprint/logger.ts +49 -3
- package/src/blueprint/machine.ts +69 -32
- package/src/blueprint/test-data.ts +4 -4
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +2 -2
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +2 -1
- package/src/extraction/quotes.ts +2 -1
- package/src/research/graph.ts +7 -9
- package/src/research/research.test.ts +9 -8
- package/src/research/research.ts +2 -2
- package/src/session/session.test.ts +10 -10
- package/src/session/session.ts +4 -3
- package/src/summarization/summarization.test.ts +17 -12
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { EdgeAiServiceClient, ToolRegistry, ToolResult, createTool } from '@dxos/ai';
|
|
9
9
|
import { AI_SERVICE_ENDPOINT, EXA_API_KEY } from '@dxos/ai/testing';
|
|
10
10
|
import { ArtifactId } from '@dxos/artifact';
|
|
11
|
+
import { Obj } from '@dxos/echo';
|
|
11
12
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
12
|
-
import { create } from '@dxos/echo-schema';
|
|
13
13
|
import { DataType, DataTypes } from '@dxos/schema';
|
|
14
14
|
|
|
15
15
|
import { BlueprintBuilder } from './blueprint';
|
|
@@ -23,7 +23,7 @@ import { createExaTool } from '../research/exa';
|
|
|
23
23
|
|
|
24
24
|
// TODO(burdon): Don't run on CI.
|
|
25
25
|
describe.skip('Blueprint', () => {
|
|
26
|
-
const
|
|
26
|
+
const aiClient = new EdgeAiServiceClient({
|
|
27
27
|
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
28
28
|
defaultGenerationOptions: {
|
|
29
29
|
// model: '@anthropic/claude-sonnet-4-20250514',
|
|
@@ -39,10 +39,10 @@ describe.skip('Blueprint', () => {
|
|
|
39
39
|
.step('Write a pitch deck for the product')
|
|
40
40
|
.build();
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
const machine = new BlueprintMachine(
|
|
42
|
+
const tools = new ToolRegistry([]);
|
|
43
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
44
44
|
setConsolePrinter(machine, true);
|
|
45
|
-
await machine.runToCompletion({
|
|
45
|
+
await machine.runToCompletion({ aiClient });
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test('email bot', { timeout: 60_000 }, async () => {
|
|
@@ -96,10 +96,10 @@ describe.skip('Blueprint', () => {
|
|
|
96
96
|
})
|
|
97
97
|
.build();
|
|
98
98
|
|
|
99
|
-
const
|
|
100
|
-
const machine = new BlueprintMachine(
|
|
99
|
+
const tools = new ToolRegistry([replyTool, labelTool]);
|
|
100
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
101
101
|
setConsolePrinter(machine);
|
|
102
|
-
await machine.runToCompletion({
|
|
102
|
+
await machine.runToCompletion({ aiClient, input: TEST_EMAILS[0] });
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
test.only('research', { timeout: 120_000 }, async () => {
|
|
@@ -108,14 +108,14 @@ describe.skip('Blueprint', () => {
|
|
|
108
108
|
|
|
109
109
|
const [org1] = [
|
|
110
110
|
db.add(
|
|
111
|
-
|
|
111
|
+
Obj.make(DataType.Organization, {
|
|
112
112
|
name: 'Exa',
|
|
113
113
|
website: 'https://exa.ai',
|
|
114
114
|
description: 'An AI-powered search engine company building search infrastructure for AI agents',
|
|
115
115
|
}),
|
|
116
116
|
),
|
|
117
117
|
db.add(
|
|
118
|
-
|
|
118
|
+
Obj.make(DataType.Organization, {
|
|
119
119
|
name: 'Cresta',
|
|
120
120
|
website: 'https://cresta.ai',
|
|
121
121
|
description: 'A company that builds AI agents',
|
|
@@ -125,25 +125,27 @@ describe.skip('Blueprint', () => {
|
|
|
125
125
|
|
|
126
126
|
await db.flush({ indexes: true });
|
|
127
127
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
const [exa, localSearch, graphWriter] = [
|
|
129
|
+
createExaTool({ apiKey: EXA_API_KEY }),
|
|
130
|
+
createLocalSearchTool(db),
|
|
131
|
+
createGraphWriterTool({ db, schema: DataTypes }),
|
|
132
|
+
];
|
|
132
133
|
|
|
133
134
|
const blueprint = BlueprintBuilder.create()
|
|
134
135
|
.step('Research founders of the organization. Do deep research.', {
|
|
135
|
-
tools: [
|
|
136
|
+
tools: [exa.id],
|
|
136
137
|
})
|
|
137
138
|
.step('Based on your research select matching entires that are already in the graph', {
|
|
138
|
-
tools: [
|
|
139
|
+
tools: [localSearch.id],
|
|
139
140
|
})
|
|
140
141
|
.step('Add researched data to the graph', {
|
|
141
|
-
tools: [
|
|
142
|
+
tools: [localSearch.id, graphWriter.id],
|
|
142
143
|
})
|
|
143
144
|
.build();
|
|
144
145
|
|
|
145
|
-
const
|
|
146
|
+
const tools = new ToolRegistry([exa, localSearch, graphWriter]);
|
|
147
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
146
148
|
setConsolePrinter(machine, true);
|
|
147
|
-
await machine.runToCompletion({
|
|
149
|
+
await machine.runToCompletion({ aiClient, input: org1 });
|
|
148
150
|
});
|
|
149
151
|
});
|
|
@@ -4,26 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
|
-
import { Obj, Type } from '@dxos/echo';
|
|
8
|
-
import { ObjectId } from '@dxos/keys';
|
|
7
|
+
import { Key, Obj, Type } from '@dxos/echo';
|
|
9
8
|
|
|
10
|
-
export const
|
|
11
|
-
id: ObjectId,
|
|
9
|
+
export const BlueprintStep = Schema.Struct({
|
|
10
|
+
id: Key.ObjectId,
|
|
12
11
|
instructions: Schema.String,
|
|
13
|
-
tools: Schema.Array(Schema.String),
|
|
12
|
+
tools: Schema.optional(Schema.Array(Schema.String)),
|
|
14
13
|
});
|
|
15
|
-
export
|
|
14
|
+
export interface BlueprintStep extends Schema.Schema.Type<typeof BlueprintStep> {}
|
|
16
15
|
|
|
17
|
-
export const
|
|
18
|
-
steps: Schema.Array(
|
|
16
|
+
export const BlueprintDefinition = Schema.Struct({
|
|
17
|
+
steps: Schema.Array(BlueprintStep.pipe(Schema.omit('id'))),
|
|
19
18
|
});
|
|
20
|
-
export
|
|
19
|
+
export interface BlueprintDefinition extends Schema.Schema.Type<typeof BlueprintDefinition> {}
|
|
21
20
|
|
|
22
21
|
export const Blueprint = Schema.Struct({
|
|
23
22
|
name: Schema.optional(Schema.String),
|
|
24
|
-
steps: Schema.Array(
|
|
25
|
-
}).pipe(
|
|
26
|
-
|
|
23
|
+
steps: Schema.Array(BlueprintStep),
|
|
24
|
+
}).pipe(
|
|
25
|
+
Type.Obj({
|
|
26
|
+
typename: 'dxos.org/type/Blueprint',
|
|
27
|
+
version: '0.1.0',
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
export interface Blueprint extends Schema.Schema.Type<typeof Blueprint> {}
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Blueprint builder API.
|
|
@@ -36,7 +40,7 @@ export namespace BlueprintBuilder {
|
|
|
36
40
|
|
|
37
41
|
step(instructions: string, options?: { tools?: string[] }): Builder {
|
|
38
42
|
this._steps.push({
|
|
39
|
-
id: ObjectId.random(),
|
|
43
|
+
id: Key.ObjectId.random(),
|
|
40
44
|
instructions,
|
|
41
45
|
tools: options?.tools ?? [],
|
|
42
46
|
});
|
|
@@ -57,7 +61,7 @@ export namespace BlueprintParser {
|
|
|
57
61
|
export const create = () => new Parser();
|
|
58
62
|
|
|
59
63
|
class Parser {
|
|
60
|
-
parse({ steps }:
|
|
64
|
+
parse({ steps }: BlueprintDefinition): Blueprint {
|
|
61
65
|
const builder = BlueprintBuilder.create();
|
|
62
66
|
for (const step of steps) {
|
|
63
67
|
builder.step(step.instructions, {
|
package/src/blueprint/logger.ts
CHANGED
|
@@ -7,16 +7,34 @@ import chalk from 'chalk';
|
|
|
7
7
|
|
|
8
8
|
import { ConsolePrinter } from '@dxos/ai';
|
|
9
9
|
import { type CleanupFn, combine } from '@dxos/async';
|
|
10
|
+
import { log } from '@dxos/log';
|
|
10
11
|
|
|
11
12
|
import { type Blueprint } from './blueprint';
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type BlueprintEvent,
|
|
15
|
+
type BlueprintLogger,
|
|
16
|
+
type BlueprintMachine,
|
|
17
|
+
type BlueprintMachineState,
|
|
18
|
+
} from './machine';
|
|
13
19
|
|
|
14
20
|
/* eslint-disable no-console */
|
|
15
21
|
|
|
16
22
|
// Force chalk colors on for tests.
|
|
17
23
|
chalk.level = 2;
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
// TODO(burdon): Factor out.
|
|
26
|
+
type ConsoleLogSignature = (message: string, arg?: any) => void;
|
|
27
|
+
|
|
28
|
+
interface Logger {
|
|
29
|
+
log: ConsoleLogSignature;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_LOGGER: Logger = { log: log.info };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reactive bufferedlogger.
|
|
36
|
+
*/
|
|
37
|
+
export class BufferedLogger implements Logger {
|
|
20
38
|
private _messages = signal<string[]>([]);
|
|
21
39
|
|
|
22
40
|
get messages(): ReadonlySignal<string[]> {
|
|
@@ -32,7 +50,35 @@ export class Logger {
|
|
|
32
50
|
}
|
|
33
51
|
}
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
// TODO(burdon): Reconcile with ConsolePrinter.
|
|
54
|
+
export class BlueprintLoggerAdapter implements BlueprintLogger {
|
|
55
|
+
constructor(private readonly logger: Logger = DEFAULT_LOGGER) {}
|
|
56
|
+
|
|
57
|
+
log(event: BlueprintEvent) {
|
|
58
|
+
switch (event.type) {
|
|
59
|
+
case 'begin':
|
|
60
|
+
this.logger.log('begin', { invocationId: event.invocationId });
|
|
61
|
+
break;
|
|
62
|
+
case 'end':
|
|
63
|
+
this.logger.log('end', { invocationId: event.invocationId });
|
|
64
|
+
break;
|
|
65
|
+
case 'step-start':
|
|
66
|
+
this.logger.log('step-start', { invocationId: event.invocationId, step: event.step });
|
|
67
|
+
break;
|
|
68
|
+
case 'step-complete':
|
|
69
|
+
this.logger.log('step-complete', { invocationId: event.invocationId, step: event.step });
|
|
70
|
+
break;
|
|
71
|
+
case 'message':
|
|
72
|
+
this.logger.log('message', { invocationId: event.invocationId, message: event.message });
|
|
73
|
+
break;
|
|
74
|
+
case 'block':
|
|
75
|
+
this.logger.log('block', { invocationId: event.invocationId, block: event.block });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const setLogger = (machine: BlueprintMachine, logger: BufferedLogger): CleanupFn => {
|
|
36
82
|
return combine(
|
|
37
83
|
machine.begin.on(() => {
|
|
38
84
|
logger.log('Starting...');
|
package/src/blueprint/machine.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Match, Schema } from 'effect';
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createTool,
|
|
9
|
-
type
|
|
9
|
+
type AiServiceClient,
|
|
10
10
|
Message,
|
|
11
11
|
type MessageContentBlock,
|
|
12
12
|
ToolResult,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type ToolRegistry,
|
|
15
15
|
} from '@dxos/ai';
|
|
16
16
|
import { Event } from '@dxos/async';
|
|
17
|
-
import {
|
|
17
|
+
import { Key, Obj } from '@dxos/echo';
|
|
18
18
|
import { type ObjectId } from '@dxos/keys';
|
|
19
19
|
import { log } from '@dxos/log';
|
|
20
20
|
import { isNonNullable } from '@dxos/util';
|
|
@@ -22,6 +22,17 @@ import { isNonNullable } from '@dxos/util';
|
|
|
22
22
|
import type { Blueprint, BlueprintStep } from './blueprint';
|
|
23
23
|
import { AISession } from '../session';
|
|
24
24
|
|
|
25
|
+
const SYSTEM_PROMPT = `
|
|
26
|
+
You are a smart Rule-Following Agent.
|
|
27
|
+
Rule-Following Agent executes the command that the user sent in the last message.
|
|
28
|
+
After doing the work, the Rule-Following Agent calls report tool.
|
|
29
|
+
If the Rule-Following agent believes that no action is needed, it calls the report tool with "skipped" status.
|
|
30
|
+
If the Rule-Following Agent is unable to perform the task, it calls the report tool with "bail" status.
|
|
31
|
+
Rule-Following Agent explains the reason it is unable to perform the task before bailing.
|
|
32
|
+
The Rule-Following Agent can express creativity and imagination in the way it performs the task.
|
|
33
|
+
The Rule-Following Agent precisely follows the instructions.
|
|
34
|
+
`;
|
|
35
|
+
|
|
25
36
|
export type BlueprintTraceStep = {
|
|
26
37
|
status: 'done' | 'bailed' | 'skipped';
|
|
27
38
|
stepId: ObjectId;
|
|
@@ -41,7 +52,7 @@ const INITIAL_STATE: BlueprintMachineState = {
|
|
|
41
52
|
};
|
|
42
53
|
|
|
43
54
|
type ExecutionOptions = {
|
|
44
|
-
|
|
55
|
+
aiClient: AiServiceClient;
|
|
45
56
|
|
|
46
57
|
/**
|
|
47
58
|
* Input to the blueprint.
|
|
@@ -49,6 +60,18 @@ type ExecutionOptions = {
|
|
|
49
60
|
input?: unknown;
|
|
50
61
|
};
|
|
51
62
|
|
|
63
|
+
export type BlueprintEvent =
|
|
64
|
+
| { type: 'begin'; invocationId: string }
|
|
65
|
+
| { type: 'end'; invocationId: string }
|
|
66
|
+
| { type: 'step-start'; invocationId: string; step: BlueprintStep }
|
|
67
|
+
| { type: 'step-complete'; invocationId: string; step: BlueprintStep }
|
|
68
|
+
| { type: 'message'; invocationId: string; message: Message }
|
|
69
|
+
| { type: 'block'; invocationId: string; block: MessageContentBlock };
|
|
70
|
+
|
|
71
|
+
export interface BlueprintLogger {
|
|
72
|
+
log(event: BlueprintEvent): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
/**
|
|
53
76
|
* Blueprint state machine.
|
|
54
77
|
*/
|
|
@@ -60,46 +83,67 @@ export class BlueprintMachine {
|
|
|
60
83
|
public readonly message = new Event<Message>();
|
|
61
84
|
public readonly block = new Event<MessageContentBlock>();
|
|
62
85
|
|
|
63
|
-
|
|
86
|
+
// TODO(burdon): Replace events with logger?
|
|
87
|
+
private logger?: BlueprintLogger;
|
|
88
|
+
|
|
89
|
+
private _state: BlueprintMachineState = structuredClone(INITIAL_STATE);
|
|
64
90
|
|
|
65
91
|
constructor(
|
|
66
92
|
readonly registry: ToolRegistry,
|
|
67
93
|
readonly blueprint: Blueprint,
|
|
68
94
|
) {}
|
|
69
95
|
|
|
96
|
+
get state(): BlueprintMachineState {
|
|
97
|
+
return this._state;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setLogger(logger: BlueprintLogger): this {
|
|
101
|
+
this.logger = logger;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
70
105
|
async runToCompletion(options: ExecutionOptions): Promise<void> {
|
|
71
|
-
|
|
106
|
+
const invocationId = Key.ObjectId.random();
|
|
107
|
+
log.info('runToCompletion', { invocationId, options });
|
|
72
108
|
|
|
73
109
|
this.begin.emit();
|
|
110
|
+
this.logger?.log({ type: 'begin', invocationId });
|
|
111
|
+
|
|
74
112
|
let firstStep = true;
|
|
75
|
-
while (this.
|
|
113
|
+
while (this._state.state !== 'done') {
|
|
76
114
|
const input = firstStep ? options.input : undefined;
|
|
77
|
-
|
|
78
115
|
firstStep = false;
|
|
79
|
-
this.state = await this._execStep(this.state, {
|
|
80
|
-
input,
|
|
81
|
-
aiService: options.aiService,
|
|
82
|
-
});
|
|
83
116
|
|
|
84
|
-
this.
|
|
117
|
+
this._state = await this._execStep(invocationId, this._state, { input, aiClient: options.aiClient });
|
|
118
|
+
|
|
119
|
+
const step = this.blueprint.steps.find((step) => step.id === this._state.trace.at(-1)?.stepId)!;
|
|
120
|
+
this.stepComplete.emit(step);
|
|
121
|
+
this.logger?.log({ type: 'step-complete', invocationId, step });
|
|
85
122
|
|
|
86
|
-
if (this.
|
|
123
|
+
if (this._state.state === 'bail') {
|
|
87
124
|
throw new Error('Agent unable to follow the blueprint');
|
|
88
125
|
}
|
|
89
126
|
}
|
|
90
127
|
|
|
91
128
|
this.end.emit();
|
|
129
|
+
this.logger?.log({ type: 'end', invocationId });
|
|
92
130
|
}
|
|
93
131
|
|
|
94
|
-
private async _execStep(
|
|
95
|
-
|
|
132
|
+
private async _execStep(
|
|
133
|
+
invocationId: string,
|
|
134
|
+
state: BlueprintMachineState,
|
|
135
|
+
options: ExecutionOptions,
|
|
136
|
+
): Promise<BlueprintMachineState> {
|
|
137
|
+
const prevStep = this.blueprint.steps.findIndex((step) => step.id === this._state.trace.at(-1)?.stepId);
|
|
96
138
|
if (prevStep === this.blueprint.steps.length - 1) {
|
|
97
139
|
throw new Error('Done execution blueprint');
|
|
98
140
|
}
|
|
99
141
|
|
|
100
142
|
const nextStep = this.blueprint.steps[prevStep + 1];
|
|
101
143
|
const onLastStep = prevStep === this.blueprint.steps.length - 2;
|
|
144
|
+
|
|
102
145
|
this.stepStart.emit(nextStep);
|
|
146
|
+
this.logger?.log({ type: 'step-start', invocationId, step: nextStep });
|
|
103
147
|
|
|
104
148
|
const ReportSchema = Schema.Struct({
|
|
105
149
|
status: Schema.Literal('done', 'bailed', 'skipped').annotations({
|
|
@@ -113,7 +157,7 @@ export class BlueprintMachine {
|
|
|
113
157
|
|
|
114
158
|
const report = createTool('system', {
|
|
115
159
|
name: 'report',
|
|
116
|
-
description: 'This tool reports that the agent has completed the task or is unable to do so',
|
|
160
|
+
description: 'This tool reports that the agent has completed the task or is unable to do so.',
|
|
117
161
|
schema: ReportSchema,
|
|
118
162
|
execute: async (input) => ToolResult.Break(input),
|
|
119
163
|
});
|
|
@@ -128,38 +172,31 @@ export class BlueprintMachine {
|
|
|
128
172
|
|
|
129
173
|
const inputMessages = options.input
|
|
130
174
|
? [
|
|
131
|
-
|
|
175
|
+
Obj.make(Message, {
|
|
132
176
|
role: 'user',
|
|
133
177
|
content: [taggedDataBlock('input', options.input)],
|
|
134
178
|
}),
|
|
135
179
|
]
|
|
136
180
|
: [];
|
|
137
|
-
inputMessages.forEach((message) =>
|
|
181
|
+
inputMessages.forEach((message) => {
|
|
182
|
+
this.message.emit(message);
|
|
183
|
+
this.logger?.log({ type: 'message', invocationId, message });
|
|
184
|
+
});
|
|
138
185
|
|
|
139
186
|
// TODO(wittjosiah): Warn if tool is not found.
|
|
140
|
-
const tools = nextStep.tools
|
|
187
|
+
const tools = nextStep.tools?.map((tool) => this.registry.get(tool)).filter(isNonNullable) ?? [];
|
|
141
188
|
const messages = await session.run({
|
|
142
|
-
systemPrompt:
|
|
143
|
-
You are a smart Rule-Following Agent.
|
|
144
|
-
Rule-Following Agent executes the command that the user sent in the last message.
|
|
145
|
-
After doing the work, the Rule-Following Agent calls report tool.
|
|
146
|
-
If the Rule-Following agent believes that no action is needed, it calls the report tool with "skipped" status.
|
|
147
|
-
If the Rule-Following Agent is unable to perform the task, it calls the report tool with "bail" status.
|
|
148
|
-
Rule-Following Agent explains the reason it is unable to perform the task before bailing.
|
|
149
|
-
The Rule-Following Agent can express creativity and imagination in the way it performs the task.
|
|
150
|
-
The Rule-Following Agent precisely follows the instructions.
|
|
151
|
-
`,
|
|
189
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
152
190
|
history: [...state.history, ...inputMessages],
|
|
153
191
|
tools: [...tools, report],
|
|
154
192
|
artifacts: [],
|
|
155
|
-
client: options.
|
|
193
|
+
client: options.aiClient,
|
|
156
194
|
prompt: nextStep.instructions,
|
|
157
195
|
});
|
|
158
196
|
|
|
159
197
|
const { messages: trimmedHistory, call: lastBlock } = popLastToolCall(messages);
|
|
160
|
-
|
|
161
198
|
if (!lastBlock) {
|
|
162
|
-
// TODO(dmaretskyi): Handle this with grace
|
|
199
|
+
// TODO(dmaretskyi): Handle this with grace.
|
|
163
200
|
throw new Error('Agent did not call the report tool');
|
|
164
201
|
}
|
|
165
202
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Obj } from '@dxos/echo';
|
|
6
6
|
import { DataType } from '@dxos/schema';
|
|
7
7
|
|
|
8
8
|
// TODO(burdon): Convert to queue.
|
|
9
9
|
export const TEST_EMAILS: DataType.Message[] = [
|
|
10
|
-
|
|
10
|
+
Obj.make(DataType.Message, {
|
|
11
11
|
properties: {
|
|
12
12
|
subject: 'Introduction email',
|
|
13
13
|
},
|
|
@@ -22,7 +22,7 @@ export const TEST_EMAILS: DataType.Message[] = [
|
|
|
22
22
|
],
|
|
23
23
|
created: new Date().toISOString(),
|
|
24
24
|
}),
|
|
25
|
-
|
|
25
|
+
Obj.make(DataType.Message, {
|
|
26
26
|
properties: {
|
|
27
27
|
subject: 'Question about integration',
|
|
28
28
|
},
|
|
@@ -37,7 +37,7 @@ export const TEST_EMAILS: DataType.Message[] = [
|
|
|
37
37
|
],
|
|
38
38
|
created: new Date().toISOString(),
|
|
39
39
|
}),
|
|
40
|
-
|
|
40
|
+
Obj.make(DataType.Message, {
|
|
41
41
|
properties: {
|
|
42
42
|
subject: 'MAKE MONEY FAST!!!',
|
|
43
43
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, test, beforeAll } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { EdgeAiServiceClient } from '@dxos/ai';
|
|
8
8
|
import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
|
|
9
9
|
import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
@@ -21,7 +21,7 @@ describe.skip('LLM EntityExtraction', () => {
|
|
|
21
21
|
executor = new FunctionExecutor(
|
|
22
22
|
new ServiceContainer().setServices({
|
|
23
23
|
ai: {
|
|
24
|
-
client: new
|
|
24
|
+
client: new EdgeAiServiceClient({
|
|
25
25
|
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
26
26
|
defaultGenerationOptions: {
|
|
27
27
|
// model: '@anthropic/claude-sonnet-4-20250514',
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Message } from '@dxos/ai';
|
|
6
|
+
import { Obj } from '@dxos/echo';
|
|
6
7
|
import { create } from '@dxos/echo-schema';
|
|
7
8
|
import { AiService, defineFunction, type FunctionDefinition } from '@dxos/functions';
|
|
8
9
|
import { DataType } from '@dxos/schema';
|
|
@@ -27,7 +28,7 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
|
|
|
27
28
|
client: ai.client,
|
|
28
29
|
systemPrompt: PROMPT,
|
|
29
30
|
history: [
|
|
30
|
-
|
|
31
|
+
Obj.make(Message, {
|
|
31
32
|
role: 'user',
|
|
32
33
|
content: [
|
|
33
34
|
{
|
package/src/extraction/quotes.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
|
+
import { Query, Filter } from '@dxos/echo';
|
|
7
8
|
import { type EchoDatabase } from '@dxos/echo-db';
|
|
8
|
-
import {
|
|
9
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
9
10
|
import { DXN } from '@dxos/keys';
|
|
10
11
|
import { log } from '@dxos/log';
|
|
11
12
|
|
package/src/research/graph.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Schema, identity, Option, SchemaAST } from 'effect';
|
|
|
6
6
|
|
|
7
7
|
import { createTool, ToolResult } from '@dxos/ai';
|
|
8
8
|
import type { Obj, Relation } from '@dxos/echo';
|
|
9
|
+
import { Query, Filter } from '@dxos/echo';
|
|
9
10
|
import { type EchoDatabase, type Queue } from '@dxos/echo-db';
|
|
10
11
|
import { isEncodedReference } from '@dxos/echo-protocol';
|
|
11
12
|
import {
|
|
@@ -13,13 +14,10 @@ import {
|
|
|
13
14
|
getSchemaTypename,
|
|
14
15
|
getTypeAnnotation,
|
|
15
16
|
getTypeIdentifierAnnotation,
|
|
16
|
-
type AnyEchoObject,
|
|
17
17
|
create,
|
|
18
|
-
Filter,
|
|
19
18
|
getEntityKind,
|
|
20
19
|
getSchemaDXN,
|
|
21
20
|
ObjectId,
|
|
22
|
-
Query,
|
|
23
21
|
ReferenceAnnotationId,
|
|
24
22
|
type BaseObject,
|
|
25
23
|
RelationSourceDXNId,
|
|
@@ -319,21 +317,21 @@ const preprocessSchema = (schema: Schema.Schema.AnyNoContext) => {
|
|
|
319
317
|
export const createGraphWriterTool = ({
|
|
320
318
|
db,
|
|
321
319
|
queue,
|
|
322
|
-
|
|
320
|
+
schema,
|
|
323
321
|
onDone = async (x) => x,
|
|
324
322
|
}: {
|
|
325
323
|
db: EchoDatabase;
|
|
326
324
|
queue?: Queue;
|
|
327
|
-
|
|
328
|
-
onDone?: (data:
|
|
325
|
+
schema: Schema.Schema.AnyNoContext[];
|
|
326
|
+
onDone?: (data: Obj.Any[]) => Promise<any>;
|
|
329
327
|
}) => {
|
|
330
328
|
return createTool('graph', {
|
|
331
329
|
name: 'writer',
|
|
332
330
|
description: 'Write to the local graph database',
|
|
333
|
-
schema: createExtractionSchema(
|
|
331
|
+
schema: createExtractionSchema(schema),
|
|
334
332
|
execute: async (input) => {
|
|
335
|
-
const data = await sanitizeObjects(
|
|
336
|
-
return ToolResult.Success(await onDone(data as
|
|
333
|
+
const data = await sanitizeObjects(schema, input as any, db, queue);
|
|
334
|
+
return ToolResult.Success(await onDone(data as Obj.Any[]));
|
|
337
335
|
},
|
|
338
336
|
});
|
|
339
337
|
};
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import { inspect } from 'node:util';
|
|
6
6
|
import { beforeAll, describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { EdgeAiServiceClient, OllamaAiServiceClient, structuredOutputParser } from '@dxos/ai';
|
|
9
9
|
import { AI_SERVICE_ENDPOINT, EXA_API_KEY } from '@dxos/ai/testing';
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
10
11
|
import { type EchoDatabase } from '@dxos/echo-db';
|
|
11
12
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
12
13
|
import { getSchemaDXN } from '@dxos/echo-schema';
|
|
13
14
|
import { ConfiguredCredentialsService, FunctionExecutor, ServiceContainer, TracingService } from '@dxos/functions';
|
|
14
|
-
import { live } from '@dxos/live-object';
|
|
15
15
|
import { DataType, DataTypes } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { createExtractionSchema, getSanitizedSchemaName } from './graph';
|
|
@@ -20,7 +20,7 @@ import { researchFn } from './research';
|
|
|
20
20
|
const REMOTE_AI = true;
|
|
21
21
|
const MOCK_SEARCH = false;
|
|
22
22
|
|
|
23
|
-
describe('Research', () => {
|
|
23
|
+
describe.skip('Research', () => {
|
|
24
24
|
let builder: EchoTestBuilder;
|
|
25
25
|
let db: EchoDatabase;
|
|
26
26
|
let executor: FunctionExecutor;
|
|
@@ -28,21 +28,22 @@ describe('Research', () => {
|
|
|
28
28
|
beforeAll(async () => {
|
|
29
29
|
// TODO(dmaretskyi): Helper to scaffold this from a config.
|
|
30
30
|
builder = await new EchoTestBuilder().open();
|
|
31
|
-
|
|
32
|
-
db =
|
|
31
|
+
|
|
32
|
+
db = (await builder.createDatabase({ indexing: { vector: true } })).db;
|
|
33
33
|
db.graph.schemaRegistry.addSchema(DataTypes);
|
|
34
|
+
|
|
34
35
|
executor = new FunctionExecutor(
|
|
35
36
|
new ServiceContainer().setServices({
|
|
36
37
|
ai: {
|
|
37
38
|
client: REMOTE_AI
|
|
38
|
-
? new
|
|
39
|
+
? new EdgeAiServiceClient({
|
|
39
40
|
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
40
41
|
defaultGenerationOptions: {
|
|
41
42
|
// model: '@anthropic/claude-sonnet-4-20250514',
|
|
42
43
|
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
43
44
|
},
|
|
44
45
|
})
|
|
45
|
-
: new
|
|
46
|
+
: new OllamaAiServiceClient({
|
|
46
47
|
overrides: {
|
|
47
48
|
model: 'llama3.1:8b',
|
|
48
49
|
},
|
|
@@ -57,7 +58,7 @@ describe('Research', () => {
|
|
|
57
58
|
|
|
58
59
|
test('should generate a research report', { timeout: 300_000 }, async () => {
|
|
59
60
|
db.add(
|
|
60
|
-
|
|
61
|
+
Obj.make(DataType.Organization, {
|
|
61
62
|
name: 'Notion',
|
|
62
63
|
website: 'https://www.notion.com',
|
|
63
64
|
}),
|
package/src/research/research.ts
CHANGED
|
@@ -41,7 +41,7 @@ export const researchFn = defineFunction({
|
|
|
41
41
|
const ai = context.getService(AiService);
|
|
42
42
|
const credentials = context.getService(CredentialsService);
|
|
43
43
|
const { db } = context.getService(DatabaseService);
|
|
44
|
-
// const queues = context.getService(
|
|
44
|
+
// const queues = context.getService(QueueService);
|
|
45
45
|
const tracing = context.getService(TracingService);
|
|
46
46
|
|
|
47
47
|
tracing.write(create(AgentStatusReport, { message: 'Researching...' }));
|
|
@@ -60,7 +60,7 @@ export const researchFn = defineFunction({
|
|
|
60
60
|
});
|
|
61
61
|
session.streamEvent.on((event) => log('stream', { event }));
|
|
62
62
|
|
|
63
|
-
const graphWriteTool = createGraphWriterTool({ db,
|
|
63
|
+
const graphWriteTool = createGraphWriterTool({ db, schema: DataTypes });
|
|
64
64
|
log.info('graphWriteTool', { schema: graphWriteTool.parameters });
|
|
65
65
|
|
|
66
66
|
// TODO(dmaretskyi): Consider adding this pattern as the "Graph" output mode for the session.
|