@apo-ai/sdk 0.7.1 → 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/dist/agent-task/integrations/ai-sdk.d.ts +1 -1
- package/dist/agent-task/integrations/ai-sdk.js +2 -2
- package/dist/agent-task/integrations/anthropic.d.ts +1 -1
- package/dist/agent-task/integrations/anthropic.js +2 -2
- package/dist/agent-task/integrations/openai.d.ts +2 -2
- package/dist/agent-task/integrations/openai.js +2 -2
- package/dist/agent-task/public.d.ts +35 -5
- package/dist/agent-task/public.js +623 -252
- package/dist/agent-task/public.js.map +1 -1
- package/dist/{chunk-6ZRW5IW6.js → chunk-3B3L2H4Y.js} +5 -2
- package/dist/chunk-3B3L2H4Y.js.map +1 -0
- package/dist/{chunk-GDB6SDGN.js → chunk-3OQ3JBFJ.js} +2 -1
- package/dist/chunk-3OQ3JBFJ.js.map +1 -0
- package/dist/{chunk-SZDSIPXC.js → chunk-43OI6GP7.js} +2 -2
- package/dist/{chunk-QHRWPVPZ.js → chunk-CKB3SUDO.js} +2 -2
- package/dist/{chunk-HWZVL6TS.js → chunk-HLRXQAZ5.js} +11 -6
- package/dist/chunk-HLRXQAZ5.js.map +1 -0
- package/dist/{chunk-GETY34VI.js → chunk-IPQ6IIEG.js} +3 -2
- package/dist/{chunk-GETY34VI.js.map → chunk-IPQ6IIEG.js.map} +1 -1
- package/dist/otel/index.js +2 -2
- package/dist/{tracing-DVLUqD-i.d.ts → tracing-CevT0hQg.d.ts} +13 -0
- package/package.json +1 -1
- package/dist/chunk-6ZRW5IW6.js.map +0 -1
- package/dist/chunk-GDB6SDGN.js.map +0 -1
- package/dist/chunk-HWZVL6TS.js.map +0 -1
- /package/dist/{chunk-SZDSIPXC.js.map → chunk-43OI6GP7.js.map} +0 -0
- /package/dist/{chunk-QHRWPVPZ.js.map → chunk-CKB3SUDO.js.map} +0 -0
|
@@ -8,18 +8,18 @@ import {
|
|
|
8
8
|
resetApoTracing,
|
|
9
9
|
withApoRun,
|
|
10
10
|
withApoRunSync
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-IPQ6IIEG.js";
|
|
12
12
|
import {
|
|
13
13
|
createApoTracer
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-CKB3SUDO.js";
|
|
15
|
+
import "../chunk-HLRXQAZ5.js";
|
|
16
16
|
import {
|
|
17
17
|
createApoOpenAI
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-3B3L2H4Y.js";
|
|
19
19
|
import {
|
|
20
20
|
createApoAnthropic
|
|
21
|
-
} from "../chunk-
|
|
22
|
-
import "../chunk-
|
|
21
|
+
} from "../chunk-43OI6GP7.js";
|
|
22
|
+
import "../chunk-3OQ3JBFJ.js";
|
|
23
23
|
|
|
24
24
|
// src/agent-task/trace-projection/view.ts
|
|
25
25
|
function invocationOrderKey(obs) {
|
|
@@ -314,6 +314,12 @@ async function callSecondJudge(args) {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// src/agent-task/checks/judge.ts
|
|
317
|
+
var JudgeUnavailableError = class extends Error {
|
|
318
|
+
constructor(message) {
|
|
319
|
+
super(message);
|
|
320
|
+
this.name = "JudgeUnavailableError";
|
|
321
|
+
}
|
|
322
|
+
};
|
|
317
323
|
var VERDICT_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"pass": true/false, "reasoning": "your reasoning"}';
|
|
318
324
|
var REASONING_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"reasoning": "your reasoning", "pass": true/false}';
|
|
319
325
|
function isJudgeVerdictFirstOverrideEnabled() {
|
|
@@ -389,7 +395,92 @@ function parseJudgeJson(raw) {
|
|
|
389
395
|
reasoning: "Judge response could not be parsed as JSON \u2014 the verdict is unknown, so this check is treated as a failure. The model's raw response is available in the judge metadata."
|
|
390
396
|
};
|
|
391
397
|
}
|
|
392
|
-
var JUDGE_TIMEOUT_MS =
|
|
398
|
+
var JUDGE_TIMEOUT_MS = 3e5;
|
|
399
|
+
var JUDGE_IDLE_TIMEOUT_MS = 9e4;
|
|
400
|
+
var JUDGE_RETRY_DELAY_MS = 1e3;
|
|
401
|
+
var JUDGE_RETRY_AFTER_CAP_MS = 2e4;
|
|
402
|
+
var JUDGE_MIN_RETRY_BUDGET_MS = 6e4;
|
|
403
|
+
function isRetryableStatus(status) {
|
|
404
|
+
return status === 429 || status >= 500;
|
|
405
|
+
}
|
|
406
|
+
function retryDelayMs(retryAfter) {
|
|
407
|
+
if (!retryAfter) return JUDGE_RETRY_DELAY_MS;
|
|
408
|
+
const seconds = Number(retryAfter);
|
|
409
|
+
const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(retryAfter) - Date.now();
|
|
410
|
+
if (!Number.isFinite(ms)) return JUDGE_RETRY_DELAY_MS;
|
|
411
|
+
return Math.min(Math.max(ms, JUDGE_RETRY_DELAY_MS), JUDGE_RETRY_AFTER_CAP_MS);
|
|
412
|
+
}
|
|
413
|
+
async function readCompletion(response, onData) {
|
|
414
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
415
|
+
if (!contentType.includes("text/event-stream")) {
|
|
416
|
+
const data = await response.json();
|
|
417
|
+
return {
|
|
418
|
+
text: data.choices?.[0]?.message?.content ?? "",
|
|
419
|
+
usage: data.usage,
|
|
420
|
+
finishReason: data.choices?.[0]?.finish_reason ?? void 0
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
let text = "";
|
|
424
|
+
let usage;
|
|
425
|
+
let finishReason;
|
|
426
|
+
let eventType = "message";
|
|
427
|
+
let dataLines = [];
|
|
428
|
+
let finished = false;
|
|
429
|
+
const dispatch = () => {
|
|
430
|
+
const payload = dataLines.join("\n").trim();
|
|
431
|
+
const type = eventType;
|
|
432
|
+
eventType = "message";
|
|
433
|
+
dataLines = [];
|
|
434
|
+
if (payload === "[DONE]") finished = true;
|
|
435
|
+
if (!payload || finished) return;
|
|
436
|
+
onData();
|
|
437
|
+
let chunk;
|
|
438
|
+
try {
|
|
439
|
+
chunk = JSON.parse(payload);
|
|
440
|
+
} catch {
|
|
441
|
+
chunk = void 0;
|
|
442
|
+
}
|
|
443
|
+
if (type === "error" || chunk?.error) {
|
|
444
|
+
const error = chunk?.error;
|
|
445
|
+
const message = typeof error === "string" ? error : error?.message ?? (chunk ? void 0 : payload);
|
|
446
|
+
throw new JudgeUnavailableError(`Judge stream error: ${message ?? "unknown"}`);
|
|
447
|
+
}
|
|
448
|
+
if (!chunk) return;
|
|
449
|
+
const choice = chunk.choices?.[0];
|
|
450
|
+
text += choice?.delta?.content ?? "";
|
|
451
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
452
|
+
if (chunk.usage) usage = chunk.usage;
|
|
453
|
+
};
|
|
454
|
+
const handleLine = (line) => {
|
|
455
|
+
if (line === "") return dispatch();
|
|
456
|
+
if (line.startsWith(":")) return;
|
|
457
|
+
const colon = line.indexOf(":");
|
|
458
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
459
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
460
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
461
|
+
if (field === "data") dataLines.push(value);
|
|
462
|
+
else if (field === "event") eventType = value;
|
|
463
|
+
};
|
|
464
|
+
const reader = response.body?.getReader();
|
|
465
|
+
if (!reader) return { text, usage, finishReason };
|
|
466
|
+
const decoder = new TextDecoder();
|
|
467
|
+
let buffer = "";
|
|
468
|
+
for (; ; ) {
|
|
469
|
+
const { done, value } = await reader.read();
|
|
470
|
+
if (done) break;
|
|
471
|
+
buffer += decoder.decode(value, { stream: true });
|
|
472
|
+
const lines = buffer.split(/\r\n|\r(?!$)|\n/);
|
|
473
|
+
buffer = lines.pop() ?? "";
|
|
474
|
+
for (const line of lines) {
|
|
475
|
+
handleLine(line);
|
|
476
|
+
if (finished) return { text, usage, finishReason };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
buffer += decoder.decode();
|
|
480
|
+
for (const line of buffer.split(/\r\n|\r|\n/)) if (line) handleLine(line);
|
|
481
|
+
dispatch();
|
|
482
|
+
return { text, usage, finishReason };
|
|
483
|
+
}
|
|
393
484
|
var prefixQueues = /* @__PURE__ */ new Map();
|
|
394
485
|
function runWithSharedPrefix(key, task2) {
|
|
395
486
|
const prev = prefixQueues.get(key) ?? Promise.resolve();
|
|
@@ -422,75 +513,155 @@ ${instructionText}`,
|
|
|
422
513
|
apiKey: resolveSecondJudgeAPIKey(apiKey)
|
|
423
514
|
}) : void 0;
|
|
424
515
|
const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
433
|
-
},
|
|
434
|
-
body: JSON.stringify({
|
|
435
|
-
model: args.model,
|
|
436
|
-
messages: [
|
|
516
|
+
const requestBody = (stream) => JSON.stringify({
|
|
517
|
+
model: args.model,
|
|
518
|
+
messages: [
|
|
519
|
+
{
|
|
520
|
+
role: "system",
|
|
521
|
+
content: [
|
|
522
|
+
{ type: "text", text: briefingText },
|
|
437
523
|
{
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
524
|
+
type: "text",
|
|
525
|
+
text: deliverableText,
|
|
526
|
+
cache_control: { type: "ephemeral" }
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
},
|
|
530
|
+
{ role: "user", content: instructionText }
|
|
531
|
+
],
|
|
532
|
+
temperature: 0,
|
|
533
|
+
response_format: { type: "json_object" },
|
|
534
|
+
...stream ? { stream: true, stream_options: { include_usage: true } } : {}
|
|
535
|
+
});
|
|
536
|
+
const attempt = async (stream, deadline) => {
|
|
537
|
+
const controller = new AbortController();
|
|
538
|
+
const expire = (reason, name) => () => controller.abort(new DOMException(reason, name));
|
|
539
|
+
const total = setTimeout(
|
|
540
|
+
expire(`no complete response within ${JUDGE_TIMEOUT_MS / 1e3}s`, "TimeoutError"),
|
|
541
|
+
Math.max(deadline - Date.now(), 0)
|
|
542
|
+
);
|
|
543
|
+
const stalled = expire(`no data for ${JUDGE_IDLE_TIMEOUT_MS / 1e3}s`, "IdleTimeoutError");
|
|
544
|
+
let idle;
|
|
545
|
+
const onData = () => {
|
|
546
|
+
clearTimeout(idle);
|
|
547
|
+
idle = setTimeout(stalled, JUDGE_IDLE_TIMEOUT_MS);
|
|
548
|
+
};
|
|
549
|
+
try {
|
|
550
|
+
let response;
|
|
551
|
+
try {
|
|
552
|
+
response = await fetch(`${baseURL}/chat/completions`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
signal: controller.signal,
|
|
555
|
+
headers: {
|
|
556
|
+
"Content-Type": "application/json",
|
|
557
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
447
558
|
},
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
559
|
+
body: requestBody(stream)
|
|
560
|
+
});
|
|
561
|
+
} catch (error) {
|
|
562
|
+
return transportFailure("Judge request failed", controller.signal.reason ?? error);
|
|
563
|
+
}
|
|
564
|
+
if (!response.ok) {
|
|
565
|
+
const body = await response.text().catch(() => "");
|
|
566
|
+
return {
|
|
567
|
+
kind: "unavailable",
|
|
568
|
+
error: new JudgeUnavailableError(`Judge API ${response.status}: ${body.slice(0, 200)}`),
|
|
569
|
+
retryable: isRetryableStatus(response.status),
|
|
570
|
+
...isRetryableStatus(response.status) ? { retryDelayMs: retryDelayMs(response.headers.get("retry-after")) } : {},
|
|
571
|
+
// Name the field, not the word: gateways echo request params in
|
|
572
|
+
// unrelated 400 bodies.
|
|
573
|
+
...stream && response.status === 400 && /stream_options|["'`]stream["'`]/.test(body) ? { streamRejected: true } : {}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
return { kind: "completion", completion: await readCompletion(response, onData) };
|
|
578
|
+
} catch (error) {
|
|
579
|
+
return transportFailure("Judge response failed", controller.signal.reason ?? error);
|
|
580
|
+
}
|
|
581
|
+
} finally {
|
|
582
|
+
clearTimeout(total);
|
|
583
|
+
clearTimeout(idle);
|
|
584
|
+
controller.abort();
|
|
457
585
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
586
|
+
};
|
|
587
|
+
const isTruncated = ({ text, usage, finishReason }) => {
|
|
588
|
+
if (!text.trim()) return true;
|
|
589
|
+
if (usage !== void 0 && usage.completion_tokens === 0) return true;
|
|
590
|
+
if (finishReason !== "length" && finishReason !== "content_filter" && finishReason !== "error") {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
JSON.parse(text);
|
|
595
|
+
return false;
|
|
596
|
+
} catch {
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
return runWithSharedPrefix(cacheKey, async () => {
|
|
601
|
+
const startedAt = Date.now();
|
|
602
|
+
const deadline = startedAt + JUDGE_TIMEOUT_MS;
|
|
603
|
+
let stream = true;
|
|
604
|
+
let result = await attempt(stream, deadline);
|
|
605
|
+
const truncated = result.kind === "completion" && isTruncated(result.completion);
|
|
606
|
+
const streamRejected = result.kind === "unavailable" && result.streamRejected === true;
|
|
607
|
+
const retryable = result.kind === "unavailable" && result.retryable;
|
|
608
|
+
if (truncated || streamRejected || retryable) {
|
|
609
|
+
const delay = result.kind === "unavailable" && result.retryDelayMs !== void 0 ? result.retryDelayMs : streamRejected ? 0 : JUDGE_RETRY_DELAY_MS;
|
|
610
|
+
if (deadline - Date.now() - delay >= JUDGE_MIN_RETRY_BUDGET_MS) {
|
|
611
|
+
if (streamRejected) stream = false;
|
|
612
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
613
|
+
const first = result;
|
|
614
|
+
result = await attempt(stream, deadline);
|
|
615
|
+
if (result.kind === "unavailable" && first.kind === "unavailable") {
|
|
616
|
+
result = {
|
|
617
|
+
...result,
|
|
618
|
+
error: new JudgeUnavailableError(
|
|
619
|
+
`${first.error.message} (retry: ${result.error.message})`
|
|
620
|
+
)
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (result.kind === "unavailable") throw result.error;
|
|
626
|
+
const { text, usage } = result.completion;
|
|
627
|
+
const judge = {
|
|
628
|
+
model: args.model,
|
|
629
|
+
contract: judgeContractInUse(),
|
|
630
|
+
prompt: { system: systemPromptText, user: instructionText },
|
|
631
|
+
response: text,
|
|
632
|
+
tokens: parseJudgeUsage(usage),
|
|
633
|
+
latency_ms: Date.now() - startedAt,
|
|
634
|
+
secondJudge: await secondJudgePromise
|
|
635
|
+
};
|
|
636
|
+
if (isTruncated(result.completion)) {
|
|
464
637
|
return {
|
|
465
638
|
pass: false,
|
|
466
|
-
reasoning: "Judge returned an empty or truncated response \u2014 likely a transient provider failure. The verdict is unknown, so this check is
|
|
467
|
-
judge
|
|
468
|
-
|
|
469
|
-
contract: judgeContractInUse(),
|
|
470
|
-
prompt: { system: systemPromptText, user: instructionText },
|
|
471
|
-
response: text,
|
|
472
|
-
tokens: parseJudgeUsage(data.usage),
|
|
473
|
-
latency_ms: Date.now() - startedAt,
|
|
474
|
-
secondJudge: await secondJudgePromise
|
|
475
|
-
}
|
|
639
|
+
reasoning: "Judge returned an empty or truncated response \u2014 likely a transient provider failure. The verdict is unknown, so this check is recorded as a judge error, not a verdict.",
|
|
640
|
+
judge,
|
|
641
|
+
unavailable: true
|
|
476
642
|
};
|
|
477
643
|
}
|
|
478
644
|
const parsed = parseJudgeJson(text);
|
|
479
645
|
return {
|
|
480
646
|
pass: parsed.pass === true,
|
|
481
647
|
reasoning: typeof parsed.reasoning === "string" ? parsed.reasoning : "",
|
|
482
|
-
judge
|
|
483
|
-
model: args.model,
|
|
484
|
-
contract: judgeContractInUse(),
|
|
485
|
-
prompt: { system: systemPromptText, user: instructionText },
|
|
486
|
-
response: text,
|
|
487
|
-
tokens: parseJudgeUsage(data.usage),
|
|
488
|
-
latency_ms: Date.now() - startedAt,
|
|
489
|
-
secondJudge: await secondJudgePromise
|
|
490
|
-
}
|
|
648
|
+
judge
|
|
491
649
|
};
|
|
492
650
|
});
|
|
493
651
|
}
|
|
652
|
+
function transportFailure(prefix, error) {
|
|
653
|
+
if (error instanceof JudgeUnavailableError) {
|
|
654
|
+
return { kind: "unavailable", error, retryable: true };
|
|
655
|
+
}
|
|
656
|
+
const timedOut = error instanceof Error && error.name === "TimeoutError";
|
|
657
|
+
return {
|
|
658
|
+
kind: "unavailable",
|
|
659
|
+
error: new JudgeUnavailableError(
|
|
660
|
+
`${prefix}: ${error instanceof Error ? error.message : String(error)}`
|
|
661
|
+
),
|
|
662
|
+
retryable: !timedOut
|
|
663
|
+
};
|
|
664
|
+
}
|
|
494
665
|
function assembleBriefing(args) {
|
|
495
666
|
if (!args.prompt) {
|
|
496
667
|
return {
|
|
@@ -521,6 +692,53 @@ ${args.instruction}`
|
|
|
521
692
|
|
|
522
693
|
// src/agent-task/checks/agent-session.ts
|
|
523
694
|
import { createHash } from "crypto";
|
|
695
|
+
|
|
696
|
+
// src/agent-task/tracing.ts
|
|
697
|
+
function createNoopAgentTaskTraceContext() {
|
|
698
|
+
const noopFn = async (fn) => fn();
|
|
699
|
+
return {
|
|
700
|
+
runId: "agent-task-untraced",
|
|
701
|
+
rootSpanId: "agent-task-untraced-root",
|
|
702
|
+
async step(options, fn) {
|
|
703
|
+
return fn(options.id ?? "agent-task-untraced-step");
|
|
704
|
+
},
|
|
705
|
+
recordEvent(options) {
|
|
706
|
+
return options.id ?? "agent-task-untraced-event";
|
|
707
|
+
},
|
|
708
|
+
endRoot() {
|
|
709
|
+
},
|
|
710
|
+
traceTool(_name, _params, fn) {
|
|
711
|
+
return noopFn(fn);
|
|
712
|
+
},
|
|
713
|
+
traceRetriever(_query, fn) {
|
|
714
|
+
return noopFn(fn);
|
|
715
|
+
},
|
|
716
|
+
traceChain(_name, fn) {
|
|
717
|
+
return noopFn(fn);
|
|
718
|
+
},
|
|
719
|
+
traceAgent(_name, fn) {
|
|
720
|
+
return noopFn(fn);
|
|
721
|
+
},
|
|
722
|
+
traceGuardrail(_name, fn) {
|
|
723
|
+
return noopFn(fn);
|
|
724
|
+
},
|
|
725
|
+
traceEmbedding(_model, _input, fn) {
|
|
726
|
+
return noopFn(fn);
|
|
727
|
+
},
|
|
728
|
+
async score(_params) {
|
|
729
|
+
},
|
|
730
|
+
createSpan(_options) {
|
|
731
|
+
return "agent-task-untraced-span";
|
|
732
|
+
},
|
|
733
|
+
endSpan(_spanId, _params) {
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function isTraceableSpanId(spanId) {
|
|
738
|
+
return spanId !== void 0 && !spanId.startsWith("agent-task-untraced");
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/agent-task/checks/agent-session.ts
|
|
524
742
|
var DEFAULT_BUDGET = {
|
|
525
743
|
maxTurns: 12,
|
|
526
744
|
maxToolCalls: 24,
|
|
@@ -581,7 +799,7 @@ function extractVerdict(steps) {
|
|
|
581
799
|
return void 0;
|
|
582
800
|
}
|
|
583
801
|
function buildEvidenceTools(args) {
|
|
584
|
-
const { z, tool, evidence, scope, budget, ledger, stepIndexOf, includeTrace } = args;
|
|
802
|
+
const { z, tool, evidence, scope, budget, ledger, stepIndexOf, includeTrace, includeHistory, tracer } = args;
|
|
585
803
|
const budgetGuard = () => {
|
|
586
804
|
ledger.toolCallsUsed += 1;
|
|
587
805
|
if (ledger.toolCallsUsed > budget.maxToolCalls) {
|
|
@@ -600,6 +818,7 @@ function buildEvidenceTools(args) {
|
|
|
600
818
|
});
|
|
601
819
|
return ledger.readBytesUsed > budget.maxReadBytes ? overflow() : null;
|
|
602
820
|
};
|
|
821
|
+
const span = (name, input, fn) => tracer ? tracer.traceTool(name, input, fn) : fn();
|
|
603
822
|
const tools = {
|
|
604
823
|
read_deliverable: tool({
|
|
605
824
|
description: `Read one of this run's deliverables by name, with optional offset/limit for pagination (max ${MAX_READ_WINDOW} bytes per call).`,
|
|
@@ -610,21 +829,23 @@ function buildEvidenceTools(args) {
|
|
|
610
829
|
}),
|
|
611
830
|
execute: async (input) => {
|
|
612
831
|
const { name, offset, limit } = input;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
832
|
+
return span("read_deliverable", input, async () => {
|
|
833
|
+
const guard = budgetGuard();
|
|
834
|
+
if (guard) return { error: guard };
|
|
835
|
+
if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
|
|
836
|
+
return { error: `unknown deliverable: ${name}` };
|
|
837
|
+
}
|
|
838
|
+
const content = renderValue(evidence.deliverables[name]);
|
|
839
|
+
const slice = content.slice(offset, offset + limit);
|
|
840
|
+
const overflow = accountRead(
|
|
841
|
+
"read_deliverable",
|
|
842
|
+
JSON.stringify({ name, offset, limit }),
|
|
843
|
+
slice,
|
|
844
|
+
() => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
|
|
845
|
+
);
|
|
846
|
+
if (overflow) return { error: overflow };
|
|
847
|
+
return { name, total_bytes: content.length, offset, returned: slice.length, content: slice };
|
|
848
|
+
});
|
|
628
849
|
}
|
|
629
850
|
}),
|
|
630
851
|
search_deliverable: tool({
|
|
@@ -632,49 +853,53 @@ function buildEvidenceTools(args) {
|
|
|
632
853
|
inputSchema: z.object({ name: z.string(), pattern: z.string() }),
|
|
633
854
|
execute: async (input) => {
|
|
634
855
|
const { name, pattern } = input;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
856
|
+
return span("search_deliverable", input, async () => {
|
|
857
|
+
const guard = budgetGuard();
|
|
858
|
+
if (guard) return { error: guard };
|
|
859
|
+
if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
|
|
860
|
+
return { error: `unknown deliverable: ${name}` };
|
|
861
|
+
}
|
|
862
|
+
if (pattern.length > SEARCH_MAX_PATTERN) {
|
|
863
|
+
return { error: `pattern too long (max ${SEARCH_MAX_PATTERN} chars)` };
|
|
864
|
+
}
|
|
865
|
+
let re;
|
|
866
|
+
try {
|
|
867
|
+
re = new RegExp(pattern, "gi");
|
|
868
|
+
} catch (error) {
|
|
869
|
+
return { error: `invalid regex: ${error instanceof Error ? error.message : String(error)}` };
|
|
870
|
+
}
|
|
871
|
+
const content = renderValue(evidence.deliverables[name]).slice(0, SEARCH_SCAN_LIMIT);
|
|
872
|
+
const hits = [];
|
|
873
|
+
let m;
|
|
874
|
+
let execs = 0;
|
|
875
|
+
while ((m = re.exec(content)) && hits.length < SEARCH_HITS) {
|
|
876
|
+
if (++execs > SEARCH_MAX_EXEC) break;
|
|
877
|
+
const start = Math.max(0, m.index - SEARCH_CONTEXT);
|
|
878
|
+
hits.push({ at_byte: m.index, context: content.slice(start, m.index + m[0].length + SEARCH_CONTEXT) });
|
|
879
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
880
|
+
}
|
|
881
|
+
const served = JSON.stringify({ total_bytes: content.length, match_count: hits.length, matches: hits });
|
|
882
|
+
const overflow = accountRead(
|
|
883
|
+
"search_deliverable",
|
|
884
|
+
JSON.stringify({ name, pattern }),
|
|
885
|
+
served,
|
|
886
|
+
() => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
|
|
887
|
+
);
|
|
888
|
+
if (overflow) return { error: overflow };
|
|
889
|
+
return { total_bytes: content.length, match_count: hits.length, matches: hits };
|
|
890
|
+
});
|
|
668
891
|
}
|
|
669
892
|
}),
|
|
670
|
-
...evidence.history ? {
|
|
893
|
+
...includeHistory && evidence.history ? {
|
|
671
894
|
list_runs: tool({
|
|
672
895
|
description: "List runs of this task \u2014 id, status, pass/fail, model, check counts. The run under judgment is flagged; use get_run for a prior run's full check report.",
|
|
673
896
|
inputSchema: z.object({}),
|
|
674
897
|
execute: async () => {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
898
|
+
return span("list_runs", {}, async () => {
|
|
899
|
+
const guard = budgetGuard();
|
|
900
|
+
if (guard) return { error: guard };
|
|
901
|
+
return evidence.history.runs;
|
|
902
|
+
});
|
|
678
903
|
}
|
|
679
904
|
}),
|
|
680
905
|
get_run: tool({
|
|
@@ -682,18 +907,20 @@ function buildEvidenceTools(args) {
|
|
|
682
907
|
inputSchema: z.object({ run_id: z.string() }),
|
|
683
908
|
execute: async (input) => {
|
|
684
909
|
const { run_id } = input;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
910
|
+
return span("get_run", input, async () => {
|
|
911
|
+
const guard = budgetGuard();
|
|
912
|
+
if (guard) return { error: guard };
|
|
913
|
+
const detail = await evidence.history.getRun(run_id);
|
|
914
|
+
const served = JSON.stringify(detail);
|
|
915
|
+
const overflow = accountRead(
|
|
916
|
+
"get_run",
|
|
917
|
+
JSON.stringify({ run_id }),
|
|
918
|
+
served,
|
|
919
|
+
() => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
|
|
920
|
+
);
|
|
921
|
+
if (overflow) return { error: overflow };
|
|
922
|
+
return detail;
|
|
923
|
+
});
|
|
697
924
|
}
|
|
698
925
|
})
|
|
699
926
|
} : {},
|
|
@@ -701,13 +928,15 @@ function buildEvidenceTools(args) {
|
|
|
701
928
|
description: "The task this run executed: id, description, deliverable names.",
|
|
702
929
|
inputSchema: z.object({}),
|
|
703
930
|
execute: async () => {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
931
|
+
return span("get_task_definition", {}, async () => {
|
|
932
|
+
const guard = budgetGuard();
|
|
933
|
+
if (guard) return { error: guard };
|
|
934
|
+
return {
|
|
935
|
+
task_id: scope?.taskId,
|
|
936
|
+
description: scope?.taskDescription ?? "(unavailable)",
|
|
937
|
+
deliverables: Object.keys(evidence.deliverables)
|
|
938
|
+
};
|
|
939
|
+
});
|
|
711
940
|
}
|
|
712
941
|
})
|
|
713
942
|
};
|
|
@@ -717,21 +946,23 @@ function buildEvidenceTools(args) {
|
|
|
717
946
|
inputSchema: z.object({ query: z.string().describe("substring filter on tool names; empty = all") }),
|
|
718
947
|
execute: async (input) => {
|
|
719
948
|
const { query } = input;
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
949
|
+
return span("get_trace", input, async () => {
|
|
950
|
+
const guard = budgetGuard();
|
|
951
|
+
if (guard) return { error: guard };
|
|
952
|
+
const view = evidence.view;
|
|
953
|
+
if (!view) {
|
|
954
|
+
return {
|
|
955
|
+
status: "unsupported",
|
|
956
|
+
detail: "no trace projection exists for this run \u2014 judge on deliverables instead"
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
if (view.requireCapability("tools") !== "available") {
|
|
960
|
+
return { status: "unsupported", detail: "tool-call evidence is unavailable in this trace projection" };
|
|
961
|
+
}
|
|
962
|
+
const calls = view.toolCalls.filter((c) => query ? c.name.includes(query) : true).slice(0, 30).map((c) => ({ name: c.name, input: renderValue(c.input).slice(0, 200) }));
|
|
963
|
+
const reply = view.requireCapability("messages") === "available" && view.reply ? view.reply.slice(0, 1500) : void 0;
|
|
964
|
+
return { turns: view.turnCount, tool_calls: calls, ...reply !== void 0 ? { final_reply: reply } : {} };
|
|
965
|
+
});
|
|
735
966
|
}
|
|
736
967
|
});
|
|
737
968
|
}
|
|
@@ -858,7 +1089,9 @@ async function runAgentSession(spec) {
|
|
|
858
1089
|
budget,
|
|
859
1090
|
ledger,
|
|
860
1091
|
stepIndexOf: () => liveSteps.length,
|
|
861
|
-
includeTrace: spec.tools?.trace !== false
|
|
1092
|
+
includeTrace: spec.tools?.trace !== false,
|
|
1093
|
+
includeHistory: spec.tools?.history !== false,
|
|
1094
|
+
tracer: spec.tracer
|
|
862
1095
|
});
|
|
863
1096
|
const system = buildBriefing(spec.scope, spec.evidence, budget);
|
|
864
1097
|
const exhibitsBlock = spec.exhibits?.length ? "\n\nValues submitted for judgment:\n" + spec.exhibits.map((v, i) => ` [${i + 1}] ${renderValue(v).slice(0, RECORD_RESULT_LIMIT)}`).join("\n") : "";
|
|
@@ -918,7 +1151,7 @@ async function runAgentSession(spec) {
|
|
|
918
1151
|
latency_ms: Date.now() - started
|
|
919
1152
|
};
|
|
920
1153
|
}
|
|
921
|
-
function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
|
|
1154
|
+
function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer) {
|
|
922
1155
|
return async (instruction, opts) => {
|
|
923
1156
|
const label = opts?.label ?? "agent";
|
|
924
1157
|
const location = rec.captureLocation();
|
|
@@ -934,18 +1167,54 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
|
|
|
934
1167
|
);
|
|
935
1168
|
return;
|
|
936
1169
|
}
|
|
1170
|
+
const run = () => runAgentSession({
|
|
1171
|
+
instruction,
|
|
1172
|
+
model: effective.model,
|
|
1173
|
+
baseURL: effective.baseURL,
|
|
1174
|
+
apiKey: effective.apiKey,
|
|
1175
|
+
evidence: evidence ?? { deliverables: {} },
|
|
1176
|
+
scope: judgeScope,
|
|
1177
|
+
exhibits,
|
|
1178
|
+
tools: opts?.tools,
|
|
1179
|
+
budget: opts?.budget,
|
|
1180
|
+
tracer: judgeTracer
|
|
1181
|
+
});
|
|
937
1182
|
try {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1183
|
+
let sessionSpanId;
|
|
1184
|
+
const result = await (judgeTracer ? judgeTracer.step(
|
|
1185
|
+
{
|
|
1186
|
+
step_name: `t.agent:${judgeScope?.checkName ?? label}`,
|
|
1187
|
+
observation_type: "AGENT",
|
|
1188
|
+
// The span carries the model + tokens so the backend prices the
|
|
1189
|
+
// judge session and trace rollups count its spend (issue #288).
|
|
1190
|
+
model: effective.model,
|
|
1191
|
+
input: { model: effective.model, instruction },
|
|
1192
|
+
usage: (r) => {
|
|
1193
|
+
const usage = r?.usage;
|
|
1194
|
+
return usage ? {
|
|
1195
|
+
prompt_tokens: usage.input_tokens,
|
|
1196
|
+
completion_tokens: usage.output_tokens
|
|
1197
|
+
} : void 0;
|
|
1198
|
+
},
|
|
1199
|
+
summarize: (r) => {
|
|
1200
|
+
const res = r;
|
|
1201
|
+
const reasoning = res?.verdict?.reasoning?.slice(0, 2e3) ?? `session ended without a verdict (${res?.outcome ?? "unknown"})`;
|
|
1202
|
+
return {
|
|
1203
|
+
text: reasoning,
|
|
1204
|
+
// reasoning-first, matching the judge response
|
|
1205
|
+
// contract's default order (#163).
|
|
1206
|
+
verdict: {
|
|
1207
|
+
reasoning,
|
|
1208
|
+
pass: res?.verdict?.pass ?? null
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
async (spanId) => {
|
|
1214
|
+
sessionSpanId = spanId;
|
|
1215
|
+
return run();
|
|
1216
|
+
}
|
|
1217
|
+
) : run());
|
|
949
1218
|
const judge = {
|
|
950
1219
|
model: effective.model,
|
|
951
1220
|
temperature: 0,
|
|
@@ -956,7 +1225,8 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
|
|
|
956
1225
|
},
|
|
957
1226
|
...result.cost !== void 0 ? { cost: result.cost } : {},
|
|
958
1227
|
latency_ms: result.latency_ms,
|
|
959
|
-
session: result.session
|
|
1228
|
+
session: result.session,
|
|
1229
|
+
...isTraceableSpanId(sessionSpanId) ? { span_id: sessionSpanId } : {}
|
|
960
1230
|
};
|
|
961
1231
|
const detail = result.outcome === "error" ? `agentic judge session errored before verdicting: ${result.error ?? "unknown error"}` : `agentic judge session ended without a verdict (budget exhausted after ${result.session.usage?.steps ?? "?"} steps; last tool: ${result.session.steps?.at(-1)?.tool_calls?.map((c) => c.name).join(", ") ?? "none"})`;
|
|
962
1232
|
rec.record(label, result.verdict?.pass ?? false, result.verdict?.reasoning ?? detail, {
|
|
@@ -1016,7 +1286,14 @@ function matchName(name, m) {
|
|
|
1016
1286
|
if (m instanceof RegExp) return m.test(name);
|
|
1017
1287
|
return m(name);
|
|
1018
1288
|
}
|
|
1019
|
-
function
|
|
1289
|
+
function trackPending(rec, method) {
|
|
1290
|
+
return ((...args) => {
|
|
1291
|
+
const promise = method(...args);
|
|
1292
|
+
rec.track(promise);
|
|
1293
|
+
return promise;
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
1020
1297
|
return async (values, instruction, opts) => {
|
|
1021
1298
|
const label = opts?.label ?? "judge";
|
|
1022
1299
|
const location = rec.captureLocation();
|
|
@@ -1032,16 +1309,49 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
|
|
|
1032
1309
|
return;
|
|
1033
1310
|
}
|
|
1034
1311
|
const context2 = judgeScopeToContext(judgeScope);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1312
|
+
const call = () => callJudge({
|
|
1313
|
+
values: valueArray,
|
|
1314
|
+
instruction,
|
|
1315
|
+
model: effective.model,
|
|
1316
|
+
baseURL: effective.baseURL,
|
|
1317
|
+
apiKey: effective.apiKey,
|
|
1318
|
+
prompt: effective.prompt,
|
|
1319
|
+
...context2 ? { context: context2 } : {}
|
|
1320
|
+
});
|
|
1321
|
+
let judgeSpanId;
|
|
1322
|
+
const traced = judgeTracer ? judgeTracer.step(
|
|
1323
|
+
{
|
|
1324
|
+
step_name: `judge:${judgeScope?.checkName ?? label}`,
|
|
1325
|
+
// A single-shot judge call is an LLM generation — project it
|
|
1326
|
+
// with the same observation semantics as the main agent's.
|
|
1327
|
+
observation_type: "GENERATION",
|
|
1328
|
+
// The span carries the model + tokens so the backend prices the
|
|
1329
|
+
// judge call and trace rollups count its spend (issue #288).
|
|
1039
1330
|
model: effective.model,
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1331
|
+
input: { model: effective.model, instruction },
|
|
1332
|
+
summarize: (r) => {
|
|
1333
|
+
const res = r;
|
|
1334
|
+
return {
|
|
1335
|
+
text: res?.reasoning?.slice(0, 2e3) ?? "",
|
|
1336
|
+
verdict: {
|
|
1337
|
+
reasoning: res?.reasoning?.slice(0, 2e3) ?? null,
|
|
1338
|
+
pass: res?.pass ?? null
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
},
|
|
1342
|
+
usage: (r) => {
|
|
1343
|
+
const tokens = r?.judge?.tokens;
|
|
1344
|
+
return tokens ? { prompt_tokens: tokens.input, completion_tokens: tokens.output } : void 0;
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
async (spanId) => {
|
|
1348
|
+
judgeSpanId = spanId;
|
|
1349
|
+
return call();
|
|
1350
|
+
}
|
|
1351
|
+
) : call();
|
|
1352
|
+
try {
|
|
1353
|
+
const { pass, reasoning, judge, unavailable } = await traced;
|
|
1354
|
+
if (isTraceableSpanId(judgeSpanId)) judge.span_id = judgeSpanId;
|
|
1045
1355
|
rec.record(label, pass, reasoning, {
|
|
1046
1356
|
evaluator_type: "llm",
|
|
1047
1357
|
judge,
|
|
@@ -1050,7 +1360,8 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
|
|
|
1050
1360
|
// dashboard can render it with the right viewer — Markdown for
|
|
1051
1361
|
// prose, a JSON tree for structured objects.
|
|
1052
1362
|
received: valueArray.length === 1 ? valueArray[0] : valueArray,
|
|
1053
|
-
location
|
|
1363
|
+
location,
|
|
1364
|
+
...unavailable ? { outcome: "error" } : {}
|
|
1054
1365
|
});
|
|
1055
1366
|
} catch (error) {
|
|
1056
1367
|
rec.record(
|
|
@@ -1061,7 +1372,8 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
|
|
|
1061
1372
|
evaluator_type: "llm",
|
|
1062
1373
|
expected: instruction,
|
|
1063
1374
|
received: valueArray.length === 1 ? valueArray[0] : valueArray,
|
|
1064
|
-
location
|
|
1375
|
+
location,
|
|
1376
|
+
outcome: "error"
|
|
1065
1377
|
}
|
|
1066
1378
|
);
|
|
1067
1379
|
}
|
|
@@ -1085,7 +1397,7 @@ var CAPABILITY_LABELS = {
|
|
|
1085
1397
|
skills: "skill",
|
|
1086
1398
|
subagents: "subagent"
|
|
1087
1399
|
};
|
|
1088
|
-
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence) {
|
|
1400
|
+
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer) {
|
|
1089
1401
|
const unsupported = (id, capability) => {
|
|
1090
1402
|
const label = CAPABILITY_LABELS[capability];
|
|
1091
1403
|
rec.record(id, false, `${label} evidence is unavailable in this trace projection`, {
|
|
@@ -1271,8 +1583,8 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
1271
1583
|
},
|
|
1272
1584
|
// judge does not consult trace capabilities; the scope carries the
|
|
1273
1585
|
// task/check frame for prompt builders (#161).
|
|
1274
|
-
judge: createJudgeMethod(rec, judgeConfig, judgeScope),
|
|
1275
|
-
agent: createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view })
|
|
1586
|
+
judge: trackPending(rec, createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer)),
|
|
1587
|
+
agent: trackPending(rec, createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view }, judgeTracer))
|
|
1276
1588
|
};
|
|
1277
1589
|
}
|
|
1278
1590
|
function matchesTraceToolCall(call, name, opts) {
|
|
@@ -1293,16 +1605,9 @@ function matchesTraceToolCall(call, name, opts) {
|
|
|
1293
1605
|
var Recorder = class {
|
|
1294
1606
|
constructor(locate) {
|
|
1295
1607
|
this.records = [];
|
|
1608
|
+
this.pending = [];
|
|
1296
1609
|
this.locate = locate;
|
|
1297
1610
|
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Record an assertion.
|
|
1300
|
-
*
|
|
1301
|
-
* - ``extra.location`` overrides auto-capture (used by the runner for thrown
|
|
1302
|
-
* errors, whose relevant stack is the error's own, not the call site).
|
|
1303
|
-
* - ``extra.expected`` / ``extra.received`` carry the structured values for
|
|
1304
|
-
* testing-framework-style display.
|
|
1305
|
-
*/
|
|
1306
1611
|
/**
|
|
1307
1612
|
* Capture the call site location synchronously. Use this when a record will
|
|
1308
1613
|
* happen AFTER an `await` (e.g. `t.judge`): once an async function resumes,
|
|
@@ -1314,6 +1619,28 @@ var Recorder = class {
|
|
|
1314
1619
|
captureLocation() {
|
|
1315
1620
|
return this.locate ? this.locate(new Error().stack ?? "") : void 0;
|
|
1316
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Register an async evaluation (``t.judge`` / ``t.agent``) started by this
|
|
1624
|
+
* check. If the check returns without awaiting it, the runner still waits
|
|
1625
|
+
* for its record via ``settlePending`` — a dropped await must not let the
|
|
1626
|
+
* check vacuously pass ("no assertions recorded") while the verdict is
|
|
1627
|
+
* still in flight.
|
|
1628
|
+
*/
|
|
1629
|
+
track(promise) {
|
|
1630
|
+
this.pending.push(promise);
|
|
1631
|
+
}
|
|
1632
|
+
/** Wait until every tracked evaluation has settled (and recorded). */
|
|
1633
|
+
async settlePending() {
|
|
1634
|
+
await Promise.allSettled(this.pending);
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Record an assertion.
|
|
1638
|
+
*
|
|
1639
|
+
* - ``extra.location`` overrides auto-capture (used by the runner for thrown
|
|
1640
|
+
* errors, whose relevant stack is the error's own, not the call site).
|
|
1641
|
+
* - ``extra.expected`` / ``extra.received`` carry the structured values for
|
|
1642
|
+
* testing-framework-style display.
|
|
1643
|
+
*/
|
|
1317
1644
|
record(id, pass, reasoning, extra) {
|
|
1318
1645
|
const location = extra?.location ?? (this.locate ? this.locate(new Error().stack ?? "") : void 0);
|
|
1319
1646
|
this.records.push({
|
|
@@ -1425,8 +1752,10 @@ async function runTraceChecks(args) {
|
|
|
1425
1752
|
{
|
|
1426
1753
|
deliverables: args.deliverables,
|
|
1427
1754
|
view,
|
|
1428
|
-
...args.historyPlane ? { history: args.historyPlane } : {}
|
|
1429
|
-
|
|
1755
|
+
...args.historyPlane ? { history: args.historyPlane } : {},
|
|
1756
|
+
...args.historyUnavailableReason ? { historyUnavailable: args.historyUnavailableReason } : {}
|
|
1757
|
+
},
|
|
1758
|
+
args.judgeTracer
|
|
1430
1759
|
);
|
|
1431
1760
|
let thrownLocation;
|
|
1432
1761
|
try {
|
|
@@ -1441,8 +1770,10 @@ async function runTraceChecks(args) {
|
|
|
1441
1770
|
location: thrownLocation
|
|
1442
1771
|
});
|
|
1443
1772
|
}
|
|
1773
|
+
await rec.settlePending();
|
|
1444
1774
|
const failed = rec.all.filter((r) => !r.pass);
|
|
1445
1775
|
const pass = failed.length === 0;
|
|
1776
|
+
const outcome = rollUpCheckOutcome(failed);
|
|
1446
1777
|
const reasoning = failed.length > 0 ? failed.map((r) => r.reasoning || r.id).join("; ") : rec.all.length > 0 ? "passed" : "no assertions recorded";
|
|
1447
1778
|
const location = failed.find((r) => r.location)?.location;
|
|
1448
1779
|
const judge = rec.all.find((r) => r.judge)?.judge;
|
|
@@ -1451,6 +1782,7 @@ async function runTraceChecks(args) {
|
|
|
1451
1782
|
pass,
|
|
1452
1783
|
reasoning,
|
|
1453
1784
|
evaluator_type: "code",
|
|
1785
|
+
...outcome ? { outcome } : {},
|
|
1454
1786
|
...judge ? { judge } : {},
|
|
1455
1787
|
...location ? { location } : {},
|
|
1456
1788
|
...args.displayFile ? { source_file: args.displayFile } : {},
|
|
@@ -1465,6 +1797,14 @@ async function runTraceChecks(args) {
|
|
|
1465
1797
|
);
|
|
1466
1798
|
return results;
|
|
1467
1799
|
}
|
|
1800
|
+
function rollUpCheckOutcome(failed) {
|
|
1801
|
+
if (failed.length === 0) return void 0;
|
|
1802
|
+
const verdictless = failed.filter(
|
|
1803
|
+
(r) => r.outcome === "error" || r.outcome === "unsupported"
|
|
1804
|
+
);
|
|
1805
|
+
if (verdictless.length < failed.length) return void 0;
|
|
1806
|
+
return verdictless.some((r) => r.outcome === "error") ? "error" : "unsupported";
|
|
1807
|
+
}
|
|
1468
1808
|
async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
|
|
1469
1809
|
if (!checksPath) return [];
|
|
1470
1810
|
resetFlowChecks();
|
|
@@ -1477,6 +1817,7 @@ async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
|
|
|
1477
1817
|
task: args.task,
|
|
1478
1818
|
judgeConfig: args.judgeConfig,
|
|
1479
1819
|
...args.historyPlane ? { historyPlane: args.historyPlane } : {},
|
|
1820
|
+
...args.judgeTracer ? { judgeTracer: args.judgeTracer } : {},
|
|
1480
1821
|
moduleUrl,
|
|
1481
1822
|
displayFile: basename(checksPath)
|
|
1482
1823
|
});
|
|
@@ -2528,7 +2869,29 @@ function createProjectionTee(real) {
|
|
|
2528
2869
|
}
|
|
2529
2870
|
},
|
|
2530
2871
|
async traceTool(name, params, fn) {
|
|
2531
|
-
|
|
2872
|
+
const obsSpanId = recordStart({
|
|
2873
|
+
step_name: `tool ${name}`,
|
|
2874
|
+
observation_type: "TOOL",
|
|
2875
|
+
tool_name: name,
|
|
2876
|
+
input: params
|
|
2877
|
+
});
|
|
2878
|
+
const start = monotonicNowMs();
|
|
2879
|
+
try {
|
|
2880
|
+
const result = await real.traceTool(name, params, fn);
|
|
2881
|
+
complete(obsSpanId, {
|
|
2882
|
+
latency_ms: round3(monotonicNowMs() - start),
|
|
2883
|
+
output: result
|
|
2884
|
+
});
|
|
2885
|
+
return result;
|
|
2886
|
+
} catch (error) {
|
|
2887
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2888
|
+
complete(obsSpanId, {
|
|
2889
|
+
latency_ms: round3(monotonicNowMs() - start),
|
|
2890
|
+
status_message: message,
|
|
2891
|
+
level: "ERROR"
|
|
2892
|
+
});
|
|
2893
|
+
throw error;
|
|
2894
|
+
}
|
|
2532
2895
|
},
|
|
2533
2896
|
async traceRetriever(query, fn) {
|
|
2534
2897
|
return real.traceRetriever(query, fn);
|
|
@@ -2719,48 +3082,6 @@ function aggregateResult(checksResults) {
|
|
|
2719
3082
|
return { checks: checksResults, pass };
|
|
2720
3083
|
}
|
|
2721
3084
|
|
|
2722
|
-
// src/agent-task/tracing.ts
|
|
2723
|
-
function createNoopAgentTaskTraceContext() {
|
|
2724
|
-
const noopFn = async (fn) => fn();
|
|
2725
|
-
return {
|
|
2726
|
-
runId: "agent-task-untraced",
|
|
2727
|
-
rootSpanId: "agent-task-untraced-root",
|
|
2728
|
-
async step(options, fn) {
|
|
2729
|
-
return fn(options.id ?? "agent-task-untraced-step");
|
|
2730
|
-
},
|
|
2731
|
-
recordEvent(options) {
|
|
2732
|
-
return options.id ?? "agent-task-untraced-event";
|
|
2733
|
-
},
|
|
2734
|
-
endRoot() {
|
|
2735
|
-
},
|
|
2736
|
-
traceTool(_name, _params, fn) {
|
|
2737
|
-
return noopFn(fn);
|
|
2738
|
-
},
|
|
2739
|
-
traceRetriever(_query, fn) {
|
|
2740
|
-
return noopFn(fn);
|
|
2741
|
-
},
|
|
2742
|
-
traceChain(_name, fn) {
|
|
2743
|
-
return noopFn(fn);
|
|
2744
|
-
},
|
|
2745
|
-
traceAgent(_name, fn) {
|
|
2746
|
-
return noopFn(fn);
|
|
2747
|
-
},
|
|
2748
|
-
traceGuardrail(_name, fn) {
|
|
2749
|
-
return noopFn(fn);
|
|
2750
|
-
},
|
|
2751
|
-
traceEmbedding(_model, _input, fn) {
|
|
2752
|
-
return noopFn(fn);
|
|
2753
|
-
},
|
|
2754
|
-
async score(_params) {
|
|
2755
|
-
},
|
|
2756
|
-
createSpan(_options) {
|
|
2757
|
-
return "agent-task-untraced-span";
|
|
2758
|
-
},
|
|
2759
|
-
endSpan(_spanId, _params) {
|
|
2760
|
-
}
|
|
2761
|
-
};
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
3085
|
// src/agent-task/run/runTask.ts
|
|
2765
3086
|
var AgentTaskRunError = class extends Error {
|
|
2766
3087
|
constructor(message, options) {
|
|
@@ -2787,13 +3108,15 @@ async function runTask(taskDir, options) {
|
|
|
2787
3108
|
void 0
|
|
2788
3109
|
);
|
|
2789
3110
|
}
|
|
2790
|
-
|
|
3111
|
+
return trace2.client.traceRun(
|
|
2791
3112
|
buildTraceRunOptions(loaded, trace2),
|
|
2792
|
-
async (traceContext) =>
|
|
3113
|
+
async (traceContext) => {
|
|
3114
|
+
const phase1 = await captureExecution(loaded, options, traceContext);
|
|
3115
|
+
const canonical = await readCanonicalSnapshot(trace2);
|
|
3116
|
+
if (canonical) phase1.snapshot = canonical;
|
|
3117
|
+
return evaluate(loaded, options, phase1, traceContext);
|
|
3118
|
+
}
|
|
2793
3119
|
);
|
|
2794
|
-
const canonical = await readCanonicalSnapshot(trace2);
|
|
2795
|
-
if (canonical) phase1.snapshot = canonical;
|
|
2796
|
-
return evaluate(loaded, options, phase1);
|
|
2797
3120
|
}
|
|
2798
3121
|
async function readCanonicalSnapshot(trace2) {
|
|
2799
3122
|
if (!trace2.taskRunId) return null;
|
|
@@ -2991,6 +3314,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
2991
3314
|
files,
|
|
2992
3315
|
task: task2,
|
|
2993
3316
|
...judgeConfig ? { judgeConfig } : {},
|
|
3317
|
+
judgeTracer: trace2,
|
|
2994
3318
|
...historyPlane ? { historyPlane } : {}
|
|
2995
3319
|
},
|
|
2996
3320
|
validationResults.brokenDeliverables
|
|
@@ -3005,6 +3329,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
3005
3329
|
files,
|
|
3006
3330
|
task: task2,
|
|
3007
3331
|
...judgeConfig ? { judgeConfig } : {},
|
|
3332
|
+
judgeTracer: trace2,
|
|
3008
3333
|
...historyPlane ? { historyPlane } : {},
|
|
3009
3334
|
moduleUrl,
|
|
3010
3335
|
displayFile: evalFileName
|
|
@@ -3033,7 +3358,7 @@ async function captureExecution(loaded, options, rawTrace) {
|
|
|
3033
3358
|
snapshot: outcome.getSnapshot()
|
|
3034
3359
|
}));
|
|
3035
3360
|
}
|
|
3036
|
-
async function evaluate(loaded, options, phase1) {
|
|
3361
|
+
async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
3037
3362
|
const {
|
|
3038
3363
|
task: task2,
|
|
3039
3364
|
adapter,
|
|
@@ -3051,12 +3376,13 @@ async function evaluate(loaded, options, phase1) {
|
|
|
3051
3376
|
);
|
|
3052
3377
|
const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
|
|
3053
3378
|
const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
|
|
3054
|
-
const
|
|
3379
|
+
const runChecks = async () => inlineChecks ? runTraceChecks({
|
|
3055
3380
|
snapshot: phase1.snapshot,
|
|
3056
3381
|
deliverables,
|
|
3057
3382
|
files,
|
|
3058
3383
|
task: task2,
|
|
3059
3384
|
...judgeConfig ? { judgeConfig } : {},
|
|
3385
|
+
...judgeTracer ? { judgeTracer } : {},
|
|
3060
3386
|
...historyPlane ? { historyPlane } : {},
|
|
3061
3387
|
moduleUrl,
|
|
3062
3388
|
displayFile: evalFileName
|
|
@@ -3068,10 +3394,22 @@ async function evaluate(loaded, options, phase1) {
|
|
|
3068
3394
|
files,
|
|
3069
3395
|
task: task2,
|
|
3070
3396
|
...judgeConfig ? { judgeConfig } : {},
|
|
3397
|
+
...judgeTracer ? { judgeTracer } : {},
|
|
3071
3398
|
...historyPlane ? { historyPlane } : {}
|
|
3072
3399
|
},
|
|
3073
3400
|
validationResults.brokenDeliverables
|
|
3074
|
-
)
|
|
3401
|
+
);
|
|
3402
|
+
const hasChecksModule = inlineChecks || checksPath !== null;
|
|
3403
|
+
const checksResults = await (judgeTracer && hasChecksModule ? judgeTracer.step(
|
|
3404
|
+
{
|
|
3405
|
+
step_name: "checks.run",
|
|
3406
|
+
observation_type: "CHAIN",
|
|
3407
|
+
input: { sourceFile: inlineChecks ? evalFileName : checksPath },
|
|
3408
|
+
metadata: { sourceFile: inlineChecks ? evalFileName : checksPath },
|
|
3409
|
+
summarize: (result2) => summarizeEvaluationResults(result2)
|
|
3410
|
+
},
|
|
3411
|
+
runChecks
|
|
3412
|
+
) : runChecks());
|
|
3075
3413
|
const result = aggregateResult(checksResults);
|
|
3076
3414
|
return {
|
|
3077
3415
|
task: task2,
|
|
@@ -3169,17 +3507,29 @@ async function cleanupAdapter(loaded, trace2, state, session) {
|
|
|
3169
3507
|
}
|
|
3170
3508
|
function summarizeEvaluationResults(results) {
|
|
3171
3509
|
const passCount = results.filter((r) => r.pass).length;
|
|
3510
|
+
const noVerdictCount = results.filter(
|
|
3511
|
+
(r) => !r.pass && (r.outcome === "error" || r.outcome === "unsupported")
|
|
3512
|
+
).length;
|
|
3513
|
+
const failCount = results.length - passCount - noVerdictCount;
|
|
3514
|
+
const suffix = noVerdictCount > 0 ? ` (${noVerdictCount} no-verdict)` : "";
|
|
3172
3515
|
return {
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3516
|
+
text: `${passCount}/${results.length} checks passed${suffix}`,
|
|
3517
|
+
verdict: {
|
|
3518
|
+
total: results.length,
|
|
3519
|
+
passCount,
|
|
3520
|
+
failCount,
|
|
3521
|
+
noVerdictCount,
|
|
3522
|
+
results: results.map((r) => ({
|
|
3523
|
+
id: r.id,
|
|
3524
|
+
pass: r.pass,
|
|
3525
|
+
reasoning: r.reasoning?.slice(0, 2e3),
|
|
3526
|
+
evaluator_type: r.evaluator_type,
|
|
3527
|
+
judge_model: r.judge?.model,
|
|
3528
|
+
...r.outcome ? { outcome: r.outcome } : {},
|
|
3529
|
+
...r.group_id ? { group_id: r.group_id } : {},
|
|
3530
|
+
...r.group_name ? { group_name: r.group_name } : {}
|
|
3531
|
+
}))
|
|
3532
|
+
}
|
|
3183
3533
|
};
|
|
3184
3534
|
}
|
|
3185
3535
|
function buildTraceRunOptions(loaded, tracing) {
|
|
@@ -3307,6 +3657,7 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
|
3307
3657
|
);
|
|
3308
3658
|
const proxied = proxyBrokenDeliverables(deliverables, validation.brokenDeliverables);
|
|
3309
3659
|
const passCounts = /* @__PURE__ */ new Map();
|
|
3660
|
+
const errorCounts = /* @__PURE__ */ new Map();
|
|
3310
3661
|
let primary = [];
|
|
3311
3662
|
const judgeConfig = resolveJudgeConfig(options.judge, loaded.task.judge);
|
|
3312
3663
|
for (let sample = 0; sample < samples; sample++) {
|
|
@@ -3315,12 +3666,16 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
|
3315
3666
|
if (sample === 0) primary = results;
|
|
3316
3667
|
for (const result of results) {
|
|
3317
3668
|
passCounts.set(result.id, (passCounts.get(result.id) ?? 0) + (result.pass ? 1 : 0));
|
|
3669
|
+
if (!result.pass && result.outcome === "error") {
|
|
3670
|
+
errorCounts.set(result.id, (errorCounts.get(result.id) ?? 0) + 1);
|
|
3671
|
+
}
|
|
3318
3672
|
}
|
|
3319
3673
|
}
|
|
3320
3674
|
const stability = primary.map((result) => ({
|
|
3321
3675
|
check_id: result.id,
|
|
3322
3676
|
passes: passCounts.get(result.id) ?? 0,
|
|
3323
|
-
samples
|
|
3677
|
+
samples,
|
|
3678
|
+
...errorCounts.get(result.id) ? { errored: errorCounts.get(result.id) } : {}
|
|
3324
3679
|
}));
|
|
3325
3680
|
const aggregate = aggregateResult(primary);
|
|
3326
3681
|
return {
|
|
@@ -3544,6 +3899,7 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3544
3899
|
activeSpans.set(spanId, {
|
|
3545
3900
|
span,
|
|
3546
3901
|
startedAt: monotonicNowMs2(),
|
|
3902
|
+
startWallMs: Date.now(),
|
|
3547
3903
|
model: params.model || "unknown",
|
|
3548
3904
|
observationType: params.observation_type
|
|
3549
3905
|
});
|
|
@@ -3561,6 +3917,13 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3561
3917
|
const payload = params.output && typeof params.output === "object" ? params.output : { value: params.output };
|
|
3562
3918
|
active.span.setAttribute("gen_ai.tool.call.result", JSON.stringify(payload));
|
|
3563
3919
|
} else if (params.output.text) {
|
|
3920
|
+
active.span.setAttribute("gen_ai.response.text", String(params.output.text));
|
|
3921
|
+
if (params.output.verdict !== void 0) {
|
|
3922
|
+
active.span.setAttribute(
|
|
3923
|
+
"gen_ai.tool.call.result",
|
|
3924
|
+
JSON.stringify(params.output.verdict)
|
|
3925
|
+
);
|
|
3926
|
+
}
|
|
3564
3927
|
active.span.setAttribute("gen_ai.output.messages", JSON.stringify([{
|
|
3565
3928
|
role: "assistant",
|
|
3566
3929
|
parts: [{ type: "text", content: String(params.output.text) }]
|
|
@@ -3585,6 +3948,12 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3585
3948
|
if (params.completion_tokens !== void 0) {
|
|
3586
3949
|
active.span.setAttribute("gen_ai.usage.output_tokens", params.completion_tokens);
|
|
3587
3950
|
}
|
|
3951
|
+
if (params.reasoning_tokens !== void 0) {
|
|
3952
|
+
active.span.setAttribute(
|
|
3953
|
+
"gen_ai.usage.reasoning.output_tokens",
|
|
3954
|
+
params.reasoning_tokens
|
|
3955
|
+
);
|
|
3956
|
+
}
|
|
3588
3957
|
if (params.status_message) {
|
|
3589
3958
|
active.span.setAttribute("apo.status_message", params.status_message);
|
|
3590
3959
|
}
|
|
@@ -3594,7 +3963,7 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3594
3963
|
if (params.metadata) {
|
|
3595
3964
|
active.span.setAttribute("apo.metadata", JSON.stringify(params.metadata));
|
|
3596
3965
|
}
|
|
3597
|
-
active.span.end();
|
|
3966
|
+
active.span.end(new Date(active.startWallMs + latency));
|
|
3598
3967
|
activeSpans.delete(params.id);
|
|
3599
3968
|
}
|
|
3600
3969
|
return {
|
|
@@ -3628,6 +3997,7 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3628
3997
|
activeSpans.set(rootSpanId, {
|
|
3629
3998
|
span: rootSpan,
|
|
3630
3999
|
startedAt: monotonicNowMs2(),
|
|
4000
|
+
startWallMs: Date.now(),
|
|
3631
4001
|
model: "trace"
|
|
3632
4002
|
});
|
|
3633
4003
|
let callCount = 1;
|
|
@@ -3653,7 +4023,8 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
3653
4023
|
id: spanId,
|
|
3654
4024
|
latency_ms: round32(monotonicNowMs2() - startedAt),
|
|
3655
4025
|
output: options.summarize?.(result),
|
|
3656
|
-
metadata: options.metadata
|
|
4026
|
+
metadata: options.metadata,
|
|
4027
|
+
...options.usage?.(result)
|
|
3657
4028
|
});
|
|
3658
4029
|
callCount += 1;
|
|
3659
4030
|
return result;
|