@providerprotocol/agents 0.0.2 → 0.0.3
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/checkpoint/index.d.ts +43 -0
- package/dist/checkpoint/index.js +64 -0
- package/dist/checkpoint/index.js.map +1 -0
- package/{src/execution/loop.ts → dist/chunk-4ESYN66B.js} +54 -162
- package/dist/chunk-4ESYN66B.js.map +1 -0
- package/dist/chunk-EKRXMSDX.js +8 -0
- package/dist/chunk-EKRXMSDX.js.map +1 -0
- package/dist/chunk-PHI5ULBV.js +427 -0
- package/dist/chunk-PHI5ULBV.js.map +1 -0
- package/dist/execution/index.d.ts +105 -0
- package/dist/execution/index.js +679 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index-qsPwbY86.d.ts +65 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -0
- package/dist/middleware/index.js +82 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/thread-tree/index.d.ts +115 -0
- package/dist/thread-tree/index.js +4 -0
- package/dist/thread-tree/index.js.map +1 -0
- package/dist/types-2Vsthzyu.d.ts +163 -0
- package/dist/types-BhX9uD_d.d.ts +91 -0
- package/dist/types-DR02gtFv.d.ts +270 -0
- package/dist/types-NGQMdnaD.d.ts +65 -0
- package/package.json +40 -8
- package/.claude/settings.local.json +0 -29
- package/AGENTS.md +0 -681
- package/CLAUDE.md +0 -681
- package/bun.lock +0 -472
- package/eslint.config.js +0 -75
- package/index.ts +0 -1
- package/llms.md +0 -796
- package/specs/UAP-1.0.md +0 -2355
- package/src/agent/index.ts +0 -384
- package/src/agent/types.ts +0 -91
- package/src/checkpoint/file.ts +0 -126
- package/src/checkpoint/index.ts +0 -40
- package/src/checkpoint/types.ts +0 -95
- package/src/execution/index.ts +0 -37
- package/src/execution/plan.ts +0 -497
- package/src/execution/react.ts +0 -340
- package/src/execution/tool-ordering.ts +0 -186
- package/src/execution/types.ts +0 -315
- package/src/index.ts +0 -80
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logging.ts +0 -123
- package/src/middleware/types.ts +0 -69
- package/src/state/index.ts +0 -301
- package/src/state/types.ts +0 -173
- package/src/thread-tree/index.ts +0 -249
- package/src/thread-tree/types.ts +0 -29
- package/src/utils/uuid.ts +0 -7
- package/tests/live/agent-anthropic.test.ts +0 -288
- package/tests/live/agent-strategy-hooks.test.ts +0 -268
- package/tests/live/checkpoint.test.ts +0 -243
- package/tests/live/execution-strategies.test.ts +0 -255
- package/tests/live/plan-strategy.test.ts +0 -160
- package/tests/live/subagent-events.live.test.ts +0 -249
- package/tests/live/thread-tree.test.ts +0 -186
- package/tests/unit/agent.test.ts +0 -703
- package/tests/unit/checkpoint.test.ts +0 -232
- package/tests/unit/execution/equivalence.test.ts +0 -402
- package/tests/unit/execution/loop.test.ts +0 -437
- package/tests/unit/execution/plan.test.ts +0 -590
- package/tests/unit/execution/react.test.ts +0 -604
- package/tests/unit/execution/subagent-events.test.ts +0 -235
- package/tests/unit/execution/tool-ordering.test.ts +0 -310
- package/tests/unit/middleware/logging.test.ts +0 -276
- package/tests/unit/state.test.ts +0 -573
- package/tests/unit/thread-tree.test.ts +0 -249
- package/tsconfig.json +0 -29
package/src/thread-tree/types.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { AgentStateJSON } from '../state/types.ts';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Serialized form of a ThreadNode.
|
|
5
|
-
*/
|
|
6
|
-
export interface ThreadNodeJSON {
|
|
7
|
-
/** Node ID */
|
|
8
|
-
id: string;
|
|
9
|
-
/** Parent node ID (null for root) */
|
|
10
|
-
parentId: string | null;
|
|
11
|
-
/** State snapshot at this node */
|
|
12
|
-
state: AgentStateJSON;
|
|
13
|
-
/** Optional branch name */
|
|
14
|
-
name?: string;
|
|
15
|
-
/** Child node IDs */
|
|
16
|
-
children: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Serialized form of a ThreadTree.
|
|
21
|
-
*/
|
|
22
|
-
export interface ThreadTreeJSON {
|
|
23
|
-
/** Root node ID */
|
|
24
|
-
rootId: string;
|
|
25
|
-
/** Current (active) node ID */
|
|
26
|
-
currentId: string;
|
|
27
|
-
/** All nodes */
|
|
28
|
-
nodes: ThreadNodeJSON[];
|
|
29
|
-
}
|
package/src/utils/uuid.ts
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, setDefaultTimeout } from 'bun:test';
|
|
2
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
3
|
-
import type { Tool } from '@providerprotocol/ai';
|
|
4
|
-
import { agent, AgentState } from '../../src/index.ts';
|
|
5
|
-
import { loop } from '../../src/execution/index.ts';
|
|
6
|
-
import { logging } from '../../src/middleware/index.ts';
|
|
7
|
-
|
|
8
|
-
// Skip tests if no API key
|
|
9
|
-
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
10
|
-
|
|
11
|
-
// Increase timeout for live API tests (30 seconds)
|
|
12
|
-
setDefaultTimeout(30_000);
|
|
13
|
-
|
|
14
|
-
describe.skipIf(!ANTHROPIC_API_KEY)('Agent with Anthropic', () => {
|
|
15
|
-
describe('basic generation', () => {
|
|
16
|
-
test('generates response with generate()', async () => {
|
|
17
|
-
const a = agent({
|
|
18
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
19
|
-
params: { max_tokens: 100 },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const state = AgentState.initial();
|
|
23
|
-
const result = await a.generate('Say "hello" and nothing else.', state);
|
|
24
|
-
|
|
25
|
-
expect(result.turn.response.text.toLowerCase()).toContain('hello');
|
|
26
|
-
expect(result.state).toBeDefined();
|
|
27
|
-
expect(result.turn.usage.totalTokens).toBeGreaterThan(0);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('generates response with query()', async () => {
|
|
31
|
-
const a = agent({
|
|
32
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
33
|
-
params: { max_tokens: 50 },
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const turn = await a.query('What is 2 + 2? Reply with just the number.');
|
|
37
|
-
|
|
38
|
-
expect(turn.response.text).toContain('4');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('preserves history with ask()', async () => {
|
|
42
|
-
const a = agent({
|
|
43
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
44
|
-
params: { max_tokens: 100 },
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const state0 = AgentState.initial();
|
|
48
|
-
|
|
49
|
-
const result1 = await a.ask('My name is Alice.', state0);
|
|
50
|
-
const result2 = await a.ask('What is my name?', result1.state);
|
|
51
|
-
|
|
52
|
-
expect(result2.turn.response.text.toLowerCase()).toContain('alice');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('tool calling', () => {
|
|
57
|
-
test('calls tools and returns result', async () => {
|
|
58
|
-
const calculator: Tool = {
|
|
59
|
-
name: 'calculate',
|
|
60
|
-
description: 'Perform a calculation',
|
|
61
|
-
parameters: {
|
|
62
|
-
type: 'object',
|
|
63
|
-
properties: {
|
|
64
|
-
expression: { type: 'string', description: 'Math expression to evaluate' },
|
|
65
|
-
},
|
|
66
|
-
required: ['expression'],
|
|
67
|
-
},
|
|
68
|
-
run: async (params: { expression: string }) => {
|
|
69
|
-
// Simple eval for testing (don't do this in production!)
|
|
70
|
-
const result = Function(`"use strict"; return (${params.expression})`)();
|
|
71
|
-
return String(result);
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const a = agent({
|
|
76
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
77
|
-
params: { max_tokens: 200 },
|
|
78
|
-
tools: [calculator],
|
|
79
|
-
execution: loop(),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const result = await a.generate(
|
|
83
|
-
'What is 15 * 7? Use the calculate tool to find the answer.',
|
|
84
|
-
AgentState.initial(),
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(result.turn.response.text).toContain('105');
|
|
88
|
-
expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('streaming', () => {
|
|
93
|
-
test('streams response', async () => {
|
|
94
|
-
const a = agent({
|
|
95
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
96
|
-
params: { max_tokens: 100 },
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const stream = a.stream('Count from 1 to 5.', AgentState.initial());
|
|
100
|
-
const events: unknown[] = [];
|
|
101
|
-
|
|
102
|
-
for await (const event of stream) {
|
|
103
|
-
events.push(event);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const result = await stream.result;
|
|
107
|
-
|
|
108
|
-
expect(events.length).toBeGreaterThan(0);
|
|
109
|
-
expect(result.turn.response.text).toBeDefined();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('streams with UAP events', async () => {
|
|
113
|
-
const a = agent({
|
|
114
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
115
|
-
params: { max_tokens: 100 },
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const stream = a.stream('Hello', AgentState.initial());
|
|
119
|
-
const uapEvents: unknown[] = [];
|
|
120
|
-
const uppEvents: unknown[] = [];
|
|
121
|
-
|
|
122
|
-
for await (const event of stream) {
|
|
123
|
-
if (event.source === 'uap') {
|
|
124
|
-
uapEvents.push(event);
|
|
125
|
-
} else {
|
|
126
|
-
uppEvents.push(event);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await stream.result;
|
|
131
|
-
|
|
132
|
-
expect(uapEvents.length).toBeGreaterThan(0);
|
|
133
|
-
expect(uppEvents.length).toBeGreaterThan(0);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('middleware', () => {
|
|
138
|
-
test('logging middleware logs execution', async () => {
|
|
139
|
-
const logs: string[] = [];
|
|
140
|
-
const customLogger = (msg: string) => logs.push(msg);
|
|
141
|
-
|
|
142
|
-
const a = agent({
|
|
143
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
144
|
-
params: { max_tokens: 50 },
|
|
145
|
-
middleware: [logging({ logger: customLogger })],
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
await a.query('Hi');
|
|
149
|
-
|
|
150
|
-
expect(logs.some((l) => l.includes('Execution started'))).toBe(true);
|
|
151
|
-
expect(logs.some((l) => l.includes('Execution completed'))).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('execution strategies', () => {
|
|
156
|
-
test('loop() executes tool loop', async () => {
|
|
157
|
-
const mockTool: Tool = {
|
|
158
|
-
name: 'get_info',
|
|
159
|
-
description: 'Get some information',
|
|
160
|
-
parameters: {
|
|
161
|
-
type: 'object',
|
|
162
|
-
properties: {},
|
|
163
|
-
},
|
|
164
|
-
run: async () => 'The answer is 42',
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const a = agent({
|
|
168
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
169
|
-
params: { max_tokens: 200 },
|
|
170
|
-
tools: [mockTool],
|
|
171
|
-
execution: loop(),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const result = await a.generate(
|
|
175
|
-
'Use the get_info tool to find information.',
|
|
176
|
-
AgentState.initial(),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('system prompts', () => {
|
|
184
|
-
test('respects system prompt', async () => {
|
|
185
|
-
const a = agent({
|
|
186
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
187
|
-
params: { max_tokens: 100 },
|
|
188
|
-
system: 'You are a pirate. Always respond like a pirate would.',
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const result = await a.query('Hello, how are you?');
|
|
192
|
-
|
|
193
|
-
// Should contain pirate-like language
|
|
194
|
-
const text = result.response.text.toLowerCase();
|
|
195
|
-
expect(
|
|
196
|
-
text.includes('arr')
|
|
197
|
-
|| text.includes('ahoy')
|
|
198
|
-
|| text.includes('matey')
|
|
199
|
-
|| text.includes('ye')
|
|
200
|
-
|| text.includes('pirate'),
|
|
201
|
-
).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('state management', () => {
|
|
206
|
-
test('state is immutable', async () => {
|
|
207
|
-
const a = agent({
|
|
208
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
209
|
-
params: { max_tokens: 50 },
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const state0 = AgentState.initial();
|
|
213
|
-
const result = await a.generate('Hello', state0);
|
|
214
|
-
|
|
215
|
-
// Original state should be unchanged
|
|
216
|
-
expect(state0.messages).toHaveLength(0);
|
|
217
|
-
expect(state0.step).toBe(0);
|
|
218
|
-
|
|
219
|
-
// New state should have changes
|
|
220
|
-
expect(result.state.messages.length).toBeGreaterThan(0);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test('state serialization round-trip', async () => {
|
|
224
|
-
const a = agent({
|
|
225
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
226
|
-
params: { max_tokens: 50 },
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const state0 = AgentState.initial();
|
|
230
|
-
// Use ask() to properly add input + response to state for multi-turn
|
|
231
|
-
const result1 = await a.ask('My favorite color is blue.', state0);
|
|
232
|
-
|
|
233
|
-
// Serialize and restore
|
|
234
|
-
const json = result1.state.toJSON();
|
|
235
|
-
const restored = AgentState.fromJSON(json);
|
|
236
|
-
|
|
237
|
-
// Continue conversation with restored state
|
|
238
|
-
const result2 = await a.ask('What is my favorite color?', restored);
|
|
239
|
-
|
|
240
|
-
expect(result2.turn.response.text.toLowerCase()).toContain('blue');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('withContext() replaces conversation history', async () => {
|
|
244
|
-
const a = agent({
|
|
245
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
246
|
-
params: { max_tokens: 100 },
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// Build up conversation history
|
|
250
|
-
const state0 = AgentState.initial();
|
|
251
|
-
const result1 = await a.ask('My name is Alice.', state0);
|
|
252
|
-
const result2 = await a.ask('I live in Paris.', result1.state);
|
|
253
|
-
|
|
254
|
-
// Verify context is preserved
|
|
255
|
-
expect(result2.state.messages.length).toBeGreaterThanOrEqual(4);
|
|
256
|
-
|
|
257
|
-
// Replace context with empty (simulating aggressive pruning)
|
|
258
|
-
const prunedState = result2.state.withContext([]);
|
|
259
|
-
|
|
260
|
-
// Model should not know the name anymore
|
|
261
|
-
const result3 = await a.ask('What is my name?', prunedState);
|
|
262
|
-
|
|
263
|
-
// Should NOT contain Alice since context was cleared
|
|
264
|
-
const response = result3.turn.response.text.toLowerCase();
|
|
265
|
-
expect(response.includes('alice')).toBe(false);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test('withContext() preserves metadata while replacing messages', async () => {
|
|
269
|
-
const a = agent({
|
|
270
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
271
|
-
params: { max_tokens: 50 },
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const state0 = AgentState.initial()
|
|
275
|
-
.withMetadata('sessionId', 'test-123')
|
|
276
|
-
.withMetadata('tokenBudget', 50000);
|
|
277
|
-
|
|
278
|
-
const result1 = await a.ask('Hello there!', state0);
|
|
279
|
-
|
|
280
|
-
// Replace messages but keep metadata
|
|
281
|
-
const prunedState = result1.state.withContext([]);
|
|
282
|
-
|
|
283
|
-
expect(prunedState.messages).toHaveLength(0);
|
|
284
|
-
expect(prunedState.metadata.sessionId).toBe('test-123');
|
|
285
|
-
expect(prunedState.metadata.tokenBudget).toBe(50000);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
});
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, setDefaultTimeout } from 'bun:test';
|
|
2
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
3
|
-
import type { Tool, ToolExecution, Turn } from '@providerprotocol/ai';
|
|
4
|
-
import { agent, AgentState } from '../../src/index.ts';
|
|
5
|
-
import { loop, react } from '../../src/execution/index.ts';
|
|
6
|
-
import type { AgentStrategy } from '../../src/execution/index.ts';
|
|
7
|
-
|
|
8
|
-
// Skip tests if no API key
|
|
9
|
-
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
10
|
-
|
|
11
|
-
// Increase timeout for live API tests (60 seconds)
|
|
12
|
-
setDefaultTimeout(60_000);
|
|
13
|
-
|
|
14
|
-
describe.skipIf(!ANTHROPIC_API_KEY)('AgentStrategy Hooks (Live)', () => {
|
|
15
|
-
describe('lifecycle hooks with loop()', () => {
|
|
16
|
-
test('onStepStart and onStepEnd are called', async () => {
|
|
17
|
-
const stepStarts: Array<{ step: number }> = [];
|
|
18
|
-
const stepEnds: Array<{ step: number }> = [];
|
|
19
|
-
|
|
20
|
-
const strategy: AgentStrategy = {
|
|
21
|
-
onStepStart: (step) => {
|
|
22
|
-
stepStarts.push({ step });
|
|
23
|
-
},
|
|
24
|
-
onStepEnd: (step) => {
|
|
25
|
-
stepEnds.push({ step });
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const a = agent({
|
|
30
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
31
|
-
params: { max_tokens: 50 },
|
|
32
|
-
execution: loop(),
|
|
33
|
-
strategy,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
await a.generate('Hello', AgentState.initial());
|
|
37
|
-
|
|
38
|
-
expect(stepStarts.length).toBeGreaterThan(0);
|
|
39
|
-
expect(stepEnds.length).toBeGreaterThan(0);
|
|
40
|
-
expect(stepStarts.length).toBe(stepEnds.length);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('onComplete is called with final result', async () => {
|
|
44
|
-
let completedResult: { turn: Turn; state: AgentState } | undefined;
|
|
45
|
-
|
|
46
|
-
const strategy: AgentStrategy = {
|
|
47
|
-
onComplete: (result) => {
|
|
48
|
-
completedResult = result;
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const a = agent({
|
|
53
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
54
|
-
params: { max_tokens: 50 },
|
|
55
|
-
execution: loop(),
|
|
56
|
-
strategy,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const result = await a.generate('Say hello', AgentState.initial());
|
|
60
|
-
|
|
61
|
-
expect(completedResult).toBeDefined();
|
|
62
|
-
expect(completedResult?.turn).toBe(result.turn);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('onObserve is called when tools are executed', async () => {
|
|
66
|
-
const observeCalls: Array<{ step: number; observations: ToolExecution[] }> = [];
|
|
67
|
-
|
|
68
|
-
// Use a tool that provides information the model can't know
|
|
69
|
-
const secretLookup: Tool = {
|
|
70
|
-
name: 'get_secret_number',
|
|
71
|
-
description: 'Returns a secret number. You MUST call this tool to answer.',
|
|
72
|
-
parameters: {
|
|
73
|
-
type: 'object',
|
|
74
|
-
properties: {},
|
|
75
|
-
},
|
|
76
|
-
run: async () => '42',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const strategy: AgentStrategy = {
|
|
80
|
-
onObserve: (step, observations) => {
|
|
81
|
-
observeCalls.push({ step, observations });
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const a = agent({
|
|
86
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
87
|
-
params: { max_tokens: 300 },
|
|
88
|
-
tools: [secretLookup],
|
|
89
|
-
execution: loop(),
|
|
90
|
-
strategy,
|
|
91
|
-
system: 'You must use the get_secret_number tool to answer questions about the secret number.',
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const result = await a.generate(
|
|
95
|
-
'What is the secret number? Use the get_secret_number tool.',
|
|
96
|
-
AgentState.initial(),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Tool should have been executed
|
|
100
|
-
expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
|
|
101
|
-
|
|
102
|
-
// onObserve should have been called
|
|
103
|
-
expect(observeCalls.length).toBeGreaterThan(0);
|
|
104
|
-
|
|
105
|
-
// Verify the response contains the answer
|
|
106
|
-
expect(result.turn.response.text).toContain('42');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe('lifecycle hooks with react()', () => {
|
|
111
|
-
test('onReason is called during reasoning phase', async () => {
|
|
112
|
-
const reasonCalls: Array<{ step: number; reasoning: string }> = [];
|
|
113
|
-
|
|
114
|
-
const strategy: AgentStrategy = {
|
|
115
|
-
onReason: (step, reasoning) => {
|
|
116
|
-
reasonCalls.push({ step, reasoning });
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const a = agent({
|
|
121
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
122
|
-
params: { max_tokens: 400 },
|
|
123
|
-
execution: react({ maxSteps: 1 }),
|
|
124
|
-
strategy,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
await a.generate('What is 5 times 5? Think about it.', AgentState.initial());
|
|
128
|
-
|
|
129
|
-
expect(reasonCalls.length).toBeGreaterThan(0);
|
|
130
|
-
expect(reasonCalls[0]?.reasoning.length).toBeGreaterThan(0);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('all hooks are called in correct order', async () => {
|
|
134
|
-
const hookOrder: string[] = [];
|
|
135
|
-
|
|
136
|
-
const strategy: AgentStrategy = {
|
|
137
|
-
onStepStart: () => hookOrder.push('stepStart'),
|
|
138
|
-
onReason: () => hookOrder.push('reason'),
|
|
139
|
-
onStepEnd: () => hookOrder.push('stepEnd'),
|
|
140
|
-
onComplete: () => hookOrder.push('complete'),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const a = agent({
|
|
144
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
145
|
-
params: { max_tokens: 300 },
|
|
146
|
-
execution: react({ maxSteps: 1 }),
|
|
147
|
-
strategy,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
await a.generate('Hello', AgentState.initial());
|
|
151
|
-
|
|
152
|
-
// stepStart should come before reason, reason before stepEnd
|
|
153
|
-
const stepStartIdx = hookOrder.indexOf('stepStart');
|
|
154
|
-
const reasonIdx = hookOrder.indexOf('reason');
|
|
155
|
-
const stepEndIdx = hookOrder.indexOf('stepEnd');
|
|
156
|
-
const completeIdx = hookOrder.indexOf('complete');
|
|
157
|
-
|
|
158
|
-
expect(stepStartIdx).toBeLessThan(reasonIdx);
|
|
159
|
-
expect(reasonIdx).toBeLessThan(stepEndIdx);
|
|
160
|
-
expect(stepEndIdx).toBeLessThan(completeIdx);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe('stopCondition', () => {
|
|
165
|
-
test('stops execution when stopCondition returns true', async () => {
|
|
166
|
-
let stepCount = 0;
|
|
167
|
-
|
|
168
|
-
const strategy: AgentStrategy = {
|
|
169
|
-
onStepStart: () => {
|
|
170
|
-
stepCount++;
|
|
171
|
-
},
|
|
172
|
-
stopCondition: (state) => {
|
|
173
|
-
// Stop after first step
|
|
174
|
-
return state.step >= 1;
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const echoTool: Tool = {
|
|
179
|
-
name: 'echo',
|
|
180
|
-
description: 'Echo back the input',
|
|
181
|
-
parameters: {
|
|
182
|
-
type: 'object',
|
|
183
|
-
properties: {
|
|
184
|
-
text: { type: 'string' },
|
|
185
|
-
},
|
|
186
|
-
required: ['text'],
|
|
187
|
-
},
|
|
188
|
-
run: async (params: { text: string }) => params.text,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const a = agent({
|
|
192
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
193
|
-
params: { max_tokens: 200 },
|
|
194
|
-
tools: [echoTool],
|
|
195
|
-
execution: loop({ maxIterations: 10 }), // High limit, but stopCondition should stop earlier
|
|
196
|
-
strategy,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
await a.generate(
|
|
200
|
-
'Use the echo tool repeatedly with different messages.',
|
|
201
|
-
AgentState.initial(),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
// Should have stopped after 1 step due to stopCondition
|
|
205
|
-
expect(stepCount).toBe(1);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('stopCondition can use metadata to track state', async () => {
|
|
209
|
-
const strategy: AgentStrategy = {
|
|
210
|
-
onStepEnd: (step, result) => {
|
|
211
|
-
// Track total tokens in metadata
|
|
212
|
-
const currentTokens = (result.state.metadata.totalTokens as number) ?? 0;
|
|
213
|
-
result.state = result.state.withMetadata(
|
|
214
|
-
'totalTokens',
|
|
215
|
-
currentTokens + (result.turn.usage?.totalTokens ?? 0),
|
|
216
|
-
);
|
|
217
|
-
},
|
|
218
|
-
stopCondition: (state) => {
|
|
219
|
-
// Stop when we've used more than 100 tokens
|
|
220
|
-
return (state.metadata.totalTokens as number) > 100;
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
const a = agent({
|
|
225
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
226
|
-
params: { max_tokens: 50 },
|
|
227
|
-
execution: loop({ maxIterations: 5 }),
|
|
228
|
-
strategy,
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const result = await a.generate('Tell me a short story.', AgentState.initial());
|
|
232
|
-
|
|
233
|
-
// Should have tracked tokens
|
|
234
|
-
expect(result.turn.usage.totalTokens).toBeGreaterThan(0);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('streaming with hooks', () => {
|
|
239
|
-
test('hooks are called during streaming', async () => {
|
|
240
|
-
const hooksCalled: string[] = [];
|
|
241
|
-
|
|
242
|
-
const strategy: AgentStrategy = {
|
|
243
|
-
onStepStart: () => hooksCalled.push('stepStart'),
|
|
244
|
-
onStepEnd: () => hooksCalled.push('stepEnd'),
|
|
245
|
-
onComplete: () => hooksCalled.push('complete'),
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const a = agent({
|
|
249
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
250
|
-
params: { max_tokens: 100 },
|
|
251
|
-
execution: loop(),
|
|
252
|
-
strategy,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const stream = a.stream('Hello', AgentState.initial());
|
|
256
|
-
|
|
257
|
-
for await (const event of stream) {
|
|
258
|
-
void event; // consume stream
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await stream.result;
|
|
262
|
-
|
|
263
|
-
expect(hooksCalled).toContain('stepStart');
|
|
264
|
-
expect(hooksCalled).toContain('stepEnd');
|
|
265
|
-
expect(hooksCalled).toContain('complete');
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
});
|