@axlsdk/axl 0.7.5 → 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 +267 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +266 -92
- 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,
|
|
@@ -310,29 +311,32 @@ async function fetchWithRetry(input, init, maxRetries = MAX_RETRIES) {
|
|
|
310
311
|
|
|
311
312
|
// src/providers/openai.ts
|
|
312
313
|
var OPENAI_PRICING = {
|
|
313
|
-
|
|
314
|
-
"gpt-4o
|
|
315
|
-
"gpt-
|
|
316
|
-
"gpt-4": [
|
|
317
|
-
"gpt-
|
|
318
|
-
"gpt-5": [
|
|
319
|
-
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
|
|
323
|
-
"gpt-
|
|
324
|
-
"gpt-
|
|
325
|
-
"gpt-
|
|
326
|
-
|
|
327
|
-
"
|
|
328
|
-
"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
"gpt-
|
|
334
|
-
"gpt-
|
|
335
|
-
"gpt-
|
|
314
|
+
// gpt-4o era — cache reads at 50% of input rate
|
|
315
|
+
"gpt-4o": [25e-7, 1e-5, 0.5],
|
|
316
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0.5],
|
|
317
|
+
"gpt-4-turbo": [1e-5, 3e-5, 0.5],
|
|
318
|
+
"gpt-4": [3e-5, 6e-5, 0.5],
|
|
319
|
+
"gpt-3.5-turbo": [5e-7, 15e-7, 0.5],
|
|
320
|
+
o1: [15e-6, 6e-5, 0.5],
|
|
321
|
+
"o1-mini": [3e-6, 12e-6, 0.5],
|
|
322
|
+
"o1-pro": [15e-5, 6e-4, 0.5],
|
|
323
|
+
// gpt-4.1 / o3 / o4 era — cache reads at 25% of input rate
|
|
324
|
+
"gpt-4.1": [2e-6, 8e-6, 0.25],
|
|
325
|
+
"gpt-4.1-mini": [4e-7, 16e-7, 0.25],
|
|
326
|
+
"gpt-4.1-nano": [1e-7, 4e-7, 0.25],
|
|
327
|
+
o3: [1e-5, 4e-5, 0.25],
|
|
328
|
+
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
329
|
+
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
330
|
+
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
331
|
+
// gpt-5 era — cache reads at 10% of input rate
|
|
332
|
+
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
333
|
+
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
334
|
+
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
335
|
+
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
336
|
+
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
337
|
+
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
338
|
+
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
339
|
+
"gpt-5.4-pro": [3e-5, 18e-5, 0.1]
|
|
336
340
|
};
|
|
337
341
|
var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
|
|
338
342
|
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
@@ -346,9 +350,9 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
|
|
|
346
350
|
}
|
|
347
351
|
}
|
|
348
352
|
if (!pricing) return 0;
|
|
349
|
-
const [inputRate, outputRate] = pricing;
|
|
353
|
+
const [inputRate, outputRate, cacheMultiplier] = pricing;
|
|
350
354
|
const cached = cachedTokens ?? 0;
|
|
351
|
-
const inputCost = (promptTokens - cached) * inputRate + cached * inputRate *
|
|
355
|
+
const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
|
|
352
356
|
return inputCost + completionTokens * outputRate;
|
|
353
357
|
}
|
|
354
358
|
function isOSeriesModel(model) {
|
|
@@ -458,7 +462,7 @@ var OpenAIProvider = class {
|
|
|
458
462
|
if (!res.body) {
|
|
459
463
|
throw new Error("OpenAI stream response has no body");
|
|
460
464
|
}
|
|
461
|
-
yield* this.parseSSEStream(res.body);
|
|
465
|
+
yield* this.parseSSEStream(res.body, options.model);
|
|
462
466
|
}
|
|
463
467
|
// ---------------------------------------------------------------------------
|
|
464
468
|
// Internal helpers
|
|
@@ -532,7 +536,7 @@ var OpenAIProvider = class {
|
|
|
532
536
|
if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
|
|
533
537
|
return out;
|
|
534
538
|
}
|
|
535
|
-
async *parseSSEStream(body) {
|
|
539
|
+
async *parseSSEStream(body, model) {
|
|
536
540
|
const reader = body.getReader();
|
|
537
541
|
const decoder = new TextDecoder();
|
|
538
542
|
let buffer = "";
|
|
@@ -549,7 +553,16 @@ var OpenAIProvider = class {
|
|
|
549
553
|
const trimmed = line.trim();
|
|
550
554
|
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
551
555
|
if (trimmed === "data: [DONE]") {
|
|
552
|
-
yield {
|
|
556
|
+
yield {
|
|
557
|
+
type: "done",
|
|
558
|
+
usage: usageData,
|
|
559
|
+
cost: usageData ? estimateOpenAICost(
|
|
560
|
+
model,
|
|
561
|
+
usageData.prompt_tokens,
|
|
562
|
+
usageData.completion_tokens,
|
|
563
|
+
usageData.cached_tokens
|
|
564
|
+
) : void 0
|
|
565
|
+
};
|
|
553
566
|
return;
|
|
554
567
|
}
|
|
555
568
|
if (trimmed.startsWith("data: ")) {
|
|
@@ -592,7 +605,16 @@ var OpenAIProvider = class {
|
|
|
592
605
|
}
|
|
593
606
|
}
|
|
594
607
|
}
|
|
595
|
-
yield {
|
|
608
|
+
yield {
|
|
609
|
+
type: "done",
|
|
610
|
+
usage: usageData,
|
|
611
|
+
cost: usageData ? estimateOpenAICost(
|
|
612
|
+
model,
|
|
613
|
+
usageData.prompt_tokens,
|
|
614
|
+
usageData.completion_tokens,
|
|
615
|
+
usageData.cached_tokens
|
|
616
|
+
) : void 0
|
|
617
|
+
};
|
|
596
618
|
} finally {
|
|
597
619
|
reader.releaseLock();
|
|
598
620
|
}
|
|
@@ -850,6 +872,7 @@ var OpenAIResponsesProvider = class {
|
|
|
850
872
|
const decoder = new TextDecoder();
|
|
851
873
|
let buffer = "";
|
|
852
874
|
const callIdMap = /* @__PURE__ */ new Map();
|
|
875
|
+
let eventType = "";
|
|
853
876
|
try {
|
|
854
877
|
while (true) {
|
|
855
878
|
const { done, value } = await reader.read();
|
|
@@ -857,7 +880,6 @@ var OpenAIResponsesProvider = class {
|
|
|
857
880
|
buffer += decoder.decode(value, { stream: true });
|
|
858
881
|
const lines = buffer.split("\n");
|
|
859
882
|
buffer = lines.pop() ?? "";
|
|
860
|
-
let eventType = "";
|
|
861
883
|
for (const line of lines) {
|
|
862
884
|
const trimmed = line.trim();
|
|
863
885
|
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
@@ -925,7 +947,17 @@ var OpenAIResponsesProvider = class {
|
|
|
925
947
|
} : void 0;
|
|
926
948
|
const reasoningItems = response?.output?.filter((item) => item.type === "reasoning") ?? [];
|
|
927
949
|
const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
|
|
928
|
-
return {
|
|
950
|
+
return {
|
|
951
|
+
type: "done",
|
|
952
|
+
usage,
|
|
953
|
+
cost: usage ? estimateOpenAICost(
|
|
954
|
+
model,
|
|
955
|
+
usage.prompt_tokens,
|
|
956
|
+
usage.completion_tokens,
|
|
957
|
+
usage.cached_tokens
|
|
958
|
+
) : void 0,
|
|
959
|
+
providerMetadata
|
|
960
|
+
};
|
|
929
961
|
}
|
|
930
962
|
case "response.failed": {
|
|
931
963
|
const errorMsg = data.response?.error?.message ?? data.response?.status_details?.error?.message ?? "Unknown error";
|
|
@@ -1058,7 +1090,7 @@ var AnthropicProvider = class {
|
|
|
1058
1090
|
if (!res.body) {
|
|
1059
1091
|
throw new Error("Anthropic stream response has no body");
|
|
1060
1092
|
}
|
|
1061
|
-
yield* this.parseSSEStream(res.body);
|
|
1093
|
+
yield* this.parseSSEStream(res.body, options.model);
|
|
1062
1094
|
}
|
|
1063
1095
|
// ---------------------------------------------------------------------------
|
|
1064
1096
|
// Internal: request building
|
|
@@ -1301,13 +1333,14 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1301
1333
|
// ---------------------------------------------------------------------------
|
|
1302
1334
|
// Internal: SSE stream parsing
|
|
1303
1335
|
// ---------------------------------------------------------------------------
|
|
1304
|
-
async *parseSSEStream(body) {
|
|
1336
|
+
async *parseSSEStream(body, model) {
|
|
1305
1337
|
const reader = body.getReader();
|
|
1306
1338
|
const decoder = new TextDecoder();
|
|
1307
1339
|
let buffer = "";
|
|
1308
1340
|
let currentToolId = "";
|
|
1309
1341
|
let currentToolName = "";
|
|
1310
1342
|
let usage;
|
|
1343
|
+
let cacheWrite = 0;
|
|
1311
1344
|
try {
|
|
1312
1345
|
while (true) {
|
|
1313
1346
|
const { done, value } = await reader.read();
|
|
@@ -1364,7 +1397,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1364
1397
|
case "message_start": {
|
|
1365
1398
|
if (event.message?.usage) {
|
|
1366
1399
|
const cacheRead = event.message.usage.cache_read_input_tokens ?? 0;
|
|
1367
|
-
|
|
1400
|
+
cacheWrite = event.message.usage.cache_creation_input_tokens ?? 0;
|
|
1368
1401
|
const inputTokens = (event.message.usage.input_tokens ?? 0) + cacheRead + cacheWrite;
|
|
1369
1402
|
usage = {
|
|
1370
1403
|
prompt_tokens: inputTokens,
|
|
@@ -1395,13 +1428,33 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1395
1428
|
if (usage) {
|
|
1396
1429
|
usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
|
|
1397
1430
|
}
|
|
1398
|
-
yield {
|
|
1431
|
+
yield {
|
|
1432
|
+
type: "done",
|
|
1433
|
+
usage,
|
|
1434
|
+
cost: usage ? estimateAnthropicCost(
|
|
1435
|
+
model,
|
|
1436
|
+
usage.prompt_tokens,
|
|
1437
|
+
usage.completion_tokens,
|
|
1438
|
+
usage.cached_tokens,
|
|
1439
|
+
cacheWrite
|
|
1440
|
+
) : void 0
|
|
1441
|
+
};
|
|
1399
1442
|
return;
|
|
1400
1443
|
}
|
|
1401
1444
|
}
|
|
1402
1445
|
}
|
|
1403
1446
|
}
|
|
1404
|
-
yield {
|
|
1447
|
+
yield {
|
|
1448
|
+
type: "done",
|
|
1449
|
+
usage,
|
|
1450
|
+
cost: usage ? estimateAnthropicCost(
|
|
1451
|
+
model,
|
|
1452
|
+
usage.prompt_tokens,
|
|
1453
|
+
usage.completion_tokens,
|
|
1454
|
+
usage.cached_tokens,
|
|
1455
|
+
cacheWrite
|
|
1456
|
+
) : void 0
|
|
1457
|
+
};
|
|
1405
1458
|
} finally {
|
|
1406
1459
|
reader.releaseLock();
|
|
1407
1460
|
}
|
|
@@ -1528,7 +1581,7 @@ var GeminiProvider = class {
|
|
|
1528
1581
|
if (!res.body) {
|
|
1529
1582
|
throw new Error("Gemini stream response has no body");
|
|
1530
1583
|
}
|
|
1531
|
-
yield* this.parseSSEStream(res.body);
|
|
1584
|
+
yield* this.parseSSEStream(res.body, options.model);
|
|
1532
1585
|
}
|
|
1533
1586
|
// ---------------------------------------------------------------------------
|
|
1534
1587
|
// Internal: request building
|
|
@@ -1809,7 +1862,7 @@ var GeminiProvider = class {
|
|
|
1809
1862
|
// ---------------------------------------------------------------------------
|
|
1810
1863
|
// Internal: SSE stream parsing
|
|
1811
1864
|
// ---------------------------------------------------------------------------
|
|
1812
|
-
async *parseSSEStream(body) {
|
|
1865
|
+
async *parseSSEStream(body, model) {
|
|
1813
1866
|
const reader = body.getReader();
|
|
1814
1867
|
const decoder = new TextDecoder();
|
|
1815
1868
|
let buffer = "";
|
|
@@ -1865,7 +1918,17 @@ var GeminiProvider = class {
|
|
|
1865
1918
|
}
|
|
1866
1919
|
}
|
|
1867
1920
|
const providerMetadata = accumulatedParts.length > 0 ? { geminiParts: accumulatedParts } : void 0;
|
|
1868
|
-
yield {
|
|
1921
|
+
yield {
|
|
1922
|
+
type: "done",
|
|
1923
|
+
usage,
|
|
1924
|
+
cost: usage ? estimateGeminiCost(
|
|
1925
|
+
model,
|
|
1926
|
+
usage.prompt_tokens,
|
|
1927
|
+
usage.completion_tokens,
|
|
1928
|
+
usage.cached_tokens
|
|
1929
|
+
) : void 0,
|
|
1930
|
+
providerMetadata
|
|
1931
|
+
};
|
|
1869
1932
|
} finally {
|
|
1870
1933
|
reader.releaseLock();
|
|
1871
1934
|
}
|
|
@@ -2081,6 +2144,18 @@ var GuardrailError = class extends AxlError {
|
|
|
2081
2144
|
this.reason = reason;
|
|
2082
2145
|
}
|
|
2083
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
|
+
};
|
|
2084
2159
|
var ToolDenied = class extends AxlError {
|
|
2085
2160
|
toolName;
|
|
2086
2161
|
agentName;
|
|
@@ -2345,9 +2420,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2345
2420
|
agent2,
|
|
2346
2421
|
prompt,
|
|
2347
2422
|
options,
|
|
2348
|
-
0,
|
|
2349
|
-
void 0,
|
|
2350
|
-
void 0,
|
|
2351
2423
|
void 0,
|
|
2352
2424
|
usageCapture
|
|
2353
2425
|
);
|
|
@@ -2397,7 +2469,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2397
2469
|
return result;
|
|
2398
2470
|
});
|
|
2399
2471
|
}
|
|
2400
|
-
async executeAgentCall(agent2, prompt, options,
|
|
2472
|
+
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2401
2473
|
if (this.budgetContext?.exceeded) {
|
|
2402
2474
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2403
2475
|
if (policy === "warn") {
|
|
@@ -2469,16 +2541,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2469
2541
|
|
|
2470
2542
|
Respond with valid JSON matching this schema:
|
|
2471
2543
|
${JSON.stringify(jsonSchema, null, 2)}`;
|
|
2472
|
-
}
|
|
2473
|
-
if (previousOutput && previousError) {
|
|
2474
|
-
userContent += `
|
|
2475
|
-
|
|
2476
|
-
Your previous response was invalid:
|
|
2477
|
-
${previousOutput}
|
|
2478
|
-
|
|
2479
|
-
Error: ${previousError}
|
|
2480
|
-
|
|
2481
|
-
Please fix and try again.`;
|
|
2482
2544
|
}
|
|
2483
2545
|
messages.push({ role: "user", content: userContent });
|
|
2484
2546
|
if (handoffMessages && handoffMessages.length > 0) {
|
|
@@ -2523,9 +2585,17 @@ Please fix and try again.`;
|
|
|
2523
2585
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2524
2586
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2525
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
|
+
}
|
|
2526
2594
|
const currentMessages = [...messages];
|
|
2527
2595
|
let turns = 0;
|
|
2528
2596
|
let guardrailOutputRetries = 0;
|
|
2597
|
+
let schemaRetries = 0;
|
|
2598
|
+
let validateRetries = 0;
|
|
2529
2599
|
while (turns < maxTurns) {
|
|
2530
2600
|
if (Date.now() - startTime > timeoutMs) {
|
|
2531
2601
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
@@ -2575,7 +2645,8 @@ Please fix and try again.`;
|
|
|
2575
2645
|
response = {
|
|
2576
2646
|
content: content2,
|
|
2577
2647
|
tool_calls: void 0,
|
|
2578
|
-
usage: chunk.usage
|
|
2648
|
+
usage: chunk.usage,
|
|
2649
|
+
cost: chunk.cost
|
|
2579
2650
|
};
|
|
2580
2651
|
}
|
|
2581
2652
|
}
|
|
@@ -2652,14 +2723,17 @@ Please fix and try again.`;
|
|
|
2652
2723
|
}
|
|
2653
2724
|
}
|
|
2654
2725
|
const handoffStart = Date.now();
|
|
2655
|
-
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;
|
|
2656
2733
|
const handoffFn = () => this.executeAgentCall(
|
|
2657
2734
|
descriptor.agent,
|
|
2658
2735
|
handoffPrompt,
|
|
2659
2736
|
handoffOptions,
|
|
2660
|
-
0,
|
|
2661
|
-
void 0,
|
|
2662
|
-
void 0,
|
|
2663
2737
|
currentMessages,
|
|
2664
2738
|
usageCapture
|
|
2665
2739
|
);
|
|
@@ -2993,26 +3067,26 @@ Please fix and try again.`;
|
|
|
2993
3067
|
throw new GuardrailError("output", outputResult.reason ?? "Output blocked by guardrail");
|
|
2994
3068
|
}
|
|
2995
3069
|
}
|
|
3070
|
+
let validated = void 0;
|
|
2996
3071
|
if (options?.schema) {
|
|
2997
3072
|
try {
|
|
2998
3073
|
const parsed = JSON.parse(stripMarkdownFences(content));
|
|
2999
|
-
|
|
3000
|
-
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3001
|
-
return validated;
|
|
3074
|
+
validated = options.schema.parse(parsed);
|
|
3002
3075
|
} catch (err) {
|
|
3003
|
-
const
|
|
3004
|
-
if (
|
|
3076
|
+
const maxSchemaRetries = options.retries ?? 3;
|
|
3077
|
+
if (schemaRetries < maxSchemaRetries) {
|
|
3078
|
+
schemaRetries++;
|
|
3005
3079
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
prompt,
|
|
3009
|
-
options,
|
|
3010
|
-
retryCount + 1,
|
|
3080
|
+
currentMessages.push({
|
|
3081
|
+
role: "assistant",
|
|
3011
3082
|
content,
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
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;
|
|
3016
3090
|
}
|
|
3017
3091
|
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([
|
|
3018
3092
|
{
|
|
@@ -3021,11 +3095,55 @@ Please fix and try again.`;
|
|
|
3021
3095
|
message: err instanceof Error ? err.message : String(err)
|
|
3022
3096
|
}
|
|
3023
3097
|
]);
|
|
3024
|
-
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
|
+
);
|
|
3025
3143
|
}
|
|
3026
3144
|
}
|
|
3027
3145
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3028
|
-
return content;
|
|
3146
|
+
return validated ?? content;
|
|
3029
3147
|
}
|
|
3030
3148
|
throw new MaxTurnsError("ctx.ask()", maxTurns);
|
|
3031
3149
|
}
|
|
@@ -3382,32 +3500,57 @@ ${summaryResponse.content}`
|
|
|
3382
3500
|
// ── ctx.verify() ──────────────────────────────────────────────────────
|
|
3383
3501
|
async verify(fn, schema, options) {
|
|
3384
3502
|
const maxRetries = options?.retries ?? 3;
|
|
3385
|
-
let
|
|
3386
|
-
let lastErrorMessage = void 0;
|
|
3503
|
+
let lastRetry = void 0;
|
|
3387
3504
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3388
|
-
let
|
|
3505
|
+
let rawOutput;
|
|
3389
3506
|
try {
|
|
3390
|
-
result = await fn(
|
|
3391
|
-
|
|
3392
|
-
|
|
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;
|
|
3393
3529
|
} catch (err) {
|
|
3394
|
-
if (err instanceof
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
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;
|
|
3400
3541
|
}
|
|
3542
|
+
const errorMsg = err instanceof import_zod.ZodError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3543
|
+
lastRetry = { error: errorMsg, output: rawOutput };
|
|
3401
3544
|
if (attempt === maxRetries) {
|
|
3402
3545
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3403
|
-
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([{ code: "custom", path: [], message:
|
|
3404
|
-
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);
|
|
3405
3548
|
}
|
|
3406
3549
|
}
|
|
3407
3550
|
}
|
|
3408
3551
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3409
3552
|
throw new VerifyError(
|
|
3410
|
-
|
|
3553
|
+
lastRetry?.output,
|
|
3411
3554
|
new import_zod.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
|
|
3412
3555
|
maxRetries
|
|
3413
3556
|
);
|
|
@@ -3509,7 +3652,7 @@ ${summaryResponse.content}`
|
|
|
3509
3652
|
let remaining = fns.length;
|
|
3510
3653
|
for (const fn of fns) {
|
|
3511
3654
|
const p = signalStorage.run(composedSignal, fn);
|
|
3512
|
-
p.then((value) => {
|
|
3655
|
+
p.then(async (value) => {
|
|
3513
3656
|
if (settled) return;
|
|
3514
3657
|
if (schema) {
|
|
3515
3658
|
const parsed = schema.safeParse(value);
|
|
@@ -3522,6 +3665,33 @@ ${summaryResponse.content}`
|
|
|
3522
3665
|
}
|
|
3523
3666
|
return;
|
|
3524
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;
|
|
3525
3695
|
settled = true;
|
|
3526
3696
|
controller.abort();
|
|
3527
3697
|
resolve(parsed.data);
|
|
@@ -3773,7 +3943,9 @@ ${summaryResponse.content}`
|
|
|
3773
3943
|
return this.ask(agents[0], prompt, {
|
|
3774
3944
|
schema: options?.schema,
|
|
3775
3945
|
retries: options?.retries,
|
|
3776
|
-
metadata: options?.metadata
|
|
3946
|
+
metadata: options?.metadata,
|
|
3947
|
+
validate: options?.validate,
|
|
3948
|
+
validateRetries: options?.validateRetries
|
|
3777
3949
|
});
|
|
3778
3950
|
}
|
|
3779
3951
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -3814,7 +3986,9 @@ ${summaryResponse.content}`
|
|
|
3814
3986
|
return this.ask(routerAgent, prompt, {
|
|
3815
3987
|
schema: options?.schema,
|
|
3816
3988
|
retries: options?.retries,
|
|
3817
|
-
metadata: options?.metadata
|
|
3989
|
+
metadata: options?.metadata,
|
|
3990
|
+
validate: options?.validate,
|
|
3991
|
+
validateRetries: options?.validateRetries
|
|
3818
3992
|
});
|
|
3819
3993
|
}
|
|
3820
3994
|
// ── Private ───────────────────────────────────────────────────────────
|
|
@@ -6096,6 +6270,7 @@ function cosineSimilarity2(a, b) {
|
|
|
6096
6270
|
SqliteVectorStore,
|
|
6097
6271
|
TimeoutError,
|
|
6098
6272
|
ToolDenied,
|
|
6273
|
+
ValidationError,
|
|
6099
6274
|
VerifyError,
|
|
6100
6275
|
WorkflowContext,
|
|
6101
6276
|
agent,
|