@axlsdk/axl 0.7.6 → 0.8.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/README.md +29 -6
- package/dist/index.cjs +164 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +163 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -320,7 +320,7 @@ for await (const event of sessionStream) {
|
|
|
320
320
|
All available on `ctx` inside workflow handlers. See the [API Reference](../../docs/api-reference.md) for complete option types, valid values, and defaults.
|
|
321
321
|
|
|
322
322
|
```typescript
|
|
323
|
-
// Invoke an agent (schema retries
|
|
323
|
+
// Invoke an agent (schema/validate retries accumulate — LLM sees all previous failed attempts)
|
|
324
324
|
const answer = await ctx.ask(agent, 'prompt', { schema, retries });
|
|
325
325
|
|
|
326
326
|
// Run 3 agents in parallel — each gets the same question independently
|
|
@@ -329,11 +329,11 @@ const results = await ctx.spawn(3, async (i) => ctx.ask(agent, prompts[i]));
|
|
|
329
329
|
// Pick the answer that appeared most often — also supports LLM-as-judge via scorer
|
|
330
330
|
const winner = await ctx.vote(results, { strategy: 'majority', key: 'answer' });
|
|
331
331
|
|
|
332
|
-
//
|
|
332
|
+
// Retry-until-valid loop — for APIs, pipelines, or as a repair fallback for ctx.ask()
|
|
333
333
|
const valid = await ctx.verify(
|
|
334
|
-
async (
|
|
335
|
-
|
|
336
|
-
{ retries: 3, fallback:
|
|
334
|
+
async () => fetchRouteFromAPI(origin, destination),
|
|
335
|
+
RouteSchema,
|
|
336
|
+
{ retries: 3, fallback: defaultRoute },
|
|
337
337
|
);
|
|
338
338
|
|
|
339
339
|
// Cost control — returns { value, budgetExceeded, totalCost }
|
|
@@ -477,7 +477,29 @@ const safe = agent({
|
|
|
477
477
|
});
|
|
478
478
|
```
|
|
479
479
|
|
|
480
|
-
When `onBlock` is `'retry'`, the LLM's blocked output is appended to the conversation (as an assistant message) along with a system message containing the block reason, then the LLM is re-called so it can self-correct. These messages **accumulate** across retries — if the guardrail blocks multiple times, the LLM sees all prior failed attempts and corrections before its next try. All retry messages are ephemeral — they are **not** persisted to session history, so subsequent session turns never see the blocked attempts.
|
|
480
|
+
When `onBlock` is `'retry'`, the LLM's blocked output is appended to the conversation (as an assistant message) along with a system message containing the block reason, then the LLM is re-called so it can self-correct. These messages **accumulate** across retries — if the guardrail blocks multiple times, the LLM sees all prior failed attempts and corrections before its next try. All retry messages are ephemeral — they are **not** persisted to session history, so subsequent session turns never see the blocked attempts. Schema retries and validate retries use the same accumulating pattern. Input guardrails always throw since the prompt is user-supplied. Throws `GuardrailError` if retries are exhausted or `onBlock` is `'throw'`.
|
|
481
|
+
|
|
482
|
+
For **business rule validation** on the parsed typed object (not raw text), use `validate` on `ctx.ask()`:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
const UserSchema = z.object({
|
|
486
|
+
name: z.string(),
|
|
487
|
+
email: z.string(),
|
|
488
|
+
role: z.enum(['admin', 'editor', 'viewer']),
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const result = await ctx.ask(extractAgent, 'Extract user from this text', {
|
|
492
|
+
schema: UserSchema,
|
|
493
|
+
validate: (user) => {
|
|
494
|
+
if (user.role === 'admin' && !user.email.endsWith('@company.com')) {
|
|
495
|
+
return { valid: false, reason: 'Admin users must have a company email' };
|
|
496
|
+
}
|
|
497
|
+
return { valid: true };
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
`validate` is per-call, co-located with the `schema` it validates. It runs **after** schema parsing succeeds, receiving the fully typed object. On failure, the LLM sees all previous attempts (accumulating context) and the validation reason. Requires `schema` — without it, validate is skipped (use guardrails for raw text). Throws `ValidationError` after retries are exhausted. Also supported on `ctx.delegate()`, `ctx.race()`, and `ctx.verify()`.
|
|
481
503
|
|
|
482
504
|
### State Stores
|
|
483
505
|
|
|
@@ -556,6 +578,7 @@ import {
|
|
|
556
578
|
MaxTurnsError, // Agent exceeded max tool-calling turns
|
|
557
579
|
BudgetExceededError, // Budget limit exceeded
|
|
558
580
|
GuardrailError, // Guardrail blocked input or output
|
|
581
|
+
ValidationError, // Post-schema business rule validation failed after retries
|
|
559
582
|
ToolDenied, // Agent tried to call unauthorized tool
|
|
560
583
|
} from '@axlsdk/axl';
|
|
561
584
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -135,6 +135,7 @@ __export(index_exports, {
|
|
|
135
135
|
SqliteVectorStore: () => SqliteVectorStore,
|
|
136
136
|
TimeoutError: () => TimeoutError,
|
|
137
137
|
ToolDenied: () => ToolDenied,
|
|
138
|
+
ValidationError: () => ValidationError,
|
|
138
139
|
VerifyError: () => VerifyError,
|
|
139
140
|
WorkflowContext: () => WorkflowContext,
|
|
140
141
|
agent: () => agent,
|
|
@@ -2143,6 +2144,18 @@ var GuardrailError = class extends AxlError {
|
|
|
2143
2144
|
this.reason = reason;
|
|
2144
2145
|
}
|
|
2145
2146
|
};
|
|
2147
|
+
var ValidationError = class extends AxlError {
|
|
2148
|
+
lastOutput;
|
|
2149
|
+
reason;
|
|
2150
|
+
retries;
|
|
2151
|
+
constructor(lastOutput, reason, retries) {
|
|
2152
|
+
super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
|
|
2153
|
+
this.name = "ValidationError";
|
|
2154
|
+
this.lastOutput = lastOutput;
|
|
2155
|
+
this.reason = reason;
|
|
2156
|
+
this.retries = retries;
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2146
2159
|
var ToolDenied = class extends AxlError {
|
|
2147
2160
|
toolName;
|
|
2148
2161
|
agentName;
|
|
@@ -2407,9 +2420,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2407
2420
|
agent2,
|
|
2408
2421
|
prompt,
|
|
2409
2422
|
options,
|
|
2410
|
-
0,
|
|
2411
|
-
void 0,
|
|
2412
|
-
void 0,
|
|
2413
2423
|
void 0,
|
|
2414
2424
|
usageCapture
|
|
2415
2425
|
);
|
|
@@ -2459,7 +2469,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2459
2469
|
return result;
|
|
2460
2470
|
});
|
|
2461
2471
|
}
|
|
2462
|
-
async executeAgentCall(agent2, prompt, options,
|
|
2472
|
+
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2463
2473
|
if (this.budgetContext?.exceeded) {
|
|
2464
2474
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2465
2475
|
if (policy === "warn") {
|
|
@@ -2531,16 +2541,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2531
2541
|
|
|
2532
2542
|
Respond with valid JSON matching this schema:
|
|
2533
2543
|
${JSON.stringify(jsonSchema, null, 2)}`;
|
|
2534
|
-
}
|
|
2535
|
-
if (previousOutput && previousError) {
|
|
2536
|
-
userContent += `
|
|
2537
|
-
|
|
2538
|
-
Your previous response was invalid:
|
|
2539
|
-
${previousOutput}
|
|
2540
|
-
|
|
2541
|
-
Error: ${previousError}
|
|
2542
|
-
|
|
2543
|
-
Please fix and try again.`;
|
|
2544
2544
|
}
|
|
2545
2545
|
messages.push({ role: "user", content: userContent });
|
|
2546
2546
|
if (handoffMessages && handoffMessages.length > 0) {
|
|
@@ -2585,9 +2585,17 @@ Please fix and try again.`;
|
|
|
2585
2585
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2586
2586
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2587
2587
|
const startTime = Date.now();
|
|
2588
|
+
if (this.onToken && options?.validate) {
|
|
2589
|
+
throw new AxlError(
|
|
2590
|
+
"INVALID_CONFIG",
|
|
2591
|
+
"Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2588
2594
|
const currentMessages = [...messages];
|
|
2589
2595
|
let turns = 0;
|
|
2590
2596
|
let guardrailOutputRetries = 0;
|
|
2597
|
+
let schemaRetries = 0;
|
|
2598
|
+
let validateRetries = 0;
|
|
2591
2599
|
while (turns < maxTurns) {
|
|
2592
2600
|
if (Date.now() - startTime > timeoutMs) {
|
|
2593
2601
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
@@ -2715,14 +2723,17 @@ Please fix and try again.`;
|
|
|
2715
2723
|
}
|
|
2716
2724
|
}
|
|
2717
2725
|
const handoffStart = Date.now();
|
|
2718
|
-
const handoffOptions = options ? {
|
|
2726
|
+
const handoffOptions = options ? {
|
|
2727
|
+
schema: options.schema,
|
|
2728
|
+
retries: options.retries,
|
|
2729
|
+
metadata: options.metadata,
|
|
2730
|
+
validate: options.validate,
|
|
2731
|
+
validateRetries: options.validateRetries
|
|
2732
|
+
} : void 0;
|
|
2719
2733
|
const handoffFn = () => this.executeAgentCall(
|
|
2720
2734
|
descriptor.agent,
|
|
2721
2735
|
handoffPrompt,
|
|
2722
2736
|
handoffOptions,
|
|
2723
|
-
0,
|
|
2724
|
-
void 0,
|
|
2725
|
-
void 0,
|
|
2726
2737
|
currentMessages,
|
|
2727
2738
|
usageCapture
|
|
2728
2739
|
);
|
|
@@ -3056,26 +3067,26 @@ Please fix and try again.`;
|
|
|
3056
3067
|
throw new GuardrailError("output", outputResult.reason ?? "Output blocked by guardrail");
|
|
3057
3068
|
}
|
|
3058
3069
|
}
|
|
3070
|
+
let validated = void 0;
|
|
3059
3071
|
if (options?.schema) {
|
|
3060
3072
|
try {
|
|
3061
3073
|
const parsed = JSON.parse(stripMarkdownFences(content));
|
|
3062
|
-
|
|
3063
|
-
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3064
|
-
return validated;
|
|
3074
|
+
validated = options.schema.parse(parsed);
|
|
3065
3075
|
} catch (err) {
|
|
3066
|
-
const
|
|
3067
|
-
if (
|
|
3076
|
+
const maxSchemaRetries = options.retries ?? 3;
|
|
3077
|
+
if (schemaRetries < maxSchemaRetries) {
|
|
3078
|
+
schemaRetries++;
|
|
3068
3079
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
prompt,
|
|
3072
|
-
options,
|
|
3073
|
-
retryCount + 1,
|
|
3080
|
+
currentMessages.push({
|
|
3081
|
+
role: "assistant",
|
|
3074
3082
|
content,
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3083
|
+
...response.providerMetadata ? { providerMetadata: response.providerMetadata } : {}
|
|
3084
|
+
});
|
|
3085
|
+
currentMessages.push({
|
|
3086
|
+
role: "system",
|
|
3087
|
+
content: `Your response was not valid JSON or did not match the required schema: ${errorMsg}. Please fix and try again.`
|
|
3088
|
+
});
|
|
3089
|
+
continue;
|
|
3079
3090
|
}
|
|
3080
3091
|
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([
|
|
3081
3092
|
{
|
|
@@ -3084,11 +3095,55 @@ Please fix and try again.`;
|
|
|
3084
3095
|
message: err instanceof Error ? err.message : String(err)
|
|
3085
3096
|
}
|
|
3086
3097
|
]);
|
|
3087
|
-
throw new VerifyError(content, zodErr,
|
|
3098
|
+
throw new VerifyError(content, zodErr, maxSchemaRetries);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (options?.schema && options.validate) {
|
|
3102
|
+
let validateResult;
|
|
3103
|
+
try {
|
|
3104
|
+
validateResult = await options.validate(validated, {
|
|
3105
|
+
metadata: this.metadata
|
|
3106
|
+
});
|
|
3107
|
+
} catch (err) {
|
|
3108
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3109
|
+
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
3110
|
+
}
|
|
3111
|
+
this.emitTrace({
|
|
3112
|
+
type: "validate",
|
|
3113
|
+
agent: agent2._name,
|
|
3114
|
+
data: {
|
|
3115
|
+
valid: validateResult.valid,
|
|
3116
|
+
...validateResult.reason ? { reason: validateResult.reason } : {}
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
this.spanManager?.addEventToActiveSpan("axl.validate.check", {
|
|
3120
|
+
"axl.validate.valid": validateResult.valid,
|
|
3121
|
+
...validateResult.reason ? { "axl.validate.reason": validateResult.reason } : {}
|
|
3122
|
+
});
|
|
3123
|
+
if (!validateResult.valid) {
|
|
3124
|
+
const maxValidateRetries = options.validateRetries ?? 2;
|
|
3125
|
+
if (validateRetries < maxValidateRetries) {
|
|
3126
|
+
validateRetries++;
|
|
3127
|
+
currentMessages.push({
|
|
3128
|
+
role: "assistant",
|
|
3129
|
+
content,
|
|
3130
|
+
...response.providerMetadata ? { providerMetadata: response.providerMetadata } : {}
|
|
3131
|
+
});
|
|
3132
|
+
currentMessages.push({
|
|
3133
|
+
role: "system",
|
|
3134
|
+
content: `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`
|
|
3135
|
+
});
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
throw new ValidationError(
|
|
3139
|
+
validated,
|
|
3140
|
+
validateResult.reason ?? "Validation failed",
|
|
3141
|
+
maxValidateRetries
|
|
3142
|
+
);
|
|
3088
3143
|
}
|
|
3089
3144
|
}
|
|
3090
3145
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3091
|
-
return content;
|
|
3146
|
+
return validated ?? content;
|
|
3092
3147
|
}
|
|
3093
3148
|
throw new MaxTurnsError("ctx.ask()", maxTurns);
|
|
3094
3149
|
}
|
|
@@ -3445,32 +3500,57 @@ ${summaryResponse.content}`
|
|
|
3445
3500
|
// ── ctx.verify() ──────────────────────────────────────────────────────
|
|
3446
3501
|
async verify(fn, schema, options) {
|
|
3447
3502
|
const maxRetries = options?.retries ?? 3;
|
|
3448
|
-
let
|
|
3449
|
-
let lastErrorMessage = void 0;
|
|
3503
|
+
let lastRetry = void 0;
|
|
3450
3504
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3451
|
-
let
|
|
3505
|
+
let rawOutput;
|
|
3452
3506
|
try {
|
|
3453
|
-
result = await fn(
|
|
3454
|
-
|
|
3455
|
-
|
|
3507
|
+
const result = await fn(lastRetry);
|
|
3508
|
+
rawOutput = result;
|
|
3509
|
+
const parsed = schema.parse(result);
|
|
3510
|
+
if (options?.validate) {
|
|
3511
|
+
let validateResult;
|
|
3512
|
+
try {
|
|
3513
|
+
validateResult = await options.validate(parsed, { metadata: this.metadata });
|
|
3514
|
+
} catch (err) {
|
|
3515
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3516
|
+
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
3517
|
+
}
|
|
3518
|
+
if (!validateResult.valid) {
|
|
3519
|
+
const errorMsg = validateResult.reason ?? "Validation failed";
|
|
3520
|
+
lastRetry = { error: errorMsg, output: rawOutput, parsed };
|
|
3521
|
+
if (attempt === maxRetries) {
|
|
3522
|
+
if (options?.fallback !== void 0) return options.fallback;
|
|
3523
|
+
throw new ValidationError(parsed, errorMsg, maxRetries);
|
|
3524
|
+
}
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
return parsed;
|
|
3456
3529
|
} catch (err) {
|
|
3457
|
-
if (err instanceof
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3530
|
+
if (err instanceof ValidationError) {
|
|
3531
|
+
lastRetry = {
|
|
3532
|
+
error: err.reason,
|
|
3533
|
+
output: rawOutput,
|
|
3534
|
+
parsed: err.lastOutput
|
|
3535
|
+
};
|
|
3536
|
+
if (attempt === maxRetries) {
|
|
3537
|
+
if (options?.fallback !== void 0) return options.fallback;
|
|
3538
|
+
throw err;
|
|
3539
|
+
}
|
|
3540
|
+
continue;
|
|
3463
3541
|
}
|
|
3542
|
+
const errorMsg = err instanceof import_zod.ZodError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3543
|
+
lastRetry = { error: errorMsg, output: rawOutput };
|
|
3464
3544
|
if (attempt === maxRetries) {
|
|
3465
3545
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3466
|
-
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([{ code: "custom", path: [], message:
|
|
3467
|
-
throw new VerifyError(
|
|
3546
|
+
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([{ code: "custom", path: [], message: errorMsg }]);
|
|
3547
|
+
throw new VerifyError(rawOutput, zodErr, maxRetries);
|
|
3468
3548
|
}
|
|
3469
3549
|
}
|
|
3470
3550
|
}
|
|
3471
3551
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3472
3552
|
throw new VerifyError(
|
|
3473
|
-
|
|
3553
|
+
lastRetry?.output,
|
|
3474
3554
|
new import_zod.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
|
|
3475
3555
|
maxRetries
|
|
3476
3556
|
);
|
|
@@ -3572,7 +3652,7 @@ ${summaryResponse.content}`
|
|
|
3572
3652
|
let remaining = fns.length;
|
|
3573
3653
|
for (const fn of fns) {
|
|
3574
3654
|
const p = signalStorage.run(composedSignal, fn);
|
|
3575
|
-
p.then((value) => {
|
|
3655
|
+
p.then(async (value) => {
|
|
3576
3656
|
if (settled) return;
|
|
3577
3657
|
if (schema) {
|
|
3578
3658
|
const parsed = schema.safeParse(value);
|
|
@@ -3585,6 +3665,33 @@ ${summaryResponse.content}`
|
|
|
3585
3665
|
}
|
|
3586
3666
|
return;
|
|
3587
3667
|
}
|
|
3668
|
+
if (options?.validate) {
|
|
3669
|
+
try {
|
|
3670
|
+
const validateResult = await options.validate(parsed.data, {
|
|
3671
|
+
metadata: this.metadata
|
|
3672
|
+
});
|
|
3673
|
+
if (!validateResult.valid) {
|
|
3674
|
+
remaining--;
|
|
3675
|
+
lastError = new Error(
|
|
3676
|
+
`Validation failed: ${validateResult.reason ?? "Validation failed"}`
|
|
3677
|
+
);
|
|
3678
|
+
if (remaining === 0 && !settled) {
|
|
3679
|
+
settled = true;
|
|
3680
|
+
reject(lastError);
|
|
3681
|
+
}
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
} catch (err) {
|
|
3685
|
+
remaining--;
|
|
3686
|
+
lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
|
|
3687
|
+
if (remaining === 0 && !settled) {
|
|
3688
|
+
settled = true;
|
|
3689
|
+
reject(lastError);
|
|
3690
|
+
}
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
if (settled) return;
|
|
3588
3695
|
settled = true;
|
|
3589
3696
|
controller.abort();
|
|
3590
3697
|
resolve(parsed.data);
|
|
@@ -3836,7 +3943,9 @@ ${summaryResponse.content}`
|
|
|
3836
3943
|
return this.ask(agents[0], prompt, {
|
|
3837
3944
|
schema: options?.schema,
|
|
3838
3945
|
retries: options?.retries,
|
|
3839
|
-
metadata: options?.metadata
|
|
3946
|
+
metadata: options?.metadata,
|
|
3947
|
+
validate: options?.validate,
|
|
3948
|
+
validateRetries: options?.validateRetries
|
|
3840
3949
|
});
|
|
3841
3950
|
}
|
|
3842
3951
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -3877,7 +3986,9 @@ ${summaryResponse.content}`
|
|
|
3877
3986
|
return this.ask(routerAgent, prompt, {
|
|
3878
3987
|
schema: options?.schema,
|
|
3879
3988
|
retries: options?.retries,
|
|
3880
|
-
metadata: options?.metadata
|
|
3989
|
+
metadata: options?.metadata,
|
|
3990
|
+
validate: options?.validate,
|
|
3991
|
+
validateRetries: options?.validateRetries
|
|
3881
3992
|
});
|
|
3882
3993
|
}
|
|
3883
3994
|
// ── Private ───────────────────────────────────────────────────────────
|
|
@@ -6159,6 +6270,7 @@ function cosineSimilarity2(a, b) {
|
|
|
6159
6270
|
SqliteVectorStore,
|
|
6160
6271
|
TimeoutError,
|
|
6161
6272
|
ToolDenied,
|
|
6273
|
+
ValidationError,
|
|
6162
6274
|
VerifyError,
|
|
6163
6275
|
WorkflowContext,
|
|
6164
6276
|
agent,
|