@apo-ai/sdk 0.16.0 → 0.17.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/anthropic.d.ts +1 -1
- package/dist/agent-task/integrations/anthropic.js +2 -2
- package/dist/agent-task/integrations/openai.d.ts +1 -1
- package/dist/agent-task/integrations/openai.js +2 -2
- package/dist/agent-task/public.d.ts +368 -13
- package/dist/agent-task/public.js +848 -74
- package/dist/agent-task/public.js.map +1 -1
- package/dist/{chunk-4BQSCD6T.js → chunk-6U6V7VKZ.js} +4 -3
- package/dist/{chunk-4BQSCD6T.js.map → chunk-6U6V7VKZ.js.map} +1 -1
- package/dist/{chunk-P24ECOI3.js → chunk-CSOY6OSP.js} +78 -11
- package/dist/chunk-CSOY6OSP.js.map +1 -0
- package/dist/{chunk-JN7RHBTS.js → chunk-KWIYQSWK.js} +5 -1
- package/dist/chunk-KWIYQSWK.js.map +1 -0
- package/dist/{chunk-NSMKSPBU.js → chunk-Z7DVZ34X.js} +4 -3
- package/dist/{chunk-NSMKSPBU.js.map → chunk-Z7DVZ34X.js.map} +1 -1
- package/dist/otel/index.js +1 -1
- package/dist/{tracing-CEucJqQI.d.ts → tracing-CDeEfMis.d.ts} +24 -1
- package/package.json +1 -1
- package/dist/chunk-JN7RHBTS.js.map +0 -1
- package/dist/chunk-P24ECOI3.js.map +0 -1
|
@@ -9,20 +9,70 @@ import {
|
|
|
9
9
|
resolveApoAuthHeaders,
|
|
10
10
|
withApoRun,
|
|
11
11
|
withApoRunSync
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-CSOY6OSP.js";
|
|
13
13
|
import {
|
|
14
14
|
createApoTracer
|
|
15
15
|
} from "../chunk-OZWOLCJ5.js";
|
|
16
16
|
import "../chunk-HRYXLRXJ.js";
|
|
17
17
|
import {
|
|
18
18
|
createApoOpenAI
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-Z7DVZ34X.js";
|
|
20
20
|
import {
|
|
21
21
|
createApoAnthropic
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-6U6V7VKZ.js";
|
|
23
23
|
import {
|
|
24
24
|
servingHostFromBaseURL
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-KWIYQSWK.js";
|
|
26
|
+
|
|
27
|
+
// src/agent-task/trace-projection/retrieval.ts
|
|
28
|
+
function parseRetrievedDocuments(output) {
|
|
29
|
+
const array = documentsArray(output);
|
|
30
|
+
if (array === void 0) return void 0;
|
|
31
|
+
const documents = [];
|
|
32
|
+
for (const item of array) {
|
|
33
|
+
const document = parseDocument(item);
|
|
34
|
+
if (document !== void 0) documents.push(document);
|
|
35
|
+
}
|
|
36
|
+
return documents;
|
|
37
|
+
}
|
|
38
|
+
function documentsFromObservation(obs) {
|
|
39
|
+
return parseRetrievedDocuments(obs.toolResult) ?? parseRetrievedDocuments(obs.output);
|
|
40
|
+
}
|
|
41
|
+
function retrievalQueryFrom(obs) {
|
|
42
|
+
return queryIn(obs.toolParameters) ?? queryIn(obs.input);
|
|
43
|
+
}
|
|
44
|
+
function queryIn(value) {
|
|
45
|
+
if (value != null && typeof value === "object") {
|
|
46
|
+
const query = value.query;
|
|
47
|
+
if (typeof query === "string") return query;
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function documentsArray(output) {
|
|
52
|
+
if (Array.isArray(output)) return output;
|
|
53
|
+
if (output != null && typeof output === "object") {
|
|
54
|
+
const wrapped = output.documents;
|
|
55
|
+
if (Array.isArray(wrapped)) return wrapped;
|
|
56
|
+
}
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
function parseDocument(item) {
|
|
60
|
+
if (typeof item === "string") {
|
|
61
|
+
return item.length > 0 ? { content: item } : void 0;
|
|
62
|
+
}
|
|
63
|
+
if (item == null || typeof item !== "object") return void 0;
|
|
64
|
+
const record = item;
|
|
65
|
+
const rawContent = record.content ?? record.text;
|
|
66
|
+
const content = typeof rawContent === "string" ? rawContent : "";
|
|
67
|
+
const id = typeof record.id === "string" && record.id.length > 0 ? record.id : void 0;
|
|
68
|
+
if (content.length === 0 && id === void 0) return void 0;
|
|
69
|
+
const document = { content };
|
|
70
|
+
if (id !== void 0) document.id = id;
|
|
71
|
+
if (typeof record.score === "number" && Number.isFinite(record.score)) {
|
|
72
|
+
document.score = record.score;
|
|
73
|
+
}
|
|
74
|
+
return document;
|
|
75
|
+
}
|
|
26
76
|
|
|
27
77
|
// src/agent-task/run/steer-scheduler.ts
|
|
28
78
|
var TASK_STEER_SPAN_NAME = "task.steer";
|
|
@@ -277,6 +327,18 @@ var TraceView = class _TraceView {
|
|
|
277
327
|
get toolNamesInOrder() {
|
|
278
328
|
return this.toolCalls.map((c) => c.name);
|
|
279
329
|
}
|
|
330
|
+
/** Retrievals derived from `RETRIEVER` observations, in invocation order. */
|
|
331
|
+
get retrievals() {
|
|
332
|
+
return this.sortedObservations.filter((o) => o.type === "RETRIEVER").map((o) => {
|
|
333
|
+
const retrieval = { spanId: o.spanId, status: o.status };
|
|
334
|
+
const query = retrievalQueryFrom(o);
|
|
335
|
+
if (query !== void 0) retrieval.query = query;
|
|
336
|
+
const documents = documentsFromObservation(o);
|
|
337
|
+
if (documents !== void 0) retrieval.documents = documents;
|
|
338
|
+
if (o.startedAt !== void 0) retrieval.startedAt = o.startedAt;
|
|
339
|
+
return retrieval;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
280
342
|
/** Skill loads derived from `SKILL` observations, in invocation order. */
|
|
281
343
|
get skillLoads() {
|
|
282
344
|
return this.sortedObservations.filter((o) => o.type === "SKILL").map((o) => ({ spanId: o.spanId, skill: o.name, startedAt: o.startedAt }));
|
|
@@ -2073,9 +2135,6 @@ function evaluateComputeExpression(expression) {
|
|
|
2073
2135
|
const parser = new Parser(tokens);
|
|
2074
2136
|
const value = parser.parseExpression(0);
|
|
2075
2137
|
parser.expectEnd();
|
|
2076
|
-
if (!Number.isFinite(value)) {
|
|
2077
|
-
return { error: "result is not finite" };
|
|
2078
|
-
}
|
|
2079
2138
|
return { value, text: Number(value.toPrecision(12)).toString() };
|
|
2080
2139
|
} catch (error) {
|
|
2081
2140
|
return { error: error instanceof ComputeError ? error.message : String(error) };
|
|
@@ -2863,7 +2922,7 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2863
2922
|
rec.record(
|
|
2864
2923
|
label,
|
|
2865
2924
|
false,
|
|
2866
|
-
"No judge model configured for t.agent. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter
|
|
2925
|
+
"No judge model configured for t.agent. Set one of:\n\u2022 AGENT_TASK_OPENROUTER_MODEL or OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter, needs a tool-calling-capable model)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\n\u2022 ANTHROPIC_MODEL + ANTHROPIC_API_KEY (Anthropic direct, needs a tool-calling-capable model, or ANTHROPIC_AUTH_TOKEN for plan credits)\nOr pass { judge } to runTask() programmatically.",
|
|
2867
2926
|
{ evaluator_type: "agent", location, outcome: "error" }
|
|
2868
2927
|
);
|
|
2869
2928
|
return;
|
|
@@ -2964,6 +3023,246 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2964
3023
|
};
|
|
2965
3024
|
}
|
|
2966
3025
|
|
|
3026
|
+
// src/agent-task/checks/grounded.ts
|
|
3027
|
+
var GROUNDEDNESS_RUBRIC = [
|
|
3028
|
+
"Value 1 is an answer. Value 2 is the set of source documents the answer was supposed to rely on.",
|
|
3029
|
+
"Decide whether the answer is grounded in the sources. Check every factual claim in the answer. A claim is supported only when a source states it, or it follows directly from what a source states. Greetings, hedges, and statements that the answer cannot help are not factual claims.",
|
|
3030
|
+
"PASS when every factual claim is supported. FAIL when any factual claim is unsupported or contradicted by the sources, even if it is true in the real world. Groundedness measures the answer against these sources, not against your own knowledge. In your reasoning, name each unsupported claim."
|
|
3031
|
+
].join("\n\n");
|
|
3032
|
+
function formatTraceSources(retrievals) {
|
|
3033
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3034
|
+
const documents = [];
|
|
3035
|
+
for (const retrieval of retrievals) {
|
|
3036
|
+
for (const document of retrieval.documents ?? []) {
|
|
3037
|
+
const key = document.id ?? document.content;
|
|
3038
|
+
if (seen.has(key)) continue;
|
|
3039
|
+
seen.add(key);
|
|
3040
|
+
documents.push(document);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return formatSources(documents);
|
|
3044
|
+
}
|
|
3045
|
+
function formatAuthorSources(sources) {
|
|
3046
|
+
return formatSources(sources.map((content) => ({ content })));
|
|
3047
|
+
}
|
|
3048
|
+
function formatSources(documents) {
|
|
3049
|
+
if (documents.length === 0) return "(no documents were retrieved)";
|
|
3050
|
+
return documents.map((document, i) => {
|
|
3051
|
+
const heading = document.id === void 0 ? `[${i + 1}]` : `[${i + 1}] ${document.id}`;
|
|
3052
|
+
return `${heading}
|
|
3053
|
+
${document.content}`;
|
|
3054
|
+
}).join("\n\n");
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
// src/agent-task/checks/embedding.ts
|
|
3058
|
+
function isEmbeddingHttpConfig(provider) {
|
|
3059
|
+
return typeof provider.model === "string";
|
|
3060
|
+
}
|
|
3061
|
+
function resolveEmbeddingConfig(provider, override) {
|
|
3062
|
+
const base = resolveEmbeddingProvider(provider);
|
|
3063
|
+
if (!override) return base;
|
|
3064
|
+
if (base?.custom && !override.model) return base;
|
|
3065
|
+
const baseConfig = base?.config;
|
|
3066
|
+
if (!override.model && !baseConfig) return void 0;
|
|
3067
|
+
return {
|
|
3068
|
+
config: {
|
|
3069
|
+
model: override.model ?? baseConfig.model,
|
|
3070
|
+
baseURL: override.baseURL ?? baseConfig?.baseURL,
|
|
3071
|
+
apiKey: override.apiKey ?? baseConfig?.apiKey,
|
|
3072
|
+
dimensions: override.dimensions ?? baseConfig?.dimensions
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function resolveEmbeddingProvider(provider) {
|
|
3077
|
+
if (!provider) return void 0;
|
|
3078
|
+
if ("config" in provider || "custom" in provider) return provider;
|
|
3079
|
+
return isEmbeddingHttpConfig(provider) ? { config: provider } : { custom: provider };
|
|
3080
|
+
}
|
|
3081
|
+
var EmbeddingUnavailableError = class extends Error {
|
|
3082
|
+
constructor(message) {
|
|
3083
|
+
super(message);
|
|
3084
|
+
this.name = "EmbeddingUnavailableError";
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
function createEmbeddingClient() {
|
|
3088
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3089
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
3090
|
+
return {
|
|
3091
|
+
async embed(texts, provider) {
|
|
3092
|
+
const inputs = texts.map((text) => text.replace(/\r?\n/g, " "));
|
|
3093
|
+
const keyed = inputs.map((text) => ({ text, key: cacheKeyFor(text, provider) }));
|
|
3094
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3095
|
+
const needFetch = keyed.filter(({ key }) => {
|
|
3096
|
+
if (resolved.has(key) || inflight.has(key) || seen.has(key)) return false;
|
|
3097
|
+
seen.add(key);
|
|
3098
|
+
return true;
|
|
3099
|
+
});
|
|
3100
|
+
let fetchedMeta;
|
|
3101
|
+
if (needFetch.length > 0) {
|
|
3102
|
+
const fetchPromise = (async () => fetchEmbeddings(needFetch.map((n) => n.text), provider))();
|
|
3103
|
+
for (let i = 0; i < needFetch.length; i++) {
|
|
3104
|
+
const promise = fetchPromise.then((fetched) => {
|
|
3105
|
+
fetchedMeta = {
|
|
3106
|
+
dimensions: fetched.dimensions,
|
|
3107
|
+
...fetched.servedModel !== void 0 ? { servedModel: fetched.servedModel } : {},
|
|
3108
|
+
...fetched.tokens !== void 0 ? { tokens: fetched.tokens } : {},
|
|
3109
|
+
...fetched.latencyMs !== void 0 ? { latencyMs: fetched.latencyMs } : {}
|
|
3110
|
+
};
|
|
3111
|
+
return fetched.vectors[i];
|
|
3112
|
+
});
|
|
3113
|
+
inflight.set(needFetch[i].key, promise);
|
|
3114
|
+
}
|
|
3115
|
+
void Promise.allSettled(needFetch.map(({ key }) => inflight.get(key))).then(() => {
|
|
3116
|
+
for (const { key } of needFetch) inflight.delete(key);
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
const vectors = await Promise.all(
|
|
3120
|
+
keyed.map(async ({ key }) => {
|
|
3121
|
+
const inFlight = inflight.get(key);
|
|
3122
|
+
if (inFlight !== void 0) {
|
|
3123
|
+
const vector = await inFlight;
|
|
3124
|
+
resolved.set(key, vector);
|
|
3125
|
+
return vector;
|
|
3126
|
+
}
|
|
3127
|
+
const cached = resolved.get(key);
|
|
3128
|
+
if (cached !== void 0) return cached;
|
|
3129
|
+
throw new EmbeddingUnavailableError("embedding vector vanished: concurrent cache race");
|
|
3130
|
+
})
|
|
3131
|
+
);
|
|
3132
|
+
const dims = vectors[0].length;
|
|
3133
|
+
if (vectors.some((v) => v.length !== dims)) {
|
|
3134
|
+
throw new EmbeddingUnavailableError("provider returned vectors of mixed dimensionality");
|
|
3135
|
+
}
|
|
3136
|
+
return {
|
|
3137
|
+
vectors,
|
|
3138
|
+
dimensions: dims,
|
|
3139
|
+
...fetchedMeta?.servedModel !== void 0 ? { servedModel: fetchedMeta.servedModel } : {},
|
|
3140
|
+
...fetchedMeta?.tokens !== void 0 ? { tokens: fetchedMeta.tokens } : {},
|
|
3141
|
+
...fetchedMeta?.latencyMs !== void 0 ? { latencyMs: fetchedMeta.latencyMs } : {}
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
function cacheKeyFor(text, provider) {
|
|
3147
|
+
if (provider.custom) return `custom|${text}`;
|
|
3148
|
+
const c = provider.config;
|
|
3149
|
+
return `${c.model}|${c.baseURL ?? ""}|${c.dimensions ?? "auto"}|${text}`;
|
|
3150
|
+
}
|
|
3151
|
+
async function fetchEmbeddings(inputs, provider) {
|
|
3152
|
+
const startedAt = Date.now();
|
|
3153
|
+
if (provider.custom) {
|
|
3154
|
+
const vectors = await provider.custom.embed(inputs);
|
|
3155
|
+
if (!Array.isArray(vectors) || vectors.length !== inputs.length) {
|
|
3156
|
+
throw new EmbeddingUnavailableError(
|
|
3157
|
+
`custom embed function returned ${vectors?.length ?? 0} vectors for ${inputs.length} inputs`
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
return { vectors, dimensions: vectors[0]?.length ?? 0, latencyMs: Date.now() - startedAt };
|
|
3161
|
+
}
|
|
3162
|
+
const config = provider.config;
|
|
3163
|
+
const call = (omitDimensions) => requestEmbeddings(config, inputs, omitDimensions);
|
|
3164
|
+
const first = await callWithRetry(() => call(false));
|
|
3165
|
+
const result = first.kind === "rejected-dimensions" ? await callWithRetry(() => call(true)) : first;
|
|
3166
|
+
if (result.kind !== "ok") throw new Error(result.message);
|
|
3167
|
+
return {
|
|
3168
|
+
vectors: result.vectors,
|
|
3169
|
+
...result.servedModel ? { servedModel: result.servedModel } : {},
|
|
3170
|
+
dimensions: result.vectors[0]?.length ?? 0,
|
|
3171
|
+
...result.tokens ? { tokens: result.tokens } : {},
|
|
3172
|
+
latencyMs: Date.now() - startedAt
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
async function callWithRetry(call) {
|
|
3176
|
+
const first = await call();
|
|
3177
|
+
if (first.kind !== "error" || first.status !== 429) return first;
|
|
3178
|
+
const retryAfter = Number(first.message.match(/retry-after (\d+)/)?.[1] ?? 1);
|
|
3179
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(retryAfter, 10) * 1e3));
|
|
3180
|
+
return call();
|
|
3181
|
+
}
|
|
3182
|
+
async function requestEmbeddings(config, inputs, omitDimensions) {
|
|
3183
|
+
if (!config.baseURL) {
|
|
3184
|
+
return {
|
|
3185
|
+
kind: "error",
|
|
3186
|
+
message: "no embeddings endpoint configured: set APO_EMBEDDING_BASE_URL (or pass baseURL), e.g. https://openrouter.ai/api/v1 or a local server like http://localhost:11434/v1"
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
3189
|
+
const url = `${config.baseURL.replace(/\/$/, "")}/embeddings`;
|
|
3190
|
+
let response;
|
|
3191
|
+
try {
|
|
3192
|
+
response = await fetch(url, {
|
|
3193
|
+
method: "POST",
|
|
3194
|
+
headers: {
|
|
3195
|
+
"content-type": "application/json",
|
|
3196
|
+
...config.apiKey ? { authorization: `Bearer ${config.apiKey}` } : {}
|
|
3197
|
+
},
|
|
3198
|
+
body: JSON.stringify({
|
|
3199
|
+
model: config.model,
|
|
3200
|
+
input: inputs,
|
|
3201
|
+
...omitDimensions ? {} : config.dimensions !== void 0 ? { dimensions: config.dimensions } : {}
|
|
3202
|
+
})
|
|
3203
|
+
});
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
return { kind: "error", message: `embeddings endpoint unreachable: ${error instanceof Error ? error.message : String(error)}` };
|
|
3206
|
+
}
|
|
3207
|
+
if (response.status === 400) {
|
|
3208
|
+
const body2 = await response.text();
|
|
3209
|
+
if (/dimensions/i.test(body2)) return { kind: "rejected-dimensions", message: body2 };
|
|
3210
|
+
return { kind: "error", status: 400, message: `embeddings request rejected (400): ${body2}` };
|
|
3211
|
+
}
|
|
3212
|
+
if (response.status === 429) {
|
|
3213
|
+
const retryAfter = response.headers.get("retry-after");
|
|
3214
|
+
return { kind: "error", status: 429, message: `embeddings rate-limited (429), retry-after ${retryAfter ?? 1}` };
|
|
3215
|
+
}
|
|
3216
|
+
if (!response.ok) {
|
|
3217
|
+
const body2 = await response.text();
|
|
3218
|
+
return { kind: "error", status: response.status, message: `embeddings request failed (${response.status}): ${body2.slice(0, 500)}` };
|
|
3219
|
+
}
|
|
3220
|
+
const body = await response.json();
|
|
3221
|
+
const data = body.data;
|
|
3222
|
+
if (!Array.isArray(data) || data.length !== inputs.length) {
|
|
3223
|
+
return { kind: "error", message: `embeddings response malformed: expected ${inputs.length} vectors, got ${data?.length ?? "none"}` };
|
|
3224
|
+
}
|
|
3225
|
+
const vectors = Array.from({ length: data.length });
|
|
3226
|
+
data.forEach((item, position) => {
|
|
3227
|
+
const vector = item.embedding;
|
|
3228
|
+
if (!Array.isArray(vector) || vector.some((v) => typeof v !== "number")) {
|
|
3229
|
+
throw new EmbeddingUnavailableError("embeddings response malformed: a returned embedding is not a number array");
|
|
3230
|
+
}
|
|
3231
|
+
const slot = typeof item.index === "number" && item.index >= 0 && item.index < data.length ? item.index : position;
|
|
3232
|
+
vectors[slot] = vector;
|
|
3233
|
+
});
|
|
3234
|
+
if (vectors.some((v) => v === void 0)) {
|
|
3235
|
+
return { kind: "error", message: "embeddings response malformed: index field named a slot outside the response" };
|
|
3236
|
+
}
|
|
3237
|
+
return {
|
|
3238
|
+
kind: "ok",
|
|
3239
|
+
vectors,
|
|
3240
|
+
...typeof body.model === "string" ? { servedModel: body.model } : {},
|
|
3241
|
+
...typeof body.usage?.prompt_tokens === "number" ? { tokens: { input: body.usage.prompt_tokens } } : {}
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
function cosineSimilarity(a, b) {
|
|
3245
|
+
if (a.length !== b.length) return Number.NaN;
|
|
3246
|
+
let dot = 0;
|
|
3247
|
+
let normA = 0;
|
|
3248
|
+
let normB = 0;
|
|
3249
|
+
for (let i = 0; i < a.length; i++) {
|
|
3250
|
+
dot += a[i] * b[i];
|
|
3251
|
+
normA += a[i] * a[i];
|
|
3252
|
+
normB += b[i] * b[i];
|
|
3253
|
+
}
|
|
3254
|
+
if (normA === 0 || normB === 0) return 0;
|
|
3255
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
3256
|
+
}
|
|
3257
|
+
function vectorMagnitude(v) {
|
|
3258
|
+
let sum = 0;
|
|
3259
|
+
for (const x of v) sum += x * x;
|
|
3260
|
+
return Math.sqrt(sum);
|
|
3261
|
+
}
|
|
3262
|
+
function embeddingSettingsSignature(model, dimensions) {
|
|
3263
|
+
return `${model}:${dimensions}d:cosine:apo-v1`;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
2967
3266
|
// src/agent-task/checks/t.ts
|
|
2968
3267
|
function resolveJudgeConfig(judgeConfig, override) {
|
|
2969
3268
|
const model = override?.model ?? judgeConfig?.model;
|
|
@@ -2985,6 +3284,8 @@ var TEST_METHOD_NAMES = [
|
|
|
2985
3284
|
"usedNoTools",
|
|
2986
3285
|
"maxToolCalls",
|
|
2987
3286
|
"noFailedActions",
|
|
3287
|
+
"retrieved",
|
|
3288
|
+
"maxRetrievals",
|
|
2988
3289
|
"loadedSkill",
|
|
2989
3290
|
"calledSubagent",
|
|
2990
3291
|
"messageIncludes",
|
|
@@ -2995,9 +3296,12 @@ var TEST_METHOD_NAMES = [
|
|
|
2995
3296
|
"steerDelivered",
|
|
2996
3297
|
"afterSteer",
|
|
2997
3298
|
"assert",
|
|
3299
|
+
"not",
|
|
2998
3300
|
"check",
|
|
2999
3301
|
"judge",
|
|
3000
|
-
"
|
|
3302
|
+
"semantic",
|
|
3303
|
+
"agent",
|
|
3304
|
+
"grounded"
|
|
3001
3305
|
];
|
|
3002
3306
|
function describeName(m) {
|
|
3003
3307
|
if (typeof m === "string") return `"${m}"`;
|
|
@@ -3078,7 +3382,7 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
3078
3382
|
rec.record(
|
|
3079
3383
|
label,
|
|
3080
3384
|
false,
|
|
3081
|
-
"No judge model configured. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter
|
|
3385
|
+
"No judge model configured. Set one of:\n\u2022 AGENT_TASK_OPENROUTER_MODEL or OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter, 200+ models, one account)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\n\u2022 ANTHROPIC_MODEL + ANTHROPIC_API_KEY (Anthropic direct, or ANTHROPIC_AUTH_TOKEN for plan credits)\nOr pass { judge } to runTask() programmatically.",
|
|
3082
3386
|
// No judge means no verdict — the same bucket as an unreachable judge.
|
|
3083
3387
|
{ evaluator_type: "llm", location, outcome: "error" }
|
|
3084
3388
|
);
|
|
@@ -3187,7 +3491,10 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
3187
3491
|
text: res?.reasoning?.slice(0, 2e3) ?? "",
|
|
3188
3492
|
verdict: {
|
|
3189
3493
|
reasoning: res?.reasoning?.slice(0, 2e3) ?? null,
|
|
3190
|
-
pass: res?.pass ?? null
|
|
3494
|
+
pass: res?.pass ?? null,
|
|
3495
|
+
// A judge that gave no verdict still reports pass: false;
|
|
3496
|
+
// the outcome tells a trace reader it is not a "no".
|
|
3497
|
+
...res?.unavailable ? { outcome: "error" } : {}
|
|
3191
3498
|
}
|
|
3192
3499
|
};
|
|
3193
3500
|
},
|
|
@@ -3258,6 +3565,143 @@ function judgeScopeToContext(scope) {
|
|
|
3258
3565
|
...deliverableNames && deliverableNames.length > 0 ? { deliverableNames } : {}
|
|
3259
3566
|
};
|
|
3260
3567
|
}
|
|
3568
|
+
function createSemanticMethod(rec, embedding, judgeScope) {
|
|
3569
|
+
const client = embedding?.client ?? createEmbeddingClient();
|
|
3570
|
+
const previewOf = (text) => text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
3571
|
+
return async (value, reference, opts) => {
|
|
3572
|
+
const label = opts?.label ?? "semantic";
|
|
3573
|
+
const location = rec.captureLocation();
|
|
3574
|
+
const threshold = opts.threshold;
|
|
3575
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
3576
|
+
rec.record(
|
|
3577
|
+
label,
|
|
3578
|
+
false,
|
|
3579
|
+
`t.semantic: threshold must be a finite number in [0, 1], got ${threshold}`,
|
|
3580
|
+
{ evaluator_type: "embedding", location }
|
|
3581
|
+
);
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
const resolved = resolveEmbeddingConfig(embedding?.provider, opts.embedding);
|
|
3585
|
+
if (!resolved) {
|
|
3586
|
+
rec.record(
|
|
3587
|
+
label,
|
|
3588
|
+
false,
|
|
3589
|
+
"No embedding model configured. Set one of:\n\u2022 APO_EMBEDDING_MODEL + APO_EMBEDDING_API_KEY (+ APO_EMBEDDING_BASE_URL for a local server)\n\u2022 OPENROUTER_API_KEY (defaults to openai/text-embedding-3-small via OpenRouter)\n\u2022 OPENAI_API_KEY (defaults to text-embedding-3-small)\nOr pass { embedding } to runTask() programmatically.",
|
|
3590
|
+
{ evaluator_type: "embedding", location, outcome: "error" }
|
|
3591
|
+
);
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
const valueText = typeof value === "string" ? value : describeValue(value);
|
|
3595
|
+
const modelName = resolved.config?.model ?? "custom";
|
|
3596
|
+
const tracer = embedding?.tracer;
|
|
3597
|
+
const grade = (result) => {
|
|
3598
|
+
const [valueVector, referenceVector] = result.vectors;
|
|
3599
|
+
const similarity2 = cosineSimilarity(valueVector, referenceVector);
|
|
3600
|
+
if (!Number.isFinite(similarity2)) {
|
|
3601
|
+
throw new Error("cosine similarity undefined: provider returned non-finite vector components");
|
|
3602
|
+
}
|
|
3603
|
+
const rounded = Math.round(similarity2 * 1e4) / 1e4;
|
|
3604
|
+
return {
|
|
3605
|
+
similarity: rounded,
|
|
3606
|
+
pass: rounded >= threshold,
|
|
3607
|
+
signature: embeddingSettingsSignature(modelName, result.dimensions),
|
|
3608
|
+
dimensions: result.dimensions,
|
|
3609
|
+
...result.servedModel ? { servedModel: result.servedModel } : {},
|
|
3610
|
+
...result.tokens ? { tokens: result.tokens } : {},
|
|
3611
|
+
...result.latencyMs !== void 0 ? { latencyMs: result.latencyMs } : {},
|
|
3612
|
+
zeroMagnitude: vectorMagnitude(valueVector) === 0 || vectorMagnitude(referenceVector) === 0
|
|
3613
|
+
};
|
|
3614
|
+
};
|
|
3615
|
+
let spanId;
|
|
3616
|
+
const call = () => client.embed([valueText, reference], resolved);
|
|
3617
|
+
try {
|
|
3618
|
+
const verdict = await (tracer ? tracer.step(
|
|
3619
|
+
{
|
|
3620
|
+
step_name: `embedding:${judgeScope?.checkName ?? label}`,
|
|
3621
|
+
observation_type: "EMBEDDING",
|
|
3622
|
+
model: modelName,
|
|
3623
|
+
...servingHostFromBaseURL(resolved.config?.baseURL),
|
|
3624
|
+
input: { model: modelName, threshold },
|
|
3625
|
+
summarize: (r) => {
|
|
3626
|
+
const res = r;
|
|
3627
|
+
const text = `cosine ${res.similarity} vs threshold ${threshold}` + (res.pass ? ": pass" : ": fail");
|
|
3628
|
+
return {
|
|
3629
|
+
text,
|
|
3630
|
+
verdict: {
|
|
3631
|
+
pass: res.pass,
|
|
3632
|
+
similarity: res.similarity,
|
|
3633
|
+
threshold,
|
|
3634
|
+
settings: res.signature
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
},
|
|
3638
|
+
usage: (r) => {
|
|
3639
|
+
const tokens = r.tokens;
|
|
3640
|
+
return tokens?.input !== void 0 ? { prompt_tokens: tokens.input } : void 0;
|
|
3641
|
+
}
|
|
3642
|
+
},
|
|
3643
|
+
async (id) => {
|
|
3644
|
+
spanId = id;
|
|
3645
|
+
return grade(await call());
|
|
3646
|
+
}
|
|
3647
|
+
) : grade(await call()));
|
|
3648
|
+
const delta = verdict.similarity - threshold;
|
|
3649
|
+
let reasoning = verdict.pass ? `semantic similarity ${verdict.similarity.toFixed(4)} \u2265 ${threshold.toFixed(4)} [${verdict.signature}]` : `semantic similarity ${verdict.similarity.toFixed(4)} < ${threshold.toFixed(4)} (delta ${delta.toFixed(4)}) [${verdict.signature}]`;
|
|
3650
|
+
if (Math.abs(delta) < 0.02) {
|
|
3651
|
+
reasoning += ". Verdict is marginal: within 0.02 of the threshold, it will flip on model drift";
|
|
3652
|
+
}
|
|
3653
|
+
if (verdict.zeroMagnitude) {
|
|
3654
|
+
reasoning += " (zero-magnitude vector: the provider returned an empty direction)";
|
|
3655
|
+
}
|
|
3656
|
+
const metadata = {
|
|
3657
|
+
model: modelName,
|
|
3658
|
+
...verdict.servedModel ? { served_model: verdict.servedModel } : {},
|
|
3659
|
+
dimensions: verdict.dimensions,
|
|
3660
|
+
similarity: verdict.similarity,
|
|
3661
|
+
threshold,
|
|
3662
|
+
metric: "cosine",
|
|
3663
|
+
settings_hash: verdict.signature,
|
|
3664
|
+
...verdict.tokens ? { tokens: verdict.tokens } : {},
|
|
3665
|
+
...verdict.latencyMs !== void 0 ? { latency_ms: verdict.latencyMs } : {},
|
|
3666
|
+
...isTraceableSpanId(spanId) ? { span_id: spanId } : {},
|
|
3667
|
+
value_preview: previewOf(valueText),
|
|
3668
|
+
reference_preview: previewOf(reference)
|
|
3669
|
+
};
|
|
3670
|
+
rec.record(label, verdict.pass, reasoning, {
|
|
3671
|
+
evaluator_type: "embedding",
|
|
3672
|
+
expected: `cosine \u2265 ${threshold.toFixed(4)} (${modelName}:${verdict.dimensions}d)`,
|
|
3673
|
+
received: verdict.similarity,
|
|
3674
|
+
location,
|
|
3675
|
+
embedding: metadata
|
|
3676
|
+
});
|
|
3677
|
+
if (tracer && isTraceableSpanId(spanId)) {
|
|
3678
|
+
try {
|
|
3679
|
+
await tracer.score({
|
|
3680
|
+
name: "semantic_similarity",
|
|
3681
|
+
value: verdict.similarity,
|
|
3682
|
+
dataType: "NUMERIC",
|
|
3683
|
+
source: "EVAL",
|
|
3684
|
+
observationId: spanId
|
|
3685
|
+
}).catch(() => void 0);
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
} catch (error) {
|
|
3690
|
+
rec.record(
|
|
3691
|
+
label,
|
|
3692
|
+
false,
|
|
3693
|
+
`embedding check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3694
|
+
{
|
|
3695
|
+
evaluator_type: "embedding",
|
|
3696
|
+
expected: `cosine \u2265 ${threshold.toFixed(4)}`,
|
|
3697
|
+
received: valueText,
|
|
3698
|
+
location,
|
|
3699
|
+
outcome: "error"
|
|
3700
|
+
}
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3261
3705
|
var CAPABILITY_LABELS = {
|
|
3262
3706
|
messages: "message",
|
|
3263
3707
|
tools: "tool",
|
|
@@ -3265,9 +3709,10 @@ var CAPABILITY_LABELS = {
|
|
|
3265
3709
|
timing: "timing",
|
|
3266
3710
|
skills: "skill",
|
|
3267
3711
|
subagents: "subagent",
|
|
3268
|
-
usage: "token usage"
|
|
3712
|
+
usage: "token usage",
|
|
3713
|
+
retrieval: "retrieval"
|
|
3269
3714
|
};
|
|
3270
|
-
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer, judgeTools) {
|
|
3715
|
+
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer, judgeTools, embedding) {
|
|
3271
3716
|
const unsupported = (id, capability) => {
|
|
3272
3717
|
const label = CAPABILITY_LABELS[capability];
|
|
3273
3718
|
rec.record(id, false, `${label} evidence is unavailable in this trace projection`, {
|
|
@@ -3277,7 +3722,24 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3277
3722
|
});
|
|
3278
3723
|
};
|
|
3279
3724
|
const isAvailable = (c) => view.requireCapability(c) === "available";
|
|
3280
|
-
|
|
3725
|
+
const traceSourcesFor = (label) => {
|
|
3726
|
+
if (!isAvailable("retrieval")) {
|
|
3727
|
+
unsupported(label, "retrieval");
|
|
3728
|
+
return void 0;
|
|
3729
|
+
}
|
|
3730
|
+
const opaque = view.retrievals.filter((r) => r.documents === void 0).length;
|
|
3731
|
+
if (opaque > 0) {
|
|
3732
|
+
rec.record(label, false, `${opaque} retrieval(s) returned results in an unrecognized shape, so the sources are incomplete`, {
|
|
3733
|
+
evaluator_type: "llm",
|
|
3734
|
+
outcome: "unsupported",
|
|
3735
|
+
expected: "every retrieval's documents known",
|
|
3736
|
+
received: `${opaque} unrecognized retrieval result(s)`
|
|
3737
|
+
});
|
|
3738
|
+
return void 0;
|
|
3739
|
+
}
|
|
3740
|
+
return formatTraceSources(view.retrievals);
|
|
3741
|
+
};
|
|
3742
|
+
const context2 = {
|
|
3281
3743
|
calledTool(name, opts) {
|
|
3282
3744
|
if (!isAvailable("tools")) {
|
|
3283
3745
|
unsupported(`calledTool(${describeName(name)})`, "tools");
|
|
@@ -3375,6 +3837,51 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3375
3837
|
received: `${failed}`
|
|
3376
3838
|
});
|
|
3377
3839
|
},
|
|
3840
|
+
retrieved(document, opts) {
|
|
3841
|
+
requireTopK(opts?.topK);
|
|
3842
|
+
const id = `retrieved(${describeDocument(document)}${describeRetrievedOptions(opts)})`;
|
|
3843
|
+
if (!isAvailable("retrieval")) {
|
|
3844
|
+
unsupported(id, "retrieval");
|
|
3845
|
+
return;
|
|
3846
|
+
}
|
|
3847
|
+
const expected = `${describeDocument(document)} retrieved${describeRetrievedOptions(opts)}`;
|
|
3848
|
+
const scoped = view.retrievals.filter(
|
|
3849
|
+
(r) => opts?.query === void 0 || (r.query?.includes(opts.query) ?? false)
|
|
3850
|
+
);
|
|
3851
|
+
const hit = findRetrievedDocument(scoped, document, opts?.topK);
|
|
3852
|
+
if (hit !== void 0) {
|
|
3853
|
+
rec.record(id, true, "", { expected, received: hit });
|
|
3854
|
+
return;
|
|
3855
|
+
}
|
|
3856
|
+
const opaque = scoped.filter((r) => r.documents === void 0).length;
|
|
3857
|
+
if (opaque > 0) {
|
|
3858
|
+
rec.record(
|
|
3859
|
+
id,
|
|
3860
|
+
false,
|
|
3861
|
+
`${opaque} retrieval(s) in scope returned results in an unrecognized shape`,
|
|
3862
|
+
{ outcome: "unsupported", expected, received: `${opaque} unrecognized retrieval result(s)` }
|
|
3863
|
+
);
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
rec.record(
|
|
3867
|
+
id,
|
|
3868
|
+
false,
|
|
3869
|
+
scoped.length === 0 ? `no retrieval ran with a query containing "${opts?.query ?? ""}"` : `no retrieval returned ${describeDocument(document)}${describeRetrievedOptions(opts)}`,
|
|
3870
|
+
{ expected, received: summarizeRetrievals(scoped) }
|
|
3871
|
+
);
|
|
3872
|
+
},
|
|
3873
|
+
maxRetrievals(n) {
|
|
3874
|
+
requireBudget("maxRetrievals", n);
|
|
3875
|
+
if (view.requireCapability("retrieval") === "unavailable") {
|
|
3876
|
+
unsupported(`maxRetrievals(${n})`, "retrieval");
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
const count = view.retrievals.length;
|
|
3880
|
+
rec.record(`maxRetrievals(${n})`, count <= n, count <= n ? "" : `expected \u2264 ${n} retrievals, got ${count}`, {
|
|
3881
|
+
expected: `\u2264 ${n} retrievals`,
|
|
3882
|
+
received: `${count}`
|
|
3883
|
+
});
|
|
3884
|
+
},
|
|
3378
3885
|
loadedSkill(skill) {
|
|
3379
3886
|
if (!isAvailable("skills")) {
|
|
3380
3887
|
unsupported(`loadedSkill("${skill}")`, "skills");
|
|
@@ -3462,7 +3969,8 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3462
3969
|
judgeScope,
|
|
3463
3970
|
agentEvidence,
|
|
3464
3971
|
judgeTracer,
|
|
3465
|
-
judgeTools
|
|
3972
|
+
judgeTools,
|
|
3973
|
+
embedding
|
|
3466
3974
|
)
|
|
3467
3975
|
),
|
|
3468
3976
|
check(value, matcher, label) {
|
|
@@ -3477,7 +3985,38 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3477
3985
|
// judge does not consult trace capabilities; the scope carries the
|
|
3478
3986
|
// task/check frame for prompt builders (#161).
|
|
3479
3987
|
judge: trackPending(rec, createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer)),
|
|
3480
|
-
agent: trackPending(rec, createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view }, judgeTracer, judgeTools))
|
|
3988
|
+
agent: trackPending(rec, createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view }, judgeTracer, judgeTools)),
|
|
3989
|
+
// Like t.judge: value-plane check, no capability gating — but traced, so
|
|
3990
|
+
// the embeddings call shows up as an EMBEDDING observation.
|
|
3991
|
+
semantic: trackPending(rec, createSemanticMethod(rec, embedding, judgeScope)),
|
|
3992
|
+
async grounded(value, opts) {
|
|
3993
|
+
const label = opts?.label ?? "grounded";
|
|
3994
|
+
const sources = opts?.sources ?? "trace";
|
|
3995
|
+
const sourceList = sources === "trace" ? traceSourcesFor(label) : formatAuthorSources(sources);
|
|
3996
|
+
if (sourceList === void 0) return;
|
|
3997
|
+
await context2.judge([value, sourceList], GROUNDEDNESS_RUBRIC, {
|
|
3998
|
+
label,
|
|
3999
|
+
judge: opts?.judge,
|
|
4000
|
+
expect: opts?.expect
|
|
4001
|
+
});
|
|
4002
|
+
},
|
|
4003
|
+
get not() {
|
|
4004
|
+
return createNotContext(context2, rec);
|
|
4005
|
+
}
|
|
4006
|
+
};
|
|
4007
|
+
return context2;
|
|
4008
|
+
}
|
|
4009
|
+
function createNotContext(t, rec) {
|
|
4010
|
+
return {
|
|
4011
|
+
calledTool: (name, opts) => rec.negated(() => t.calledTool(name, opts)),
|
|
4012
|
+
toolOrder: (names) => rec.negated(() => t.toolOrder(names)),
|
|
4013
|
+
loadedSkill: (skill) => rec.negated(() => t.loadedSkill(skill)),
|
|
4014
|
+
calledSubagent: (agent) => rec.negated(() => t.calledSubagent(agent)),
|
|
4015
|
+
messageIncludes: (token) => rec.negated(() => t.messageIncludes(token)),
|
|
4016
|
+
retrieved: (document, opts) => rec.negated(() => t.retrieved(document, opts)),
|
|
4017
|
+
check(value, matcher, label) {
|
|
4018
|
+
rec.negated(() => t.check(value, matcher, label));
|
|
4019
|
+
}
|
|
3481
4020
|
};
|
|
3482
4021
|
}
|
|
3483
4022
|
function requireBudget(method, n) {
|
|
@@ -3582,14 +4121,76 @@ function matchesTraceToolCall(call, name, opts) {
|
|
|
3582
4121
|
}
|
|
3583
4122
|
return true;
|
|
3584
4123
|
}
|
|
4124
|
+
function findRetrievedDocument(retrievals, document, topK) {
|
|
4125
|
+
for (const retrieval of retrievals) {
|
|
4126
|
+
const documents = retrieval.documents ?? [];
|
|
4127
|
+
const limit = topK === void 0 ? documents.length : Math.min(topK, documents.length);
|
|
4128
|
+
for (let i = 0; i < limit; i++) {
|
|
4129
|
+
if (matchesDocument(documents[i], document)) {
|
|
4130
|
+
return `rank ${i + 1} for query "${retrieval.query ?? "(none recorded)"}"`;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
return void 0;
|
|
4135
|
+
}
|
|
4136
|
+
function matchesDocument(doc, matcher) {
|
|
4137
|
+
if (typeof matcher === "string") {
|
|
4138
|
+
return doc.id === matcher || (doc.id?.startsWith(`${matcher}#`) ?? false);
|
|
4139
|
+
}
|
|
4140
|
+
if ("id" in matcher) return doc.id === matcher.id;
|
|
4141
|
+
if ("idPrefix" in matcher) return doc.id?.startsWith(matcher.idPrefix) ?? false;
|
|
4142
|
+
return doc.content.includes(matcher.contentIncludes);
|
|
4143
|
+
}
|
|
4144
|
+
function describeDocument(matcher) {
|
|
4145
|
+
if (typeof matcher === "string") return `"${matcher}"`;
|
|
4146
|
+
if ("id" in matcher) return `{ id: "${matcher.id}" }`;
|
|
4147
|
+
if ("idPrefix" in matcher) return `{ idPrefix: "${matcher.idPrefix}" }`;
|
|
4148
|
+
return `{ contentIncludes: "${matcher.contentIncludes}" }`;
|
|
4149
|
+
}
|
|
4150
|
+
function describeRetrievedOptions(opts) {
|
|
4151
|
+
const parts = [];
|
|
4152
|
+
if (opts?.topK !== void 0) parts.push(`in top ${opts.topK}`);
|
|
4153
|
+
if (opts?.query !== void 0) parts.push(`for query containing "${opts.query}"`);
|
|
4154
|
+
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
4155
|
+
}
|
|
4156
|
+
function summarizeRetrievals(retrievals) {
|
|
4157
|
+
if (retrievals.length === 0) return "(no retrievals)";
|
|
4158
|
+
return retrievals.map((r) => {
|
|
4159
|
+
const documents = (r.documents ?? []).map((d) => d.id ?? `"${d.content.slice(0, 40)}"`);
|
|
4160
|
+
const shown = documents.slice(0, 10).join(", ") + (documents.length > 10 ? ", ..." : "");
|
|
4161
|
+
return `"${r.query ?? "(none recorded)"}": [${shown}]`;
|
|
4162
|
+
}).join("; ");
|
|
4163
|
+
}
|
|
4164
|
+
function requireTopK(topK) {
|
|
4165
|
+
if (topK !== void 0 && (!Number.isInteger(topK) || topK < 1)) {
|
|
4166
|
+
throw new TypeError(`retrieved: topK must be a positive integer, got ${topK}`);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
3585
4169
|
|
|
3586
4170
|
// src/agent-task/checks/recorder.ts
|
|
3587
4171
|
var Recorder = class {
|
|
3588
4172
|
constructor(locate) {
|
|
3589
4173
|
this.records = [];
|
|
3590
4174
|
this.pending = [];
|
|
4175
|
+
this.negating = false;
|
|
3591
4176
|
this.locate = locate;
|
|
3592
4177
|
}
|
|
4178
|
+
/**
|
|
4179
|
+
* Run `fn` with every assertion it records negated (`t.not.*`): a pass
|
|
4180
|
+
* becomes a fail and a fail becomes a pass. Outcomes that are not verdicts
|
|
4181
|
+
* (`unsupported`, `error`) keep their outcome, so missing evidence can never
|
|
4182
|
+
* become a pass by negation. Only synchronous assertions are negatable,
|
|
4183
|
+
* which keeps the scope from leaking into a later record.
|
|
4184
|
+
*/
|
|
4185
|
+
negated(fn) {
|
|
4186
|
+
const previous = this.negating;
|
|
4187
|
+
this.negating = true;
|
|
4188
|
+
try {
|
|
4189
|
+
fn();
|
|
4190
|
+
} finally {
|
|
4191
|
+
this.negating = previous;
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
3593
4194
|
/**
|
|
3594
4195
|
* Capture the call site location synchronously. Use this when a record will
|
|
3595
4196
|
* happen AFTER an `await` (e.g. `t.judge`): once an async function resumes,
|
|
@@ -3625,7 +4226,7 @@ var Recorder = class {
|
|
|
3625
4226
|
*/
|
|
3626
4227
|
record(id, pass, reasoning, extra) {
|
|
3627
4228
|
const location = extra?.location ?? (this.locate ? this.locate(new Error().stack ?? "") : void 0);
|
|
3628
|
-
|
|
4229
|
+
const result = {
|
|
3629
4230
|
id,
|
|
3630
4231
|
pass,
|
|
3631
4232
|
reasoning,
|
|
@@ -3634,13 +4235,29 @@ var Recorder = class {
|
|
|
3634
4235
|
received: extra?.received,
|
|
3635
4236
|
evaluator_type: extra?.evaluator_type,
|
|
3636
4237
|
judge: extra?.judge,
|
|
4238
|
+
embedding: extra?.embedding,
|
|
3637
4239
|
outcome: extra?.outcome
|
|
3638
|
-
}
|
|
4240
|
+
};
|
|
4241
|
+
this.records.push(this.negating ? negate(result) : result);
|
|
3639
4242
|
}
|
|
3640
4243
|
get all() {
|
|
3641
4244
|
return this.records;
|
|
3642
4245
|
}
|
|
3643
4246
|
};
|
|
4247
|
+
function negate(result) {
|
|
4248
|
+
const negated = {
|
|
4249
|
+
...result,
|
|
4250
|
+
id: `not.${result.id}`,
|
|
4251
|
+
expected: result.expected === void 0 ? void 0 : `not ${result.expected}`
|
|
4252
|
+
};
|
|
4253
|
+
if (result.outcome === "unsupported" || result.outcome === "error") return negated;
|
|
4254
|
+
return {
|
|
4255
|
+
...negated,
|
|
4256
|
+
pass: !result.pass,
|
|
4257
|
+
reasoning: result.pass ? `expected ${result.id} not to hold, but it did` : "",
|
|
4258
|
+
outcome: result.outcome === void 0 ? void 0 : result.pass ? "fail" : "pass"
|
|
4259
|
+
};
|
|
4260
|
+
}
|
|
3644
4261
|
|
|
3645
4262
|
// src/agent-task/checks/compact.ts
|
|
3646
4263
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -3875,12 +4492,18 @@ async function runTraceChecks(args) {
|
|
|
3875
4492
|
const view = new TraceView(args.snapshot);
|
|
3876
4493
|
const locate = args.moduleUrl && args.displayFile ? (stack) => parseCheckLocation(stack, args.moduleUrl, args.displayFile) : void 0;
|
|
3877
4494
|
const taskMeta = readTaskMeta(args.task);
|
|
4495
|
+
const embedding = args.embedding ? { ...args.embedding, ...args.embedding.tracer ? {} : optionalTracer(args.judgeTracer) } : { client: createEmbeddingClient(), ...optionalTracer(args.judgeTracer) };
|
|
3878
4496
|
const results = await Promise.all(
|
|
3879
|
-
registry.map((check) => runOneCheck(check, args, view, taskMeta, locate))
|
|
4497
|
+
registry.map((check) => runOneCheck(check, args, embedding, view, taskMeta, locate))
|
|
3880
4498
|
);
|
|
3881
4499
|
return results;
|
|
3882
4500
|
}
|
|
3883
|
-
|
|
4501
|
+
function optionalTracer(tracer) {
|
|
4502
|
+
if (!tracer) return {};
|
|
4503
|
+
const candidate = tracer;
|
|
4504
|
+
return typeof candidate.step === "function" && typeof candidate.score === "function" ? { tracer: candidate } : {};
|
|
4505
|
+
}
|
|
4506
|
+
async function runOneCheck(check, args, embedding, view, taskMeta, locate) {
|
|
3884
4507
|
const runCheck = async () => {
|
|
3885
4508
|
const rec = new Recorder(locate);
|
|
3886
4509
|
const reads = trackDeliverableReads(args.deliverables);
|
|
@@ -3904,7 +4527,8 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3904
4527
|
...args.historyUnavailableReason ? { historyUnavailable: args.historyUnavailableReason } : {}
|
|
3905
4528
|
},
|
|
3906
4529
|
args.judgeTracer,
|
|
3907
|
-
args.judgeTools
|
|
4530
|
+
args.judgeTools,
|
|
4531
|
+
embedding
|
|
3908
4532
|
);
|
|
3909
4533
|
let thrownLocation;
|
|
3910
4534
|
try {
|
|
@@ -3924,9 +4548,11 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3924
4548
|
const pass = failed.length === 0;
|
|
3925
4549
|
const outcome = rollUpCheckOutcome(failed);
|
|
3926
4550
|
const judged = rec.all.filter(isJudgedAssertion);
|
|
3927
|
-
const
|
|
4551
|
+
const semantic = rec.all.filter((r) => r.evaluator_type === "embedding");
|
|
4552
|
+
const reasoning = failed.length > 0 ? failed.map((r) => r.reasoning || r.id).join("; ") : judged.length > 0 || semantic.length > 0 ? [...judged, ...semantic].map((r) => r.reasoning).filter(Boolean).join("; ") || "passed" : rec.all.length > 0 ? "passed" : "no assertions recorded";
|
|
3928
4553
|
const location = failed.find((r) => r.location)?.location;
|
|
3929
4554
|
const judge = rec.all.find((r) => r.judge)?.judge;
|
|
4555
|
+
const embeddingMeta = rec.all.find((r) => r.embedding)?.embedding;
|
|
3930
4556
|
return {
|
|
3931
4557
|
id: check.id,
|
|
3932
4558
|
pass,
|
|
@@ -3934,6 +4560,7 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3934
4560
|
evaluator_type: deriveEvaluatorType(rec.all),
|
|
3935
4561
|
...outcome ? { outcome } : {},
|
|
3936
4562
|
...judge ? { judge } : {},
|
|
4563
|
+
...embeddingMeta ? { embedding: embeddingMeta } : {},
|
|
3937
4564
|
...location ? { location } : {},
|
|
3938
4565
|
...args.displayFile ? { source_file: args.displayFile } : {},
|
|
3939
4566
|
...rec.all.length > 0 ? { assertions: rec.all.map((a) => ({ ...a })) } : {},
|
|
@@ -3963,35 +4590,48 @@ function isJudgedAssertion(assertion) {
|
|
|
3963
4590
|
function deriveEvaluatorType(assertions) {
|
|
3964
4591
|
let sawLlm = false;
|
|
3965
4592
|
let sawAgent = false;
|
|
4593
|
+
let sawEmbedding = false;
|
|
3966
4594
|
let sawDeterministic = false;
|
|
3967
4595
|
for (const assertion of assertions) {
|
|
3968
4596
|
if (isJudgedAssertion(assertion)) {
|
|
3969
4597
|
if (assertion.evaluator_type === "agent") sawAgent = true;
|
|
3970
4598
|
else sawLlm = true;
|
|
4599
|
+
} else if (assertion.evaluator_type === "embedding") {
|
|
4600
|
+
sawEmbedding = true;
|
|
3971
4601
|
} else {
|
|
3972
4602
|
sawDeterministic = true;
|
|
3973
4603
|
}
|
|
3974
4604
|
}
|
|
3975
|
-
|
|
3976
|
-
if (sawDeterministic) return "
|
|
3977
|
-
if (
|
|
3978
|
-
|
|
4605
|
+
const evaluatorKinds = [sawLlm, sawAgent, sawEmbedding].filter(Boolean).length;
|
|
4606
|
+
if (evaluatorKinds === 0 && !sawDeterministic) return "code";
|
|
4607
|
+
if (evaluatorKinds === 1 && !sawDeterministic) {
|
|
4608
|
+
if (sawAgent) return "agent";
|
|
4609
|
+
if (sawEmbedding) return "embedding";
|
|
4610
|
+
return "llm";
|
|
4611
|
+
}
|
|
4612
|
+
if (evaluatorKinds > 0) return "mixed";
|
|
4613
|
+
return "code";
|
|
3979
4614
|
}
|
|
3980
4615
|
function summarizeCheckForSpan(result) {
|
|
3981
4616
|
const check = result;
|
|
4617
|
+
const reasoning = check.reasoning.slice(0, 2e3);
|
|
3982
4618
|
return {
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4619
|
+
text: reasoning,
|
|
4620
|
+
verdict: {
|
|
4621
|
+
pass: check.pass,
|
|
4622
|
+
...check.outcome ? { outcome: check.outcome } : {},
|
|
4623
|
+
reasoning,
|
|
4624
|
+
evaluator_type: check.evaluator_type,
|
|
4625
|
+
...check.judge ? { judge_model: check.judge.model } : {},
|
|
4626
|
+
...check.assertions ? {
|
|
4627
|
+
assertions: check.assertions.map((a) => ({
|
|
4628
|
+
id: a.id,
|
|
4629
|
+
pass: a.pass,
|
|
4630
|
+
...a.expected !== void 0 ? { expected: a.expected } : {},
|
|
4631
|
+
...a.received !== void 0 ? { received: truncateReceivedValue(a.received) } : {}
|
|
4632
|
+
}))
|
|
4633
|
+
} : {}
|
|
4634
|
+
}
|
|
3995
4635
|
};
|
|
3996
4636
|
}
|
|
3997
4637
|
function rollUpCheckOutcome(failed) {
|
|
@@ -4016,6 +4656,7 @@ async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
|
|
|
4016
4656
|
...args.judgeTools ? { judgeTools: args.judgeTools } : {},
|
|
4017
4657
|
...args.historyPlane ? { historyPlane: args.historyPlane } : {},
|
|
4018
4658
|
...args.judgeTracer ? { judgeTracer: args.judgeTracer } : {},
|
|
4659
|
+
...args.embedding ? { embedding: args.embedding } : {},
|
|
4019
4660
|
moduleUrl,
|
|
4020
4661
|
displayFile: basename(checksPath)
|
|
4021
4662
|
});
|
|
@@ -4816,6 +5457,7 @@ function deriveCapabilities(observations, hierarchyAmbiguous) {
|
|
|
4816
5457
|
const hasMessages = observations.some(
|
|
4817
5458
|
(o) => o.type === "GENERATION" && o.messages && o.messages.length > 0
|
|
4818
5459
|
);
|
|
5460
|
+
const retrievals = observations.filter((o) => o.type === "RETRIEVER");
|
|
4819
5461
|
return {
|
|
4820
5462
|
messages: hasMessages ? "available" : "unavailable",
|
|
4821
5463
|
tools: hasType("TOOL") ? "available" : "unavailable",
|
|
@@ -4828,7 +5470,8 @@ function deriveCapabilities(observations, hierarchyAmbiguous) {
|
|
|
4828
5470
|
// Usage is attributed to turns through parentSpanId; with an ambiguous
|
|
4829
5471
|
// step id the chain is broken, so a per-turn or per-run sum could
|
|
4830
5472
|
// silently drop turns.
|
|
4831
|
-
usage: !observations.some((o) => o.usage !== void 0) ? "unavailable" : hierarchyAmbiguous ? "partial" : "available"
|
|
5473
|
+
usage: !observations.some((o) => o.usage !== void 0) ? "unavailable" : hierarchyAmbiguous ? "partial" : "available",
|
|
5474
|
+
retrieval: retrievals.length === 0 ? "unavailable" : retrievals.some((o) => documentsFromObservation(o) !== void 0) ? "available" : "partial"
|
|
4832
5475
|
};
|
|
4833
5476
|
}
|
|
4834
5477
|
function usageFrom(params) {
|
|
@@ -4844,6 +5487,7 @@ function createProjectionTee(real) {
|
|
|
4844
5487
|
const observations = [];
|
|
4845
5488
|
const pending = /* @__PURE__ */ new Map();
|
|
4846
5489
|
const realIdByTeeId = /* @__PURE__ */ new Map();
|
|
5490
|
+
const exportedSpanIds = /* @__PURE__ */ new Set();
|
|
4847
5491
|
const teeIdByRealStepId = /* @__PURE__ */ new Map();
|
|
4848
5492
|
const ambiguousRealStepIds = /* @__PURE__ */ new Set();
|
|
4849
5493
|
const activeStep = new AsyncLocalStorage();
|
|
@@ -4881,6 +5525,7 @@ function createProjectionTee(real) {
|
|
|
4881
5525
|
} else if (params?.output !== void 0) {
|
|
4882
5526
|
obs.output = params.output;
|
|
4883
5527
|
}
|
|
5528
|
+
if (p.type === "RETRIEVER" && p.input !== void 0) obs.input = p.input;
|
|
4884
5529
|
if (p.type === "GENERATION" && params?.output != null) {
|
|
4885
5530
|
const text = extractText(params.output);
|
|
4886
5531
|
if (text !== "") {
|
|
@@ -4920,6 +5565,7 @@ function createProjectionTee(real) {
|
|
|
4920
5565
|
ambiguousRealStepIds.add(realSpanId);
|
|
4921
5566
|
} else {
|
|
4922
5567
|
teeIdByRealStepId.set(realSpanId, obsSpanId);
|
|
5568
|
+
exportedSpanIds.add(realSpanId);
|
|
4923
5569
|
}
|
|
4924
5570
|
}
|
|
4925
5571
|
return activeStep.run(obsSpanId, () => fn(realSpanId));
|
|
@@ -4979,6 +5625,9 @@ function createProjectionTee(real) {
|
|
|
4979
5625
|
realIdByTeeId.delete(spanId);
|
|
4980
5626
|
try {
|
|
4981
5627
|
real.endSpan(realId, endParams);
|
|
5628
|
+
if (realId != null && realId !== spanId) {
|
|
5629
|
+
exportedSpanIds.add(realId);
|
|
5630
|
+
}
|
|
4982
5631
|
} catch {
|
|
4983
5632
|
}
|
|
4984
5633
|
},
|
|
@@ -5008,7 +5657,32 @@ function createProjectionTee(real) {
|
|
|
5008
5657
|
}
|
|
5009
5658
|
},
|
|
5010
5659
|
async traceRetriever(query, fn) {
|
|
5011
|
-
|
|
5660
|
+
const obsSpanId = recordStart({
|
|
5661
|
+
step_name: "retrieve",
|
|
5662
|
+
observation_type: "RETRIEVER",
|
|
5663
|
+
input: { query }
|
|
5664
|
+
});
|
|
5665
|
+
const start = monotonicNowMs();
|
|
5666
|
+
try {
|
|
5667
|
+
const result = await real.traceRetriever(query, fn);
|
|
5668
|
+
const documents = parseRetrievedDocuments(result);
|
|
5669
|
+
complete(obsSpanId, {
|
|
5670
|
+
latency_ms: round3(monotonicNowMs() - start),
|
|
5671
|
+
// Recognized results are stored normalized so every reader sees one
|
|
5672
|
+
// typed shape. Anything else passes through raw, and the retrieval
|
|
5673
|
+
// capability derives "partial" from it.
|
|
5674
|
+
output: documents !== void 0 ? { documents } : result
|
|
5675
|
+
});
|
|
5676
|
+
return result;
|
|
5677
|
+
} catch (error) {
|
|
5678
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5679
|
+
complete(obsSpanId, {
|
|
5680
|
+
latency_ms: round3(monotonicNowMs() - start),
|
|
5681
|
+
status_message: message,
|
|
5682
|
+
level: "ERROR"
|
|
5683
|
+
});
|
|
5684
|
+
throw error;
|
|
5685
|
+
}
|
|
5012
5686
|
},
|
|
5013
5687
|
async traceChain(name, fn) {
|
|
5014
5688
|
return real.traceChain(name, fn);
|
|
@@ -5045,14 +5719,22 @@ function createProjectionTee(real) {
|
|
|
5045
5719
|
observations: [...observations]
|
|
5046
5720
|
};
|
|
5047
5721
|
},
|
|
5048
|
-
injectObservations(injected) {
|
|
5722
|
+
injectObservations(injected, opts) {
|
|
5049
5723
|
observations.push(...injected);
|
|
5724
|
+
if (opts?.remote !== false) {
|
|
5725
|
+
for (const observation of injected) {
|
|
5726
|
+
exportedSpanIds.add(observation.spanId);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5050
5729
|
observations.sort((a, b) => {
|
|
5051
5730
|
const at = a.startedAt ?? "";
|
|
5052
5731
|
const bt = b.startedAt ?? "";
|
|
5053
5732
|
if (at !== bt) return at < bt ? -1 : 1;
|
|
5054
5733
|
return a.spanId < b.spanId ? -1 : 1;
|
|
5055
5734
|
});
|
|
5735
|
+
},
|
|
5736
|
+
getExportedSpanIds() {
|
|
5737
|
+
return new Set(exportedSpanIds);
|
|
5056
5738
|
}
|
|
5057
5739
|
};
|
|
5058
5740
|
}
|
|
@@ -6064,6 +6746,11 @@ function resolveJudgeToolsForTask(runLevel, taskLevel, taskDir) {
|
|
|
6064
6746
|
if (!layered?.mcp) return layered;
|
|
6065
6747
|
return { mcp: resolveMcpServerPaths(layered.mcp, taskDir) };
|
|
6066
6748
|
}
|
|
6749
|
+
function buildEmbeddingRuntime(runLevel, taskLevel) {
|
|
6750
|
+
const provider = resolveEmbeddingConfig(runLevel, taskLevel);
|
|
6751
|
+
if (!provider) return void 0;
|
|
6752
|
+
return { provider, client: createEmbeddingClient() };
|
|
6753
|
+
}
|
|
6067
6754
|
async function runTask(taskDir, options) {
|
|
6068
6755
|
const loaded = options?.loaded ?? await loadTask(taskDir);
|
|
6069
6756
|
const trace2 = options?.tracing;
|
|
@@ -6081,19 +6768,19 @@ async function runTask(taskDir, options) {
|
|
|
6081
6768
|
const phase1 = await captureExecution(loaded, options, traceContext);
|
|
6082
6769
|
const canonical = await readCanonicalSnapshot(
|
|
6083
6770
|
trace2,
|
|
6084
|
-
phase1.
|
|
6771
|
+
phase1.exportedSpanIds
|
|
6085
6772
|
);
|
|
6086
6773
|
if (canonical) phase1.snapshot = canonical;
|
|
6087
6774
|
return evaluate(loaded, options, phase1, traceContext);
|
|
6088
6775
|
}
|
|
6089
6776
|
);
|
|
6090
6777
|
}
|
|
6091
|
-
async function readCanonicalSnapshot(trace2,
|
|
6778
|
+
async function readCanonicalSnapshot(trace2, exportedSpanIds, opts = {}) {
|
|
6092
6779
|
if (!trace2.taskRunId) return null;
|
|
6093
6780
|
const endpoint = process.env.AGENT_TASK_TRACE_ENDPOINT;
|
|
6094
6781
|
const authToken = process.env.APO_AUTH_TOKEN;
|
|
6095
6782
|
if (!endpoint || !authToken) return null;
|
|
6096
|
-
const deadlineMs = 3e4;
|
|
6783
|
+
const deadlineMs = opts.deadlineMs ?? 3e4;
|
|
6097
6784
|
const start = Date.now();
|
|
6098
6785
|
try {
|
|
6099
6786
|
let snapshot = await readTaskRunProjection({
|
|
@@ -6101,25 +6788,17 @@ async function readCanonicalSnapshot(trace2, minObservations) {
|
|
|
6101
6788
|
authToken,
|
|
6102
6789
|
taskRunId: trace2.taskRunId,
|
|
6103
6790
|
deadlineMs,
|
|
6104
|
-
// Fresh-run readback: the
|
|
6105
|
-
//
|
|
6106
|
-
// the checks phase starts — a 409 "no trace yet" must wait for the
|
|
6107
|
-
// claim, not fall back to a local snapshot that cannot see the
|
|
6108
|
-
// subprocess's spans.
|
|
6109
|
-
// Fresh-run readback: the run row was pre-created by the caller/executor
|
|
6110
|
-
// flow, and the subprocess-exported trace may still be in flight when
|
|
6111
|
-
// the checks phase starts — a 409 "no trace yet" must wait for the
|
|
6112
|
-
// claim, not fall back to a local snapshot that cannot see the
|
|
6113
|
-
// subprocess's spans.
|
|
6791
|
+
// Fresh-run readback: the trace claim may still be landing, so the
|
|
6792
|
+
// 409 must be waited out rather than fallen back from.
|
|
6114
6793
|
retryNoTrace: true
|
|
6115
6794
|
});
|
|
6116
|
-
while (
|
|
6795
|
+
while (!containsAllSpanIds(snapshot, exportedSpanIds)) {
|
|
6117
6796
|
const remaining = deadlineMs - (Date.now() - start);
|
|
6118
6797
|
if (remaining <= 0) {
|
|
6119
6798
|
console.error(
|
|
6120
|
-
`[AgentTask] Projection
|
|
6799
|
+
`[AgentTask] Projection missing ${countMissingSpanIds(snapshot, exportedSpanIds)}/${exportedSpanIds.size} spans the run exported after ${deadlineMs}ms; using the local snapshot`
|
|
6121
6800
|
);
|
|
6122
|
-
|
|
6801
|
+
return null;
|
|
6123
6802
|
}
|
|
6124
6803
|
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
6125
6804
|
snapshot = await readTaskRunProjection({
|
|
@@ -6138,6 +6817,27 @@ async function readCanonicalSnapshot(trace2, minObservations) {
|
|
|
6138
6817
|
return null;
|
|
6139
6818
|
}
|
|
6140
6819
|
}
|
|
6820
|
+
function containsAllSpanIds(snapshot, required) {
|
|
6821
|
+
if (required.size === 0) return true;
|
|
6822
|
+
const present = new Set(
|
|
6823
|
+
snapshot.observations.map((observation) => observation.spanId)
|
|
6824
|
+
);
|
|
6825
|
+
for (const id of required) {
|
|
6826
|
+
if (!present.has(id)) return false;
|
|
6827
|
+
}
|
|
6828
|
+
return true;
|
|
6829
|
+
}
|
|
6830
|
+
function countMissingSpanIds(snapshot, required) {
|
|
6831
|
+
if (required.size === 0) return 0;
|
|
6832
|
+
const present = new Set(
|
|
6833
|
+
snapshot.observations.map((observation) => observation.spanId)
|
|
6834
|
+
);
|
|
6835
|
+
let missing = 0;
|
|
6836
|
+
for (const id of required) {
|
|
6837
|
+
if (!present.has(id)) missing++;
|
|
6838
|
+
}
|
|
6839
|
+
return missing;
|
|
6840
|
+
}
|
|
6141
6841
|
async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
6142
6842
|
const {
|
|
6143
6843
|
task: task2,
|
|
@@ -6272,7 +6972,7 @@ async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
|
6272
6972
|
endpoint: process.env.AGENT_TASK_TRACE_ENDPOINT
|
|
6273
6973
|
})
|
|
6274
6974
|
);
|
|
6275
|
-
tee.injectObservations(replay.observations);
|
|
6975
|
+
tee.injectObservations(replay.observations, { remote: replay.exported });
|
|
6276
6976
|
}
|
|
6277
6977
|
return await continueWith({
|
|
6278
6978
|
collected,
|
|
@@ -6280,7 +6980,8 @@ async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
|
6280
6980
|
runConfiguration,
|
|
6281
6981
|
replay,
|
|
6282
6982
|
trace: trace2,
|
|
6283
|
-
getSnapshot: () => tee.getSnapshot()
|
|
6983
|
+
getSnapshot: () => tee.getSnapshot(),
|
|
6984
|
+
getExportedSpanIds: () => tee.getExportedSpanIds()
|
|
6284
6985
|
});
|
|
6285
6986
|
} catch (error) {
|
|
6286
6987
|
throw withRunConfiguration(error, runConfiguration);
|
|
@@ -6333,6 +7034,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6333
7034
|
async () => {
|
|
6334
7035
|
const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
|
|
6335
7036
|
const judgeTools = resolveJudgeToolsForTask(options?.judgeTools, task2.judgeTools, absoluteDir);
|
|
7037
|
+
const embeddingRuntime = buildEmbeddingRuntime(options?.embedding, task2.embedding);
|
|
6336
7038
|
const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
|
|
6337
7039
|
if (!inlineChecks) {
|
|
6338
7040
|
return loadAndRunFlowChecks(
|
|
@@ -6345,7 +7047,8 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6345
7047
|
...judgeConfig ? { judgeConfig } : {},
|
|
6346
7048
|
...judgeTools ? { judgeTools } : {},
|
|
6347
7049
|
judgeTracer: trace2,
|
|
6348
|
-
...historyPlane ? { historyPlane } : {}
|
|
7050
|
+
...historyPlane ? { historyPlane } : {},
|
|
7051
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {}
|
|
6349
7052
|
},
|
|
6350
7053
|
validationResults.brokenDeliverables
|
|
6351
7054
|
);
|
|
@@ -6362,6 +7065,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6362
7065
|
...judgeTools ? { judgeTools } : {},
|
|
6363
7066
|
judgeTracer: trace2,
|
|
6364
7067
|
...historyPlane ? { historyPlane } : {},
|
|
7068
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {},
|
|
6365
7069
|
moduleUrl,
|
|
6366
7070
|
displayFile: evalFileName
|
|
6367
7071
|
});
|
|
@@ -6385,11 +7089,9 @@ async function captureExecution(loaded, options, rawTrace) {
|
|
|
6385
7089
|
collected: outcome.collected,
|
|
6386
7090
|
transcriptTurns: outcome.transcriptTurns,
|
|
6387
7091
|
runConfiguration: outcome.runConfiguration,
|
|
6388
|
-
// Freeze the snapshot before the root span ends
|
|
7092
|
+
// Freeze the snapshot before the root span ends: Phase 2 reads it.
|
|
6389
7093
|
snapshot: outcome.getSnapshot(),
|
|
6390
|
-
|
|
6391
|
-
// holds (runner spans + replayed observations) should project remotely.
|
|
6392
|
-
...outcome.replay ? { replayObservationFloor: outcome.getSnapshot().observations.length } : {}
|
|
7094
|
+
exportedSpanIds: outcome.getExportedSpanIds()
|
|
6393
7095
|
}));
|
|
6394
7096
|
}
|
|
6395
7097
|
async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
@@ -6410,9 +7112,9 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6410
7112
|
);
|
|
6411
7113
|
const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
|
|
6412
7114
|
const judgeTools = resolveJudgeToolsForTask(options?.judgeTools, task2.judgeTools, absoluteDir);
|
|
7115
|
+
const embeddingRuntime = buildEmbeddingRuntime(options?.embedding, task2.embedding);
|
|
6413
7116
|
const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
|
|
6414
7117
|
const runChecks = async () => inlineChecks ? runTraceChecks({
|
|
6415
|
-
...judgeTools ? { judgeTools } : {},
|
|
6416
7118
|
snapshot: phase1.snapshot,
|
|
6417
7119
|
deliverables,
|
|
6418
7120
|
files,
|
|
@@ -6421,6 +7123,7 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6421
7123
|
...judgeTools ? { judgeTools } : {},
|
|
6422
7124
|
...judgeTracer ? { judgeTracer } : {},
|
|
6423
7125
|
...historyPlane ? { historyPlane } : {},
|
|
7126
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {},
|
|
6424
7127
|
moduleUrl,
|
|
6425
7128
|
displayFile: evalFileName
|
|
6426
7129
|
}) : loadAndRunFlowChecks(
|
|
@@ -6433,7 +7136,8 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6433
7136
|
...judgeConfig ? { judgeConfig } : {},
|
|
6434
7137
|
...judgeTools ? { judgeTools } : {},
|
|
6435
7138
|
...judgeTracer ? { judgeTracer } : {},
|
|
6436
|
-
...historyPlane ? { historyPlane } : {}
|
|
7139
|
+
...historyPlane ? { historyPlane } : {},
|
|
7140
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {}
|
|
6437
7141
|
},
|
|
6438
7142
|
validationResults.brokenDeliverables
|
|
6439
7143
|
);
|
|
@@ -6993,13 +7697,23 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
6993
7697
|
if (params.provider) {
|
|
6994
7698
|
span.setAttribute("gen_ai.provider.name", params.provider);
|
|
6995
7699
|
}
|
|
7700
|
+
if (params.api_provider) {
|
|
7701
|
+
span.setAttribute("apo.llm.api_provider", params.api_provider);
|
|
7702
|
+
}
|
|
7703
|
+
if (params.serving_host) {
|
|
7704
|
+
span.setAttribute("apo.llm.serving_host", params.serving_host);
|
|
7705
|
+
}
|
|
7706
|
+
if (params.observation_type === "GENERATION") {
|
|
7707
|
+
span.setAttribute("apo.llm.request_kind", "request");
|
|
7708
|
+
}
|
|
6996
7709
|
if (params.route) {
|
|
6997
7710
|
span.setAttribute("apo.llm.route", params.route);
|
|
6998
7711
|
}
|
|
6999
7712
|
const isTool = params.observation_type === "TOOL";
|
|
7713
|
+
const usesToolChannels = isTool || params.observation_type === "RETRIEVER";
|
|
7000
7714
|
if (params.input) {
|
|
7001
|
-
if (
|
|
7002
|
-
if (params.tool_name) {
|
|
7715
|
+
if (usesToolChannels) {
|
|
7716
|
+
if (isTool && params.tool_name) {
|
|
7003
7717
|
span.setAttribute("gen_ai.tool.name", params.tool_name);
|
|
7004
7718
|
}
|
|
7005
7719
|
span.setAttribute("gen_ai.tool.call.arguments", JSON.stringify(params.input));
|
|
@@ -7033,7 +7747,7 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7033
7747
|
const latency = params.latency_ms ?? round32(monotonicNowMs2() - active.startedAt);
|
|
7034
7748
|
active.span.setAttribute("latency_ms", latency);
|
|
7035
7749
|
if (params.output !== void 0) {
|
|
7036
|
-
if (active.observationType === "TOOL") {
|
|
7750
|
+
if (active.observationType === "TOOL" || active.observationType === "RETRIEVER") {
|
|
7037
7751
|
const payload = params.output && typeof params.output === "object" ? params.output : { value: params.output };
|
|
7038
7752
|
active.span.setAttribute("gen_ai.tool.call.result", JSON.stringify(payload));
|
|
7039
7753
|
} else if (params.output.text) {
|
|
@@ -7092,6 +7806,12 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7092
7806
|
if (params.provider) {
|
|
7093
7807
|
active.span.setAttribute("gen_ai.provider.name", params.provider);
|
|
7094
7808
|
}
|
|
7809
|
+
if (params.serving_host) {
|
|
7810
|
+
active.span.setAttribute("apo.llm.serving_host", params.serving_host);
|
|
7811
|
+
}
|
|
7812
|
+
if (params.api_provider) {
|
|
7813
|
+
active.span.setAttribute("apo.llm.api_provider", params.api_provider);
|
|
7814
|
+
}
|
|
7095
7815
|
if (params.route) {
|
|
7096
7816
|
active.span.setAttribute("apo.llm.route", params.route);
|
|
7097
7817
|
}
|
|
@@ -7128,6 +7848,9 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7128
7848
|
if (params.tags?.length) {
|
|
7129
7849
|
rootSpan.setAttribute("apo.run.tags", JSON.stringify(params.tags));
|
|
7130
7850
|
}
|
|
7851
|
+
if (params.environment) {
|
|
7852
|
+
rootSpan.setAttribute("apo.run.environment", params.environment);
|
|
7853
|
+
}
|
|
7131
7854
|
const rootMeta = params.rootSpan?.metadata;
|
|
7132
7855
|
const taskRunId = params.taskRunId ?? rootMeta?.["apo.task.run.id"] ?? params.run_metadata?.["agent_task_run_id"];
|
|
7133
7856
|
if (taskRunId) {
|
|
@@ -7423,15 +8146,55 @@ async function loadTaskRuntime(_taskDir) {
|
|
|
7423
8146
|
judge: resolveJudgeFromEnv(),
|
|
7424
8147
|
// Env/file layer for MCP evidence servers (APO_JUDGE_MCP) — seeds the
|
|
7425
8148
|
// run-level config, so explicit runTask({ judgeTools }) always wins.
|
|
7426
|
-
judgeTools: await resolveJudgeToolsFromEnv()
|
|
8149
|
+
judgeTools: await resolveJudgeToolsFromEnv(),
|
|
8150
|
+
embedding: resolveEmbeddingFromEnv()
|
|
7427
8151
|
};
|
|
7428
8152
|
}
|
|
8153
|
+
function resolveEmbeddingFromEnv() {
|
|
8154
|
+
const model = process.env.APO_EMBEDDING_MODEL?.trim();
|
|
8155
|
+
if (model) {
|
|
8156
|
+
return {
|
|
8157
|
+
model,
|
|
8158
|
+
...process.env.APO_EMBEDDING_BASE_URL ? { baseURL: process.env.APO_EMBEDDING_BASE_URL } : {},
|
|
8159
|
+
...process.env.APO_EMBEDDING_API_KEY ? { apiKey: process.env.APO_EMBEDDING_API_KEY } : {},
|
|
8160
|
+
...process.env.APO_EMBEDDING_DIMENSIONS ? { dimensions: parseDimensions(process.env.APO_EMBEDDING_DIMENSIONS) } : {}
|
|
8161
|
+
};
|
|
8162
|
+
}
|
|
8163
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
8164
|
+
return {
|
|
8165
|
+
model: "openai/text-embedding-3-small",
|
|
8166
|
+
// The endpoint is named here, never defaulted at request time — a
|
|
8167
|
+
// host is only ever contacted because a layer resolved it.
|
|
8168
|
+
baseURL: process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1",
|
|
8169
|
+
apiKey: process.env.OPENROUTER_API_KEY
|
|
8170
|
+
};
|
|
8171
|
+
}
|
|
8172
|
+
if (process.env.OPENAI_API_KEY) {
|
|
8173
|
+
return {
|
|
8174
|
+
model: "text-embedding-3-small",
|
|
8175
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
8176
|
+
baseURL: process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"
|
|
8177
|
+
};
|
|
8178
|
+
}
|
|
8179
|
+
return void 0;
|
|
8180
|
+
}
|
|
8181
|
+
function parseDimensions(raw) {
|
|
8182
|
+
const parsed = Number.parseInt(raw, 10);
|
|
8183
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
8184
|
+
}
|
|
7429
8185
|
function resolveJudgeFromEnv() {
|
|
7430
|
-
const
|
|
7431
|
-
const
|
|
7432
|
-
const
|
|
7433
|
-
const
|
|
8186
|
+
const judgeOverride = firstEnvValue("AGENT_TASK_JUDGE_MODEL");
|
|
8187
|
+
const openRouterModel = firstEnvValue("AGENT_TASK_OPENROUTER_MODEL", "OPENROUTER_MODEL");
|
|
8188
|
+
const openAIModel = firstEnvValue("OPENAI_MODEL");
|
|
8189
|
+
const anthropicModel = firstEnvValue("ANTHROPIC_MODEL", "AGENT_TASK_ANTHROPIC_MODEL");
|
|
8190
|
+
const model = judgeOverride ?? openRouterModel ?? openAIModel ?? anthropicModel;
|
|
7434
8191
|
if (!model) return void 0;
|
|
8192
|
+
if (judgeOverride && !openRouterModel && !openAIModel && !anthropicModel) {
|
|
8193
|
+
return {
|
|
8194
|
+
model,
|
|
8195
|
+
...process.env.APO_JUDGE_MODE?.trim() === "cascade" ? { mode: "cascade" } : {}
|
|
8196
|
+
};
|
|
8197
|
+
}
|
|
7435
8198
|
return {
|
|
7436
8199
|
model,
|
|
7437
8200
|
baseURL: openRouterModel ? process.env.OPENROUTER_BASE_URL : openAIModel ? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1" : process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
@@ -7452,6 +8215,13 @@ function resolveJudgeFromEnv() {
|
|
|
7452
8215
|
...process.env.APO_JUDGE_MODE?.trim() === "cascade" ? { mode: "cascade" } : {}
|
|
7453
8216
|
};
|
|
7454
8217
|
}
|
|
8218
|
+
function firstEnvValue(...names) {
|
|
8219
|
+
for (const name of names) {
|
|
8220
|
+
const value = process.env[name]?.trim();
|
|
8221
|
+
if (value) return value;
|
|
8222
|
+
}
|
|
8223
|
+
return void 0;
|
|
8224
|
+
}
|
|
7455
8225
|
async function runTaskDir(taskDir, options) {
|
|
7456
8226
|
const [loaded, runtime] = await Promise.all([
|
|
7457
8227
|
loadTask(taskDir),
|
|
@@ -7737,14 +8507,17 @@ export {
|
|
|
7737
8507
|
compactChecksForSubmission,
|
|
7738
8508
|
connectMcpServers,
|
|
7739
8509
|
contentSha256,
|
|
8510
|
+
cosineSimilarity,
|
|
7740
8511
|
createApoAnthropic,
|
|
7741
8512
|
createApoOpenAI,
|
|
7742
8513
|
createApoTracer,
|
|
8514
|
+
createEmbeddingClient,
|
|
7743
8515
|
defineAdapter,
|
|
7744
8516
|
defineTask,
|
|
7745
8517
|
describe2 as describe,
|
|
7746
8518
|
detectTranscriptSource,
|
|
7747
8519
|
discoverAgentTaskDirs,
|
|
8520
|
+
embeddingSettingsSignature,
|
|
7748
8521
|
equals,
|
|
7749
8522
|
evaluateComputeExpression,
|
|
7750
8523
|
expandSecretPlaceholders,
|
|
@@ -7776,6 +8549,7 @@ export {
|
|
|
7776
8549
|
resetTaskRegistry,
|
|
7777
8550
|
resetTaskSteers,
|
|
7778
8551
|
resetTaskTurn,
|
|
8552
|
+
resolveEmbeddingConfig,
|
|
7779
8553
|
resolveMcpServerPaths,
|
|
7780
8554
|
resolveOtlpTracesUrl,
|
|
7781
8555
|
resolveTurn,
|