@aigne/core 1.34.0 → 1.37.0
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/CHANGELOG.md +26 -0
- package/lib/cjs/agents/agent.d.ts +71 -10
- package/lib/cjs/agents/agent.js +73 -28
- package/lib/cjs/agents/ai-agent.js +6 -3
- package/lib/cjs/agents/team-agent.d.ts +93 -1
- package/lib/cjs/agents/team-agent.js +45 -17
- package/lib/cjs/aigne/context.d.ts +1 -0
- package/lib/cjs/aigne/context.js +25 -10
- package/lib/cjs/loader/agent-js.d.ts +2 -17
- package/lib/cjs/loader/agent-js.js +4 -16
- package/lib/cjs/loader/agent-yaml.d.ts +29 -8
- package/lib/cjs/loader/agent-yaml.js +44 -15
- package/lib/cjs/loader/index.d.ts +2 -2
- package/lib/cjs/loader/index.js +55 -16
- package/lib/cjs/loader/schema.d.ts +10 -0
- package/lib/cjs/loader/schema.js +17 -1
- package/lib/cjs/utils/type-utils.d.ts +1 -1
- package/lib/cjs/utils/type-utils.js +2 -4
- package/lib/dts/agents/agent.d.ts +71 -10
- package/lib/dts/agents/team-agent.d.ts +93 -1
- package/lib/dts/aigne/context.d.ts +1 -0
- package/lib/dts/loader/agent-js.d.ts +2 -17
- package/lib/dts/loader/agent-yaml.d.ts +29 -8
- package/lib/dts/loader/index.d.ts +2 -2
- package/lib/dts/loader/schema.d.ts +10 -0
- package/lib/dts/utils/type-utils.d.ts +1 -1
- package/lib/esm/agents/agent.d.ts +71 -10
- package/lib/esm/agents/agent.js +73 -28
- package/lib/esm/agents/ai-agent.js +6 -3
- package/lib/esm/agents/team-agent.d.ts +93 -1
- package/lib/esm/agents/team-agent.js +44 -16
- package/lib/esm/aigne/context.d.ts +1 -0
- package/lib/esm/aigne/context.js +25 -10
- package/lib/esm/loader/agent-js.d.ts +2 -17
- package/lib/esm/loader/agent-js.js +4 -13
- package/lib/esm/loader/agent-yaml.d.ts +29 -8
- package/lib/esm/loader/agent-yaml.js +44 -13
- package/lib/esm/loader/index.d.ts +2 -2
- package/lib/esm/loader/index.js +56 -17
- package/lib/esm/loader/schema.d.ts +10 -0
- package/lib/esm/loader/schema.js +12 -0
- package/lib/esm/utils/type-utils.d.ts +1 -1
- package/lib/esm/utils/type-utils.js +2 -4
- package/package.json +3 -3
package/lib/esm/agents/agent.js
CHANGED
|
@@ -2,9 +2,17 @@ import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
|
|
|
2
2
|
import { ZodObject, z } from "zod";
|
|
3
3
|
import { logger } from "../utils/logger.js";
|
|
4
4
|
import { agentResponseStreamToObject, asyncGeneratorToReadableStream, isAsyncGenerator, objectToAgentResponseStream, onAgentResponseStreamEnd, } from "../utils/stream-utils.js";
|
|
5
|
-
import { checkArguments, createAccessorArray, flat, isEmpty, isRecord, } from "../utils/type-utils.js";
|
|
5
|
+
import { checkArguments, createAccessorArray, flat, isEmpty, isNil, isRecord, } from "../utils/type-utils.js";
|
|
6
6
|
import { replaceTransferAgentToName, transferToAgentOutput, } from "./types.js";
|
|
7
7
|
export * from "./types.js";
|
|
8
|
+
export const DEFAULT_INPUT_ACTION_GET = "$get";
|
|
9
|
+
const hooksSchema = z.object({
|
|
10
|
+
onStart: z.custom().optional(),
|
|
11
|
+
onEnd: z.custom().optional(),
|
|
12
|
+
onSkillStart: z.custom().optional(),
|
|
13
|
+
onSkillEnd: z.custom().optional(),
|
|
14
|
+
onHandoff: z.custom().optional(),
|
|
15
|
+
});
|
|
8
16
|
export const agentOptionsSchema = z.object({
|
|
9
17
|
subscribeTopic: z.union([z.string(), z.array(z.string())]).optional(),
|
|
10
18
|
publishTopic: z
|
|
@@ -13,21 +21,14 @@ export const agentOptionsSchema = z.object({
|
|
|
13
21
|
name: z.string().optional(),
|
|
14
22
|
description: z.string().optional(),
|
|
15
23
|
inputSchema: z.custom().optional(),
|
|
24
|
+
defaultInput: z.record(z.any()).optional(),
|
|
16
25
|
outputSchema: z.custom().optional(),
|
|
17
26
|
includeInputInOutput: z.boolean().optional(),
|
|
18
27
|
skills: z.array(z.union([z.custom(), z.custom()])).optional(),
|
|
19
28
|
disableEvents: z.boolean().optional(),
|
|
20
29
|
memory: z.union([z.custom(), z.array(z.custom())]).optional(),
|
|
21
30
|
maxRetrieveMemoryCount: z.number().optional(),
|
|
22
|
-
hooks: z
|
|
23
|
-
.object({
|
|
24
|
-
onStart: z.custom().optional(),
|
|
25
|
-
onEnd: z.custom().optional(),
|
|
26
|
-
onSkillStart: z.custom().optional(),
|
|
27
|
-
onSkillEnd: z.custom().optional(),
|
|
28
|
-
onHandoff: z.custom().optional(),
|
|
29
|
-
})
|
|
30
|
-
.optional(),
|
|
31
|
+
hooks: z.union([z.array(hooksSchema), hooksSchema]).optional(),
|
|
31
32
|
guideRails: z.array(z.custom()).optional(),
|
|
32
33
|
});
|
|
33
34
|
/**
|
|
@@ -62,6 +63,7 @@ export class Agent {
|
|
|
62
63
|
if (outputSchema)
|
|
63
64
|
checkAgentInputOutputSchema(outputSchema);
|
|
64
65
|
this._inputSchema = inputSchema;
|
|
66
|
+
this.defaultInput = options.defaultInput;
|
|
65
67
|
this._outputSchema = outputSchema;
|
|
66
68
|
this.includeInputInOutput = options.includeInputInOutput;
|
|
67
69
|
this.subscribeTopic = options.subscribeTopic;
|
|
@@ -76,7 +78,7 @@ export class Agent {
|
|
|
76
78
|
this.memories.push(options.memory);
|
|
77
79
|
}
|
|
78
80
|
this.maxRetrieveMemoryCount = options.maxRetrieveMemoryCount;
|
|
79
|
-
this.hooks = options.hooks
|
|
81
|
+
this.hooks = flat(options.hooks);
|
|
80
82
|
this.guideRails = options.guideRails;
|
|
81
83
|
}
|
|
82
84
|
/**
|
|
@@ -142,6 +144,7 @@ export class Agent {
|
|
|
142
144
|
*/
|
|
143
145
|
description;
|
|
144
146
|
_inputSchema;
|
|
147
|
+
defaultInput;
|
|
145
148
|
_outputSchema;
|
|
146
149
|
/**
|
|
147
150
|
* Get the input data schema for this agent
|
|
@@ -291,17 +294,23 @@ export class Agent {
|
|
|
291
294
|
...options,
|
|
292
295
|
context: options.context ?? (await this.newDefaultContext()),
|
|
293
296
|
};
|
|
297
|
+
input = this.mergeDefaultInput(input);
|
|
294
298
|
logger.debug("Invoke agent %s started with input: %O", this.name, input);
|
|
295
299
|
if (!this.disableEvents)
|
|
296
300
|
opts.context.emit("agentStarted", { agent: this, input });
|
|
297
301
|
try {
|
|
298
|
-
let
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
let response;
|
|
303
|
+
const s = await this.callHooks("onStart", { input }, opts);
|
|
304
|
+
if (s?.input)
|
|
305
|
+
input = s.input;
|
|
306
|
+
input = checkArguments(`Agent ${this.name} input`, this.inputSchema, input);
|
|
307
|
+
await this.preprocess(input, opts);
|
|
301
308
|
this.checkContextStatus(opts);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
response
|
|
309
|
+
if (!response) {
|
|
310
|
+
response = await this.process(input, opts);
|
|
311
|
+
if (response instanceof Agent) {
|
|
312
|
+
response = transferToAgentOutput(response);
|
|
313
|
+
}
|
|
305
314
|
}
|
|
306
315
|
if (opts.streaming) {
|
|
307
316
|
const stream = response instanceof ReadableStream
|
|
@@ -311,29 +320,62 @@ export class Agent {
|
|
|
311
320
|
: objectToAgentResponseStream(response);
|
|
312
321
|
return this.checkResponseByGuideRails(input, onAgentResponseStreamEnd(stream, {
|
|
313
322
|
onResult: async (result) => {
|
|
314
|
-
return await this.processAgentOutput(
|
|
323
|
+
return await this.processAgentOutput(input, result, opts);
|
|
315
324
|
},
|
|
316
325
|
onError: async (error) => {
|
|
317
326
|
return await this.processAgentError(input, error, opts);
|
|
318
327
|
},
|
|
319
328
|
}), opts);
|
|
320
329
|
}
|
|
321
|
-
return await this.checkResponseByGuideRails(input, this.processAgentOutput(
|
|
330
|
+
return await this.checkResponseByGuideRails(input, this.processAgentOutput(input, await agentProcessResultToObject(response), opts), opts);
|
|
322
331
|
}
|
|
323
332
|
catch (error) {
|
|
324
333
|
throw await this.processAgentError(input, error, opts);
|
|
325
334
|
}
|
|
326
335
|
}
|
|
336
|
+
async callHooks(hook, input, options) {
|
|
337
|
+
const { context } = options;
|
|
338
|
+
const result = {};
|
|
339
|
+
for (const hooks of flat(options.hooks, this.hooks)) {
|
|
340
|
+
const h = hooks[hook];
|
|
341
|
+
if (!h)
|
|
342
|
+
continue;
|
|
343
|
+
if (typeof h === "function") {
|
|
344
|
+
Object.assign(result, await h({ ...input, context }));
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
Object.assign(result, await context.invoke(h, input));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
mergeDefaultInput(input) {
|
|
353
|
+
const defaultInput = Object.fromEntries(Object.entries(this.defaultInput ?? {}).filter(([, v]) => !(typeof v === "object" && DEFAULT_INPUT_ACTION_GET in v)));
|
|
354
|
+
input = { ...defaultInput, ...input };
|
|
355
|
+
for (const key of Object.keys(this.defaultInput ?? {})) {
|
|
356
|
+
const v = this.defaultInput?.[key];
|
|
357
|
+
if (v &&
|
|
358
|
+
typeof v === "object" &&
|
|
359
|
+
DEFAULT_INPUT_ACTION_GET in v &&
|
|
360
|
+
typeof v[DEFAULT_INPUT_ACTION_GET] === "string" &&
|
|
361
|
+
isNil(input[key])) {
|
|
362
|
+
const value = input[v[DEFAULT_INPUT_ACTION_GET]];
|
|
363
|
+
if (!isNil(value))
|
|
364
|
+
Object.assign(input, { [key]: value });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return input;
|
|
368
|
+
}
|
|
327
369
|
async invokeSkill(skill, input, options) {
|
|
328
370
|
const { context } = options;
|
|
329
|
-
await this.
|
|
371
|
+
await this.callHooks("onSkillStart", { skill, input }, options);
|
|
330
372
|
try {
|
|
331
373
|
const output = await context.invoke(skill, input);
|
|
332
|
-
await this.
|
|
374
|
+
await this.callHooks("onSkillEnd", { skill, input, output }, options);
|
|
333
375
|
return output;
|
|
334
376
|
}
|
|
335
377
|
catch (error) {
|
|
336
|
-
await this.
|
|
378
|
+
await this.callHooks("onSkillEnd", { skill, input, error }, options);
|
|
337
379
|
throw error;
|
|
338
380
|
}
|
|
339
381
|
}
|
|
@@ -353,14 +395,17 @@ export class Agent {
|
|
|
353
395
|
throw new Error(`expect to return a record type such as {result: ...}, but got (${typeof output}): ${output}`);
|
|
354
396
|
}
|
|
355
397
|
const parsedOutput = checkArguments(`Agent ${this.name} output`, this.outputSchema, output);
|
|
356
|
-
|
|
398
|
+
let finalOutput = this.includeInputInOutput ? { ...input, ...parsedOutput } : parsedOutput;
|
|
357
399
|
await this.postprocess(input, finalOutput, options);
|
|
358
400
|
logger.debug("Invoke agent %s succeed with output: %O", this.name, finalOutput);
|
|
359
401
|
if (!this.disableEvents)
|
|
360
402
|
context.emit("agentSucceed", { agent: this, output: finalOutput });
|
|
361
|
-
|
|
362
|
-
if (o)
|
|
363
|
-
|
|
403
|
+
let o = await this.callHooks("onSuccess", { input, output: finalOutput }, options);
|
|
404
|
+
if (o?.output)
|
|
405
|
+
finalOutput = o.output;
|
|
406
|
+
o = await this.callHooks("onEnd", { input, output: finalOutput }, options);
|
|
407
|
+
if (o?.output)
|
|
408
|
+
finalOutput = o.output;
|
|
364
409
|
return finalOutput;
|
|
365
410
|
}
|
|
366
411
|
/**
|
|
@@ -375,8 +420,8 @@ export class Agent {
|
|
|
375
420
|
logger.error("Invoke agent %s failed with error: %O", this.name, error);
|
|
376
421
|
if (!this.disableEvents)
|
|
377
422
|
options.context.emit("agentFailed", { agent: this, error });
|
|
378
|
-
|
|
379
|
-
await this.
|
|
423
|
+
await this.callHooks("onError", { input, error }, options);
|
|
424
|
+
await this.callHooks("onEnd", { input, error }, options);
|
|
380
425
|
return error;
|
|
381
426
|
}
|
|
382
427
|
/**
|
|
@@ -240,7 +240,7 @@ export class AIAgent extends Agent {
|
|
|
240
240
|
const outputKey = this.outputKey;
|
|
241
241
|
for (;;) {
|
|
242
242
|
const modelOutput = {};
|
|
243
|
-
let stream = await options.context.invoke(model, { ...modelInput, messages: modelInput.messages.concat(toolCallMessages) }, { streaming: true });
|
|
243
|
+
let stream = await options.context.invoke(model, { ...modelInput, messages: modelInput.messages.concat(toolCallMessages) }, { ...options, streaming: true });
|
|
244
244
|
if (this.structuredStreamMode) {
|
|
245
245
|
const { metadataStart, metadataEnd, parse } = this.customStructuredStreamInstructions || STRUCTURED_STREAM_INSTRUCTIONS;
|
|
246
246
|
stream = stream.pipeThrough(new ExtractMetadataTransform({ start: metadataStart, end: metadataEnd, parse }));
|
|
@@ -315,14 +315,17 @@ export class AIAgent extends Agent {
|
|
|
315
315
|
* @protected
|
|
316
316
|
*/
|
|
317
317
|
async *_processRouter(input, model, modelInput, options, toolsMap) {
|
|
318
|
-
const { toolCalls: [call] = [] } = await options.context.invoke(model, modelInput
|
|
318
|
+
const { toolCalls: [call] = [] } = await options.context.invoke(model, modelInput, {
|
|
319
|
+
...options,
|
|
320
|
+
streaming: false,
|
|
321
|
+
});
|
|
319
322
|
if (!call) {
|
|
320
323
|
throw new Error("Router toolChoice requires exactly one tool to be executed");
|
|
321
324
|
}
|
|
322
325
|
const tool = toolsMap.get(call.function.name);
|
|
323
326
|
if (!tool)
|
|
324
327
|
throw new Error(`Tool not found: ${call.function.name}`);
|
|
325
|
-
const stream = await options.context.invoke(tool, { ...call.function.arguments, ...input }, { streaming: true, sourceAgent: this });
|
|
328
|
+
const stream = await options.context.invoke(tool, { ...call.function.arguments, ...input }, { ...options, streaming: true, sourceAgent: this });
|
|
326
329
|
return yield* stream;
|
|
327
330
|
}
|
|
328
331
|
}
|
|
@@ -22,6 +22,73 @@ export declare enum ProcessMode {
|
|
|
22
22
|
*/
|
|
23
23
|
parallel = "parallel"
|
|
24
24
|
}
|
|
25
|
+
export declare const DEFAULT_REFLECTION_MAX_ITERATIONS = 3;
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for reflection mode processing in TeamAgent.
|
|
28
|
+
*
|
|
29
|
+
* Reflection mode enables iterative refinement of agent outputs through a review process.
|
|
30
|
+
* The team agent will repeatedly process the input and have a reviewer agent evaluate
|
|
31
|
+
* the output until it meets approval criteria or reaches the maximum iteration limit.
|
|
32
|
+
*
|
|
33
|
+
* This is particularly useful for:
|
|
34
|
+
* - Quality assurance workflows where outputs need validation
|
|
35
|
+
* - Iterative improvement processes where initial results can be refined
|
|
36
|
+
* - Self-correcting agent systems that learn from feedback
|
|
37
|
+
*/
|
|
38
|
+
export interface ReflectionMode {
|
|
39
|
+
/**
|
|
40
|
+
* The agent responsible for reviewing and providing feedback on the team's output.
|
|
41
|
+
*
|
|
42
|
+
* The reviewer agent receives the combined output from the team's processing
|
|
43
|
+
* and should provide feedback or evaluation that can be used to determine
|
|
44
|
+
* whether the output meets the required standards.
|
|
45
|
+
*/
|
|
46
|
+
reviewer: Agent;
|
|
47
|
+
/**
|
|
48
|
+
* Function that determines whether the reviewer's output indicates approval.
|
|
49
|
+
*
|
|
50
|
+
* This function receives the reviewer agent's output message and should return:
|
|
51
|
+
* - `true` or truthy value: The output is approved and processing should stop
|
|
52
|
+
* - `false` or falsy value: The output needs improvement and another iteration should run
|
|
53
|
+
*
|
|
54
|
+
* The function can be synchronous or asynchronous, allowing for complex approval logic
|
|
55
|
+
* including external validation, scoring systems, or human-in-the-loop approval.
|
|
56
|
+
*
|
|
57
|
+
* @param output - The message output from the reviewer agent
|
|
58
|
+
* @returns A boolean or truthy/falsy value indicating approval status
|
|
59
|
+
*/
|
|
60
|
+
isApproved: (output: Message) => PromiseOrValue<boolean | unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Maximum number of reflection iterations before giving up.
|
|
63
|
+
*
|
|
64
|
+
* This prevents infinite loops when the approval criteria cannot be met.
|
|
65
|
+
* If the maximum iterations are reached without approval, the process will
|
|
66
|
+
* throw an error indicating the reflection failed to converge.
|
|
67
|
+
*
|
|
68
|
+
* @default 3
|
|
69
|
+
*/
|
|
70
|
+
maxIterations?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Controls the behavior when maximum iterations are reached without approval.
|
|
73
|
+
*
|
|
74
|
+
* When set to `true`, the TeamAgent will return the last generated output
|
|
75
|
+
* instead of throwing an error when the maximum number of reflection iterations
|
|
76
|
+
* is reached without the reviewer's approval.
|
|
77
|
+
*
|
|
78
|
+
* When set to `false` or undefined, the TeamAgent will throw an error
|
|
79
|
+
* indicating that the reflection process failed to converge within the
|
|
80
|
+
* maximum iteration limit.
|
|
81
|
+
*
|
|
82
|
+
* This option is useful for scenarios where:
|
|
83
|
+
* - You want to get the best available result even if it's not perfect
|
|
84
|
+
* - The approval criteria might be too strict for the given context
|
|
85
|
+
* - You prefer graceful degradation over complete failure
|
|
86
|
+
* - You want to implement custom error handling based on the returned result
|
|
87
|
+
*
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
returnLastOnMaxIterations?: boolean;
|
|
91
|
+
}
|
|
25
92
|
/**
|
|
26
93
|
* Configuration options for creating a TeamAgent.
|
|
27
94
|
*
|
|
@@ -33,6 +100,21 @@ export interface TeamAgentOptions<I extends Message, O extends Message> extends
|
|
|
33
100
|
* @default {ProcessMode.sequential}
|
|
34
101
|
*/
|
|
35
102
|
mode?: ProcessMode;
|
|
103
|
+
/**
|
|
104
|
+
* Configuration for reflection mode processing.
|
|
105
|
+
*
|
|
106
|
+
* When enabled, the TeamAgent will use an iterative refinement process where:
|
|
107
|
+
* 1. The team processes the input normally
|
|
108
|
+
* 2. A reviewer agent evaluates the output
|
|
109
|
+
* 3. If not approved, the process repeats with the previous output as context
|
|
110
|
+
* 4. This continues until approval or maximum iterations are reached
|
|
111
|
+
*
|
|
112
|
+
* This enables self-improving agent workflows that can iteratively refine
|
|
113
|
+
* their outputs based on feedback from a specialized reviewer agent.
|
|
114
|
+
*
|
|
115
|
+
* @see ReflectionMode for detailed configuration options
|
|
116
|
+
*/
|
|
117
|
+
reflection?: ReflectionMode;
|
|
36
118
|
/**
|
|
37
119
|
* Specifies which input field should be treated as an array for iterative processing.
|
|
38
120
|
*
|
|
@@ -116,6 +198,14 @@ export declare class TeamAgent<I extends Message, O extends Message> extends Age
|
|
|
116
198
|
* This can be either sequential (one after another) or parallel (all at once).
|
|
117
199
|
*/
|
|
118
200
|
mode: ProcessMode;
|
|
201
|
+
/**
|
|
202
|
+
* The reflection mode configuration with guaranteed maxIterations value.
|
|
203
|
+
*
|
|
204
|
+
* This is the internal representation after processing the user-provided
|
|
205
|
+
* reflection configuration, ensuring that maxIterations always has a value
|
|
206
|
+
* (defaulting to DEFAULT_REFLECTION_MAX_ITERATIONS if not specified).
|
|
207
|
+
*/
|
|
208
|
+
reflection?: ReflectionMode & Required<Pick<ReflectionMode, "maxIterations">>;
|
|
119
209
|
/**
|
|
120
210
|
* The input field key to iterate over when processing array inputs.
|
|
121
211
|
*
|
|
@@ -148,8 +238,10 @@ export declare class TeamAgent<I extends Message, O extends Message> extends Age
|
|
|
148
238
|
* @returns A stream of message chunks that collectively form the response
|
|
149
239
|
*/
|
|
150
240
|
process(input: I, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<O>>;
|
|
241
|
+
private _processNonReflection;
|
|
242
|
+
private _processReflection;
|
|
151
243
|
private _processIterator;
|
|
152
|
-
private
|
|
244
|
+
private _processNonIterator;
|
|
153
245
|
/**
|
|
154
246
|
* Process input sequentially through each agent in the team.
|
|
155
247
|
*
|
|
@@ -26,6 +26,7 @@ export var ProcessMode;
|
|
|
26
26
|
*/
|
|
27
27
|
ProcessMode["parallel"] = "parallel";
|
|
28
28
|
})(ProcessMode || (ProcessMode = {}));
|
|
29
|
+
export const DEFAULT_REFLECTION_MAX_ITERATIONS = 3;
|
|
29
30
|
/**
|
|
30
31
|
* TeamAgent coordinates a group of agents working together to accomplish tasks.
|
|
31
32
|
*
|
|
@@ -70,6 +71,10 @@ export class TeamAgent extends Agent {
|
|
|
70
71
|
constructor(options) {
|
|
71
72
|
super(options);
|
|
72
73
|
this.mode = options.mode ?? ProcessMode.sequential;
|
|
74
|
+
this.reflection = options.reflection && {
|
|
75
|
+
...options.reflection,
|
|
76
|
+
maxIterations: options.reflection.maxIterations ?? DEFAULT_REFLECTION_MAX_ITERATIONS,
|
|
77
|
+
};
|
|
73
78
|
this.iterateOn = options.iterateOn;
|
|
74
79
|
this.iterateWithPreviousOutput = options.iterateWithPreviousOutput;
|
|
75
80
|
}
|
|
@@ -79,6 +84,14 @@ export class TeamAgent extends Agent {
|
|
|
79
84
|
* This can be either sequential (one after another) or parallel (all at once).
|
|
80
85
|
*/
|
|
81
86
|
mode;
|
|
87
|
+
/**
|
|
88
|
+
* The reflection mode configuration with guaranteed maxIterations value.
|
|
89
|
+
*
|
|
90
|
+
* This is the internal representation after processing the user-provided
|
|
91
|
+
* reflection configuration, ensuring that maxIterations always has a value
|
|
92
|
+
* (defaulting to DEFAULT_REFLECTION_MAX_ITERATIONS if not specified).
|
|
93
|
+
*/
|
|
94
|
+
reflection;
|
|
82
95
|
/**
|
|
83
96
|
* The input field key to iterate over when processing array inputs.
|
|
84
97
|
*
|
|
@@ -113,10 +126,35 @@ export class TeamAgent extends Agent {
|
|
|
113
126
|
process(input, options) {
|
|
114
127
|
if (!this.skills.length)
|
|
115
128
|
throw new Error("TeamAgent must have at least one skill defined.");
|
|
129
|
+
if (this.reflection)
|
|
130
|
+
return this._processReflection(input, options);
|
|
131
|
+
return this._processNonReflection(input, options);
|
|
132
|
+
}
|
|
133
|
+
_processNonReflection(input, options) {
|
|
116
134
|
if (this.iterateOn) {
|
|
117
135
|
return this._processIterator(this.iterateOn, input, options);
|
|
118
136
|
}
|
|
119
|
-
return this.
|
|
137
|
+
return this._processNonIterator(input, options);
|
|
138
|
+
}
|
|
139
|
+
async _processReflection(input, options) {
|
|
140
|
+
assert(this.reflection, "Reflection mode must be defined for reflection processing");
|
|
141
|
+
let iterations = 0;
|
|
142
|
+
const previousOutput = { ...input };
|
|
143
|
+
for (;;) {
|
|
144
|
+
const output = await agentProcessResultToObject(await this._processNonReflection(previousOutput, options));
|
|
145
|
+
Object.assign(previousOutput, output);
|
|
146
|
+
const reviewOutput = await options.context.invoke(this.reflection.reviewer, previousOutput);
|
|
147
|
+
Object.assign(previousOutput, reviewOutput);
|
|
148
|
+
const approved = await this.reflection.isApproved(reviewOutput);
|
|
149
|
+
if (approved)
|
|
150
|
+
return output;
|
|
151
|
+
if (++iterations >= this.reflection.maxIterations) {
|
|
152
|
+
if (this.reflection.returnLastOnMaxIterations)
|
|
153
|
+
return output;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Reflection mode exceeded max iterations ${this.reflection.maxIterations}. Please review the feedback and try again.`);
|
|
120
158
|
}
|
|
121
159
|
async *_processIterator(key, input, options) {
|
|
122
160
|
assert(this.iterateOn, "iterateInputKey must be defined for iterator processing");
|
|
@@ -127,7 +165,7 @@ export class TeamAgent extends Agent {
|
|
|
127
165
|
const item = arr[i];
|
|
128
166
|
if (!isRecord(item))
|
|
129
167
|
throw new TypeError(`Expected ${String(key)} to be an object, got ${typeof item}`);
|
|
130
|
-
const res = await agentProcessResultToObject(await this.
|
|
168
|
+
const res = await agentProcessResultToObject(await this._processNonIterator({ ...input, [key]: arr, ...item }, { ...options, streaming: false }));
|
|
131
169
|
// Merge the item result with the original item used for next iteration
|
|
132
170
|
if (this.iterateWithPreviousOutput) {
|
|
133
171
|
arr = produce(arr, (draft) => {
|
|
@@ -140,7 +178,7 @@ export class TeamAgent extends Agent {
|
|
|
140
178
|
yield { delta: { json: { [key]: result } } };
|
|
141
179
|
}
|
|
142
180
|
}
|
|
143
|
-
|
|
181
|
+
_processNonIterator(input, options) {
|
|
144
182
|
switch (this.mode) {
|
|
145
183
|
case ProcessMode.sequential:
|
|
146
184
|
return this._processSequential(input, options);
|
|
@@ -165,19 +203,13 @@ export class TeamAgent extends Agent {
|
|
|
165
203
|
*/
|
|
166
204
|
async *_processSequential(input, options) {
|
|
167
205
|
const output = {};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const newAgents = [];
|
|
171
|
-
for (const agent of agents) {
|
|
172
|
-
const [o, transferToAgent] = await options.context.invoke(agent, { ...input, ...output }, { returnActiveAgent: true, streaming: true });
|
|
206
|
+
for (const agent of this.skills) {
|
|
207
|
+
const o = await options.context.invoke(agent, { ...input, ...output }, { ...options, streaming: true });
|
|
173
208
|
for await (const chunk of o) {
|
|
174
209
|
yield chunk;
|
|
175
210
|
mergeAgentResponseChunk(output, chunk);
|
|
176
211
|
}
|
|
177
|
-
newAgents.push(await transferToAgent);
|
|
178
212
|
}
|
|
179
|
-
this.skills.splice(0);
|
|
180
|
-
this.skills.push(...newAgents);
|
|
181
213
|
}
|
|
182
214
|
/**
|
|
183
215
|
* Process input in parallel through all agents in the team.
|
|
@@ -194,8 +226,7 @@ export class TeamAgent extends Agent {
|
|
|
194
226
|
* @private
|
|
195
227
|
*/
|
|
196
228
|
async *_processParallel(input, options) {
|
|
197
|
-
const
|
|
198
|
-
const streams = result.map((i) => i[0]);
|
|
229
|
+
const streams = await Promise.all(this.skills.map((agent) => options.context.invoke(agent, input, { ...options, streaming: true })));
|
|
199
230
|
const read = async (index, reader) => {
|
|
200
231
|
const promise = reader.read();
|
|
201
232
|
return promise.then((result) => ({ ...result, reader, index }));
|
|
@@ -230,8 +261,5 @@ export class TeamAgent extends Agent {
|
|
|
230
261
|
yield { delta: { ...delta, text } };
|
|
231
262
|
}
|
|
232
263
|
}
|
|
233
|
-
const agents = await Promise.all(result.map((i) => i[1]));
|
|
234
|
-
this.skills.splice(0);
|
|
235
|
-
this.skills.push(...agents);
|
|
236
264
|
}
|
|
237
265
|
}
|
|
@@ -180,6 +180,7 @@ declare class AIGNEContextShared {
|
|
|
180
180
|
span?: Span;
|
|
181
181
|
constructor(parent?: (Pick<Context, "model" | "skills" | "limits" | "observer"> & {
|
|
182
182
|
messageQueue?: MessageQueue;
|
|
183
|
+
events?: Emitter<any>;
|
|
183
184
|
}) | undefined);
|
|
184
185
|
readonly messageQueue: MessageQueue;
|
|
185
186
|
readonly events: Emitter<any>;
|
package/lib/esm/aigne/context.js
CHANGED
|
@@ -34,7 +34,7 @@ export class AIGNEContext {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
|
-
this.internal = new AIGNEContextShared(parent);
|
|
37
|
+
this.internal = new AIGNEContextShared(parent instanceof AIGNEContext ? parent.internal : parent);
|
|
38
38
|
this.span = tracer?.startSpan("AIGNEContext");
|
|
39
39
|
// 修改了 rootId 是否会之前的有影响?,之前为 this.id
|
|
40
40
|
this.rootId = this.span?.spanContext?.().traceId ?? v7();
|
|
@@ -265,9 +265,10 @@ class AIGNEContextShared {
|
|
|
265
265
|
constructor(parent) {
|
|
266
266
|
this.parent = parent;
|
|
267
267
|
this.messageQueue = this.parent?.messageQueue ?? new MessageQueue();
|
|
268
|
+
this.events = this.parent?.events ?? new Emitter();
|
|
268
269
|
}
|
|
269
270
|
messageQueue;
|
|
270
|
-
events
|
|
271
|
+
events;
|
|
271
272
|
get model() {
|
|
272
273
|
return this.parent?.model;
|
|
273
274
|
}
|
|
@@ -302,21 +303,35 @@ class AIGNEContextShared {
|
|
|
302
303
|
this.initTimeout();
|
|
303
304
|
return withAbortSignal(this.abortController.signal, new Error("AIGNEContext is timeout"), () => this.invokeAgent(agent, input, context, options));
|
|
304
305
|
}
|
|
305
|
-
async *invokeAgent(agent, input, context, options) {
|
|
306
|
+
async *invokeAgent(agent, input, context, options = {}) {
|
|
306
307
|
const startedAt = Date.now();
|
|
307
308
|
try {
|
|
308
309
|
let activeAgent = agent;
|
|
309
310
|
for (;;) {
|
|
310
311
|
const result = {};
|
|
311
312
|
if (options?.sourceAgent && activeAgent !== options.sourceAgent) {
|
|
312
|
-
options.sourceAgent.hooks
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
313
|
+
for (const { onHandoff } of [options.hooks ?? {}, ...options.sourceAgent.hooks]) {
|
|
314
|
+
if (!onHandoff)
|
|
315
|
+
continue;
|
|
316
|
+
await (typeof onHandoff === "function"
|
|
317
|
+
? onHandoff({
|
|
318
|
+
context,
|
|
319
|
+
source: options.sourceAgent,
|
|
320
|
+
target: activeAgent,
|
|
321
|
+
input,
|
|
322
|
+
})
|
|
323
|
+
: context.invoke(onHandoff, {
|
|
324
|
+
source: options.sourceAgent,
|
|
325
|
+
target: activeAgent,
|
|
326
|
+
input,
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
318
329
|
}
|
|
319
|
-
const stream = await activeAgent.invoke(input, {
|
|
330
|
+
const stream = await activeAgent.invoke(input, {
|
|
331
|
+
hooks: options.hooks,
|
|
332
|
+
context,
|
|
333
|
+
streaming: true,
|
|
334
|
+
});
|
|
320
335
|
for await (const value of stream) {
|
|
321
336
|
if (isAgentResponseDelta(value)) {
|
|
322
337
|
if (value.delta.json) {
|
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare function loadAgentFromJsFile(path: string): Promise<Agent<any, any> | {
|
|
4
|
-
process: FunctionAgentFn;
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string | undefined;
|
|
7
|
-
inputSchema?: ZodObject<Record<string, ZodType<any, z.ZodTypeDef, any>>, z.UnknownKeysParam, z.ZodTypeAny, {
|
|
8
|
-
[x: string]: any;
|
|
9
|
-
}, {
|
|
10
|
-
[x: string]: any;
|
|
11
|
-
}> | undefined;
|
|
12
|
-
outputSchema?: ZodObject<Record<string, ZodType<any, z.ZodTypeDef, any>>, z.UnknownKeysParam, z.ZodTypeAny, {
|
|
13
|
-
[x: string]: any;
|
|
14
|
-
}, {
|
|
15
|
-
[x: string]: any;
|
|
16
|
-
}> | undefined;
|
|
17
|
-
}>;
|
|
1
|
+
import { Agent } from "../agents/agent.js";
|
|
2
|
+
export declare function loadAgentFromJsFile(path: string): Promise<Agent<any, any> | import("./agent-yaml.js").AgentSchema>;
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import { jsonSchemaToZod } from "@aigne/json-schema-to-zod";
|
|
2
|
-
import camelize from "camelize-ts";
|
|
3
|
-
import { z } from "zod";
|
|
4
1
|
import { Agent } from "../agents/agent.js";
|
|
5
2
|
import { tryOrThrow } from "../utils/type-utils.js";
|
|
6
|
-
import {
|
|
3
|
+
import { parseAgentFile } from "./agent-yaml.js";
|
|
7
4
|
export async function loadAgentFromJsFile(path) {
|
|
8
|
-
const agentJsFileSchema = z.object({
|
|
9
|
-
name: z.string(),
|
|
10
|
-
description: optionalize(z.string()),
|
|
11
|
-
inputSchema: optionalize(inputOutputSchema({ path })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
|
|
12
|
-
outputSchema: optionalize(inputOutputSchema({ path })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
|
|
13
|
-
process: z.custom(),
|
|
14
|
-
});
|
|
15
5
|
const { default: agent } = await tryOrThrow(() => import(/* @vite-ignore */ path), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
|
|
16
6
|
if (agent instanceof Agent)
|
|
17
7
|
return agent;
|
|
18
8
|
if (typeof agent !== "function") {
|
|
19
9
|
throw new Error(`Agent file ${path} must export a default function, but got ${typeof agent}`);
|
|
20
10
|
}
|
|
21
|
-
return tryOrThrow(() =>
|
|
11
|
+
return tryOrThrow(() => parseAgentFile(path, {
|
|
22
12
|
...agent,
|
|
13
|
+
type: "function",
|
|
23
14
|
name: agent.agent_name || agent.agentName || agent.name,
|
|
24
15
|
process: agent,
|
|
25
|
-
})
|
|
16
|
+
}), (error) => new Error(`Failed to parse agent from ${path}: ${error.message}`));
|
|
26
17
|
}
|