@apo-ai/sdk 0.16.1 → 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 +367 -13
- package/dist/agent-task/public.js +832 -59
- 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-YWUTUQPJ.js → chunk-CSOY6OSP.js} +63 -13
- 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-YWUTUQPJ.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 }));
|
|
@@ -2860,7 +2922,7 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2860
2922
|
rec.record(
|
|
2861
2923
|
label,
|
|
2862
2924
|
false,
|
|
2863
|
-
"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.",
|
|
2864
2926
|
{ evaluator_type: "agent", location, outcome: "error" }
|
|
2865
2927
|
);
|
|
2866
2928
|
return;
|
|
@@ -2961,6 +3023,246 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2961
3023
|
};
|
|
2962
3024
|
}
|
|
2963
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
|
+
|
|
2964
3266
|
// src/agent-task/checks/t.ts
|
|
2965
3267
|
function resolveJudgeConfig(judgeConfig, override) {
|
|
2966
3268
|
const model = override?.model ?? judgeConfig?.model;
|
|
@@ -2982,6 +3284,8 @@ var TEST_METHOD_NAMES = [
|
|
|
2982
3284
|
"usedNoTools",
|
|
2983
3285
|
"maxToolCalls",
|
|
2984
3286
|
"noFailedActions",
|
|
3287
|
+
"retrieved",
|
|
3288
|
+
"maxRetrievals",
|
|
2985
3289
|
"loadedSkill",
|
|
2986
3290
|
"calledSubagent",
|
|
2987
3291
|
"messageIncludes",
|
|
@@ -2992,9 +3296,12 @@ var TEST_METHOD_NAMES = [
|
|
|
2992
3296
|
"steerDelivered",
|
|
2993
3297
|
"afterSteer",
|
|
2994
3298
|
"assert",
|
|
3299
|
+
"not",
|
|
2995
3300
|
"check",
|
|
2996
3301
|
"judge",
|
|
2997
|
-
"
|
|
3302
|
+
"semantic",
|
|
3303
|
+
"agent",
|
|
3304
|
+
"grounded"
|
|
2998
3305
|
];
|
|
2999
3306
|
function describeName(m) {
|
|
3000
3307
|
if (typeof m === "string") return `"${m}"`;
|
|
@@ -3075,7 +3382,7 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
3075
3382
|
rec.record(
|
|
3076
3383
|
label,
|
|
3077
3384
|
false,
|
|
3078
|
-
"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.",
|
|
3079
3386
|
// No judge means no verdict — the same bucket as an unreachable judge.
|
|
3080
3387
|
{ evaluator_type: "llm", location, outcome: "error" }
|
|
3081
3388
|
);
|
|
@@ -3184,7 +3491,10 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
3184
3491
|
text: res?.reasoning?.slice(0, 2e3) ?? "",
|
|
3185
3492
|
verdict: {
|
|
3186
3493
|
reasoning: res?.reasoning?.slice(0, 2e3) ?? null,
|
|
3187
|
-
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" } : {}
|
|
3188
3498
|
}
|
|
3189
3499
|
};
|
|
3190
3500
|
},
|
|
@@ -3255,6 +3565,143 @@ function judgeScopeToContext(scope) {
|
|
|
3255
3565
|
...deliverableNames && deliverableNames.length > 0 ? { deliverableNames } : {}
|
|
3256
3566
|
};
|
|
3257
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
|
+
}
|
|
3258
3705
|
var CAPABILITY_LABELS = {
|
|
3259
3706
|
messages: "message",
|
|
3260
3707
|
tools: "tool",
|
|
@@ -3262,9 +3709,10 @@ var CAPABILITY_LABELS = {
|
|
|
3262
3709
|
timing: "timing",
|
|
3263
3710
|
skills: "skill",
|
|
3264
3711
|
subagents: "subagent",
|
|
3265
|
-
usage: "token usage"
|
|
3712
|
+
usage: "token usage",
|
|
3713
|
+
retrieval: "retrieval"
|
|
3266
3714
|
};
|
|
3267
|
-
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer, judgeTools) {
|
|
3715
|
+
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer, judgeTools, embedding) {
|
|
3268
3716
|
const unsupported = (id, capability) => {
|
|
3269
3717
|
const label = CAPABILITY_LABELS[capability];
|
|
3270
3718
|
rec.record(id, false, `${label} evidence is unavailable in this trace projection`, {
|
|
@@ -3274,7 +3722,24 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3274
3722
|
});
|
|
3275
3723
|
};
|
|
3276
3724
|
const isAvailable = (c) => view.requireCapability(c) === "available";
|
|
3277
|
-
|
|
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 = {
|
|
3278
3743
|
calledTool(name, opts) {
|
|
3279
3744
|
if (!isAvailable("tools")) {
|
|
3280
3745
|
unsupported(`calledTool(${describeName(name)})`, "tools");
|
|
@@ -3372,6 +3837,51 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3372
3837
|
received: `${failed}`
|
|
3373
3838
|
});
|
|
3374
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
|
+
},
|
|
3375
3885
|
loadedSkill(skill) {
|
|
3376
3886
|
if (!isAvailable("skills")) {
|
|
3377
3887
|
unsupported(`loadedSkill("${skill}")`, "skills");
|
|
@@ -3459,7 +3969,8 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3459
3969
|
judgeScope,
|
|
3460
3970
|
agentEvidence,
|
|
3461
3971
|
judgeTracer,
|
|
3462
|
-
judgeTools
|
|
3972
|
+
judgeTools,
|
|
3973
|
+
embedding
|
|
3463
3974
|
)
|
|
3464
3975
|
),
|
|
3465
3976
|
check(value, matcher, label) {
|
|
@@ -3474,7 +3985,38 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
3474
3985
|
// judge does not consult trace capabilities; the scope carries the
|
|
3475
3986
|
// task/check frame for prompt builders (#161).
|
|
3476
3987
|
judge: trackPending(rec, createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer)),
|
|
3477
|
-
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
|
+
}
|
|
3478
4020
|
};
|
|
3479
4021
|
}
|
|
3480
4022
|
function requireBudget(method, n) {
|
|
@@ -3579,14 +4121,76 @@ function matchesTraceToolCall(call, name, opts) {
|
|
|
3579
4121
|
}
|
|
3580
4122
|
return true;
|
|
3581
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
|
+
}
|
|
3582
4169
|
|
|
3583
4170
|
// src/agent-task/checks/recorder.ts
|
|
3584
4171
|
var Recorder = class {
|
|
3585
4172
|
constructor(locate) {
|
|
3586
4173
|
this.records = [];
|
|
3587
4174
|
this.pending = [];
|
|
4175
|
+
this.negating = false;
|
|
3588
4176
|
this.locate = locate;
|
|
3589
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
|
+
}
|
|
3590
4194
|
/**
|
|
3591
4195
|
* Capture the call site location synchronously. Use this when a record will
|
|
3592
4196
|
* happen AFTER an `await` (e.g. `t.judge`): once an async function resumes,
|
|
@@ -3622,7 +4226,7 @@ var Recorder = class {
|
|
|
3622
4226
|
*/
|
|
3623
4227
|
record(id, pass, reasoning, extra) {
|
|
3624
4228
|
const location = extra?.location ?? (this.locate ? this.locate(new Error().stack ?? "") : void 0);
|
|
3625
|
-
|
|
4229
|
+
const result = {
|
|
3626
4230
|
id,
|
|
3627
4231
|
pass,
|
|
3628
4232
|
reasoning,
|
|
@@ -3631,13 +4235,29 @@ var Recorder = class {
|
|
|
3631
4235
|
received: extra?.received,
|
|
3632
4236
|
evaluator_type: extra?.evaluator_type,
|
|
3633
4237
|
judge: extra?.judge,
|
|
4238
|
+
embedding: extra?.embedding,
|
|
3634
4239
|
outcome: extra?.outcome
|
|
3635
|
-
}
|
|
4240
|
+
};
|
|
4241
|
+
this.records.push(this.negating ? negate(result) : result);
|
|
3636
4242
|
}
|
|
3637
4243
|
get all() {
|
|
3638
4244
|
return this.records;
|
|
3639
4245
|
}
|
|
3640
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
|
+
}
|
|
3641
4261
|
|
|
3642
4262
|
// src/agent-task/checks/compact.ts
|
|
3643
4263
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -3872,12 +4492,18 @@ async function runTraceChecks(args) {
|
|
|
3872
4492
|
const view = new TraceView(args.snapshot);
|
|
3873
4493
|
const locate = args.moduleUrl && args.displayFile ? (stack) => parseCheckLocation(stack, args.moduleUrl, args.displayFile) : void 0;
|
|
3874
4494
|
const taskMeta = readTaskMeta(args.task);
|
|
4495
|
+
const embedding = args.embedding ? { ...args.embedding, ...args.embedding.tracer ? {} : optionalTracer(args.judgeTracer) } : { client: createEmbeddingClient(), ...optionalTracer(args.judgeTracer) };
|
|
3875
4496
|
const results = await Promise.all(
|
|
3876
|
-
registry.map((check) => runOneCheck(check, args, view, taskMeta, locate))
|
|
4497
|
+
registry.map((check) => runOneCheck(check, args, embedding, view, taskMeta, locate))
|
|
3877
4498
|
);
|
|
3878
4499
|
return results;
|
|
3879
4500
|
}
|
|
3880
|
-
|
|
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) {
|
|
3881
4507
|
const runCheck = async () => {
|
|
3882
4508
|
const rec = new Recorder(locate);
|
|
3883
4509
|
const reads = trackDeliverableReads(args.deliverables);
|
|
@@ -3901,7 +4527,8 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3901
4527
|
...args.historyUnavailableReason ? { historyUnavailable: args.historyUnavailableReason } : {}
|
|
3902
4528
|
},
|
|
3903
4529
|
args.judgeTracer,
|
|
3904
|
-
args.judgeTools
|
|
4530
|
+
args.judgeTools,
|
|
4531
|
+
embedding
|
|
3905
4532
|
);
|
|
3906
4533
|
let thrownLocation;
|
|
3907
4534
|
try {
|
|
@@ -3921,9 +4548,11 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3921
4548
|
const pass = failed.length === 0;
|
|
3922
4549
|
const outcome = rollUpCheckOutcome(failed);
|
|
3923
4550
|
const judged = rec.all.filter(isJudgedAssertion);
|
|
3924
|
-
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";
|
|
3925
4553
|
const location = failed.find((r) => r.location)?.location;
|
|
3926
4554
|
const judge = rec.all.find((r) => r.judge)?.judge;
|
|
4555
|
+
const embeddingMeta = rec.all.find((r) => r.embedding)?.embedding;
|
|
3927
4556
|
return {
|
|
3928
4557
|
id: check.id,
|
|
3929
4558
|
pass,
|
|
@@ -3931,6 +4560,7 @@ async function runOneCheck(check, args, view, taskMeta, locate) {
|
|
|
3931
4560
|
evaluator_type: deriveEvaluatorType(rec.all),
|
|
3932
4561
|
...outcome ? { outcome } : {},
|
|
3933
4562
|
...judge ? { judge } : {},
|
|
4563
|
+
...embeddingMeta ? { embedding: embeddingMeta } : {},
|
|
3934
4564
|
...location ? { location } : {},
|
|
3935
4565
|
...args.displayFile ? { source_file: args.displayFile } : {},
|
|
3936
4566
|
...rec.all.length > 0 ? { assertions: rec.all.map((a) => ({ ...a })) } : {},
|
|
@@ -3960,19 +4590,27 @@ function isJudgedAssertion(assertion) {
|
|
|
3960
4590
|
function deriveEvaluatorType(assertions) {
|
|
3961
4591
|
let sawLlm = false;
|
|
3962
4592
|
let sawAgent = false;
|
|
4593
|
+
let sawEmbedding = false;
|
|
3963
4594
|
let sawDeterministic = false;
|
|
3964
4595
|
for (const assertion of assertions) {
|
|
3965
4596
|
if (isJudgedAssertion(assertion)) {
|
|
3966
4597
|
if (assertion.evaluator_type === "agent") sawAgent = true;
|
|
3967
4598
|
else sawLlm = true;
|
|
4599
|
+
} else if (assertion.evaluator_type === "embedding") {
|
|
4600
|
+
sawEmbedding = true;
|
|
3968
4601
|
} else {
|
|
3969
4602
|
sawDeterministic = true;
|
|
3970
4603
|
}
|
|
3971
4604
|
}
|
|
3972
|
-
|
|
3973
|
-
if (sawDeterministic) return "
|
|
3974
|
-
if (
|
|
3975
|
-
|
|
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";
|
|
3976
4614
|
}
|
|
3977
4615
|
function summarizeCheckForSpan(result) {
|
|
3978
4616
|
const check = result;
|
|
@@ -3981,6 +4619,7 @@ function summarizeCheckForSpan(result) {
|
|
|
3981
4619
|
text: reasoning,
|
|
3982
4620
|
verdict: {
|
|
3983
4621
|
pass: check.pass,
|
|
4622
|
+
...check.outcome ? { outcome: check.outcome } : {},
|
|
3984
4623
|
reasoning,
|
|
3985
4624
|
evaluator_type: check.evaluator_type,
|
|
3986
4625
|
...check.judge ? { judge_model: check.judge.model } : {},
|
|
@@ -4017,6 +4656,7 @@ async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
|
|
|
4017
4656
|
...args.judgeTools ? { judgeTools: args.judgeTools } : {},
|
|
4018
4657
|
...args.historyPlane ? { historyPlane: args.historyPlane } : {},
|
|
4019
4658
|
...args.judgeTracer ? { judgeTracer: args.judgeTracer } : {},
|
|
4659
|
+
...args.embedding ? { embedding: args.embedding } : {},
|
|
4020
4660
|
moduleUrl,
|
|
4021
4661
|
displayFile: basename(checksPath)
|
|
4022
4662
|
});
|
|
@@ -4817,6 +5457,7 @@ function deriveCapabilities(observations, hierarchyAmbiguous) {
|
|
|
4817
5457
|
const hasMessages = observations.some(
|
|
4818
5458
|
(o) => o.type === "GENERATION" && o.messages && o.messages.length > 0
|
|
4819
5459
|
);
|
|
5460
|
+
const retrievals = observations.filter((o) => o.type === "RETRIEVER");
|
|
4820
5461
|
return {
|
|
4821
5462
|
messages: hasMessages ? "available" : "unavailable",
|
|
4822
5463
|
tools: hasType("TOOL") ? "available" : "unavailable",
|
|
@@ -4829,7 +5470,8 @@ function deriveCapabilities(observations, hierarchyAmbiguous) {
|
|
|
4829
5470
|
// Usage is attributed to turns through parentSpanId; with an ambiguous
|
|
4830
5471
|
// step id the chain is broken, so a per-turn or per-run sum could
|
|
4831
5472
|
// silently drop turns.
|
|
4832
|
-
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"
|
|
4833
5475
|
};
|
|
4834
5476
|
}
|
|
4835
5477
|
function usageFrom(params) {
|
|
@@ -4845,6 +5487,7 @@ function createProjectionTee(real) {
|
|
|
4845
5487
|
const observations = [];
|
|
4846
5488
|
const pending = /* @__PURE__ */ new Map();
|
|
4847
5489
|
const realIdByTeeId = /* @__PURE__ */ new Map();
|
|
5490
|
+
const exportedSpanIds = /* @__PURE__ */ new Set();
|
|
4848
5491
|
const teeIdByRealStepId = /* @__PURE__ */ new Map();
|
|
4849
5492
|
const ambiguousRealStepIds = /* @__PURE__ */ new Set();
|
|
4850
5493
|
const activeStep = new AsyncLocalStorage();
|
|
@@ -4882,6 +5525,7 @@ function createProjectionTee(real) {
|
|
|
4882
5525
|
} else if (params?.output !== void 0) {
|
|
4883
5526
|
obs.output = params.output;
|
|
4884
5527
|
}
|
|
5528
|
+
if (p.type === "RETRIEVER" && p.input !== void 0) obs.input = p.input;
|
|
4885
5529
|
if (p.type === "GENERATION" && params?.output != null) {
|
|
4886
5530
|
const text = extractText(params.output);
|
|
4887
5531
|
if (text !== "") {
|
|
@@ -4921,6 +5565,7 @@ function createProjectionTee(real) {
|
|
|
4921
5565
|
ambiguousRealStepIds.add(realSpanId);
|
|
4922
5566
|
} else {
|
|
4923
5567
|
teeIdByRealStepId.set(realSpanId, obsSpanId);
|
|
5568
|
+
exportedSpanIds.add(realSpanId);
|
|
4924
5569
|
}
|
|
4925
5570
|
}
|
|
4926
5571
|
return activeStep.run(obsSpanId, () => fn(realSpanId));
|
|
@@ -4980,6 +5625,9 @@ function createProjectionTee(real) {
|
|
|
4980
5625
|
realIdByTeeId.delete(spanId);
|
|
4981
5626
|
try {
|
|
4982
5627
|
real.endSpan(realId, endParams);
|
|
5628
|
+
if (realId != null && realId !== spanId) {
|
|
5629
|
+
exportedSpanIds.add(realId);
|
|
5630
|
+
}
|
|
4983
5631
|
} catch {
|
|
4984
5632
|
}
|
|
4985
5633
|
},
|
|
@@ -5009,7 +5657,32 @@ function createProjectionTee(real) {
|
|
|
5009
5657
|
}
|
|
5010
5658
|
},
|
|
5011
5659
|
async traceRetriever(query, fn) {
|
|
5012
|
-
|
|
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
|
+
}
|
|
5013
5686
|
},
|
|
5014
5687
|
async traceChain(name, fn) {
|
|
5015
5688
|
return real.traceChain(name, fn);
|
|
@@ -5046,14 +5719,22 @@ function createProjectionTee(real) {
|
|
|
5046
5719
|
observations: [...observations]
|
|
5047
5720
|
};
|
|
5048
5721
|
},
|
|
5049
|
-
injectObservations(injected) {
|
|
5722
|
+
injectObservations(injected, opts) {
|
|
5050
5723
|
observations.push(...injected);
|
|
5724
|
+
if (opts?.remote !== false) {
|
|
5725
|
+
for (const observation of injected) {
|
|
5726
|
+
exportedSpanIds.add(observation.spanId);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5051
5729
|
observations.sort((a, b) => {
|
|
5052
5730
|
const at = a.startedAt ?? "";
|
|
5053
5731
|
const bt = b.startedAt ?? "";
|
|
5054
5732
|
if (at !== bt) return at < bt ? -1 : 1;
|
|
5055
5733
|
return a.spanId < b.spanId ? -1 : 1;
|
|
5056
5734
|
});
|
|
5735
|
+
},
|
|
5736
|
+
getExportedSpanIds() {
|
|
5737
|
+
return new Set(exportedSpanIds);
|
|
5057
5738
|
}
|
|
5058
5739
|
};
|
|
5059
5740
|
}
|
|
@@ -6065,6 +6746,11 @@ function resolveJudgeToolsForTask(runLevel, taskLevel, taskDir) {
|
|
|
6065
6746
|
if (!layered?.mcp) return layered;
|
|
6066
6747
|
return { mcp: resolveMcpServerPaths(layered.mcp, taskDir) };
|
|
6067
6748
|
}
|
|
6749
|
+
function buildEmbeddingRuntime(runLevel, taskLevel) {
|
|
6750
|
+
const provider = resolveEmbeddingConfig(runLevel, taskLevel);
|
|
6751
|
+
if (!provider) return void 0;
|
|
6752
|
+
return { provider, client: createEmbeddingClient() };
|
|
6753
|
+
}
|
|
6068
6754
|
async function runTask(taskDir, options) {
|
|
6069
6755
|
const loaded = options?.loaded ?? await loadTask(taskDir);
|
|
6070
6756
|
const trace2 = options?.tracing;
|
|
@@ -6082,19 +6768,19 @@ async function runTask(taskDir, options) {
|
|
|
6082
6768
|
const phase1 = await captureExecution(loaded, options, traceContext);
|
|
6083
6769
|
const canonical = await readCanonicalSnapshot(
|
|
6084
6770
|
trace2,
|
|
6085
|
-
phase1.
|
|
6771
|
+
phase1.exportedSpanIds
|
|
6086
6772
|
);
|
|
6087
6773
|
if (canonical) phase1.snapshot = canonical;
|
|
6088
6774
|
return evaluate(loaded, options, phase1, traceContext);
|
|
6089
6775
|
}
|
|
6090
6776
|
);
|
|
6091
6777
|
}
|
|
6092
|
-
async function readCanonicalSnapshot(trace2,
|
|
6778
|
+
async function readCanonicalSnapshot(trace2, exportedSpanIds, opts = {}) {
|
|
6093
6779
|
if (!trace2.taskRunId) return null;
|
|
6094
6780
|
const endpoint = process.env.AGENT_TASK_TRACE_ENDPOINT;
|
|
6095
6781
|
const authToken = process.env.APO_AUTH_TOKEN;
|
|
6096
6782
|
if (!endpoint || !authToken) return null;
|
|
6097
|
-
const deadlineMs = 3e4;
|
|
6783
|
+
const deadlineMs = opts.deadlineMs ?? 3e4;
|
|
6098
6784
|
const start = Date.now();
|
|
6099
6785
|
try {
|
|
6100
6786
|
let snapshot = await readTaskRunProjection({
|
|
@@ -6102,25 +6788,17 @@ async function readCanonicalSnapshot(trace2, minObservations) {
|
|
|
6102
6788
|
authToken,
|
|
6103
6789
|
taskRunId: trace2.taskRunId,
|
|
6104
6790
|
deadlineMs,
|
|
6105
|
-
// Fresh-run readback: the
|
|
6106
|
-
//
|
|
6107
|
-
// the checks phase starts — a 409 "no trace yet" must wait for the
|
|
6108
|
-
// claim, not fall back to a local snapshot that cannot see the
|
|
6109
|
-
// subprocess's spans.
|
|
6110
|
-
// Fresh-run readback: the run row was pre-created by the caller/executor
|
|
6111
|
-
// flow, and the subprocess-exported trace may still be in flight when
|
|
6112
|
-
// the checks phase starts — a 409 "no trace yet" must wait for the
|
|
6113
|
-
// claim, not fall back to a local snapshot that cannot see the
|
|
6114
|
-
// 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.
|
|
6115
6793
|
retryNoTrace: true
|
|
6116
6794
|
});
|
|
6117
|
-
while (
|
|
6795
|
+
while (!containsAllSpanIds(snapshot, exportedSpanIds)) {
|
|
6118
6796
|
const remaining = deadlineMs - (Date.now() - start);
|
|
6119
6797
|
if (remaining <= 0) {
|
|
6120
6798
|
console.error(
|
|
6121
|
-
`[AgentTask] Projection
|
|
6799
|
+
`[AgentTask] Projection missing ${countMissingSpanIds(snapshot, exportedSpanIds)}/${exportedSpanIds.size} spans the run exported after ${deadlineMs}ms; using the local snapshot`
|
|
6122
6800
|
);
|
|
6123
|
-
|
|
6801
|
+
return null;
|
|
6124
6802
|
}
|
|
6125
6803
|
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
6126
6804
|
snapshot = await readTaskRunProjection({
|
|
@@ -6139,6 +6817,27 @@ async function readCanonicalSnapshot(trace2, minObservations) {
|
|
|
6139
6817
|
return null;
|
|
6140
6818
|
}
|
|
6141
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
|
+
}
|
|
6142
6841
|
async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
6143
6842
|
const {
|
|
6144
6843
|
task: task2,
|
|
@@ -6273,7 +6972,7 @@ async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
|
6273
6972
|
endpoint: process.env.AGENT_TASK_TRACE_ENDPOINT
|
|
6274
6973
|
})
|
|
6275
6974
|
);
|
|
6276
|
-
tee.injectObservations(replay.observations);
|
|
6975
|
+
tee.injectObservations(replay.observations, { remote: replay.exported });
|
|
6277
6976
|
}
|
|
6278
6977
|
return await continueWith({
|
|
6279
6978
|
collected,
|
|
@@ -6281,7 +6980,8 @@ async function executeAdapterRun(loaded, options, rawTrace, continueWith) {
|
|
|
6281
6980
|
runConfiguration,
|
|
6282
6981
|
replay,
|
|
6283
6982
|
trace: trace2,
|
|
6284
|
-
getSnapshot: () => tee.getSnapshot()
|
|
6983
|
+
getSnapshot: () => tee.getSnapshot(),
|
|
6984
|
+
getExportedSpanIds: () => tee.getExportedSpanIds()
|
|
6285
6985
|
});
|
|
6286
6986
|
} catch (error) {
|
|
6287
6987
|
throw withRunConfiguration(error, runConfiguration);
|
|
@@ -6334,6 +7034,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6334
7034
|
async () => {
|
|
6335
7035
|
const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
|
|
6336
7036
|
const judgeTools = resolveJudgeToolsForTask(options?.judgeTools, task2.judgeTools, absoluteDir);
|
|
7037
|
+
const embeddingRuntime = buildEmbeddingRuntime(options?.embedding, task2.embedding);
|
|
6337
7038
|
const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
|
|
6338
7039
|
if (!inlineChecks) {
|
|
6339
7040
|
return loadAndRunFlowChecks(
|
|
@@ -6346,7 +7047,8 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6346
7047
|
...judgeConfig ? { judgeConfig } : {},
|
|
6347
7048
|
...judgeTools ? { judgeTools } : {},
|
|
6348
7049
|
judgeTracer: trace2,
|
|
6349
|
-
...historyPlane ? { historyPlane } : {}
|
|
7050
|
+
...historyPlane ? { historyPlane } : {},
|
|
7051
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {}
|
|
6350
7052
|
},
|
|
6351
7053
|
validationResults.brokenDeliverables
|
|
6352
7054
|
);
|
|
@@ -6363,6 +7065,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
|
|
|
6363
7065
|
...judgeTools ? { judgeTools } : {},
|
|
6364
7066
|
judgeTracer: trace2,
|
|
6365
7067
|
...historyPlane ? { historyPlane } : {},
|
|
7068
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {},
|
|
6366
7069
|
moduleUrl,
|
|
6367
7070
|
displayFile: evalFileName
|
|
6368
7071
|
});
|
|
@@ -6386,11 +7089,9 @@ async function captureExecution(loaded, options, rawTrace) {
|
|
|
6386
7089
|
collected: outcome.collected,
|
|
6387
7090
|
transcriptTurns: outcome.transcriptTurns,
|
|
6388
7091
|
runConfiguration: outcome.runConfiguration,
|
|
6389
|
-
// Freeze the snapshot before the root span ends
|
|
7092
|
+
// Freeze the snapshot before the root span ends: Phase 2 reads it.
|
|
6390
7093
|
snapshot: outcome.getSnapshot(),
|
|
6391
|
-
|
|
6392
|
-
// holds (runner spans + replayed observations) should project remotely.
|
|
6393
|
-
...outcome.replay ? { replayObservationFloor: outcome.getSnapshot().observations.length } : {}
|
|
7094
|
+
exportedSpanIds: outcome.getExportedSpanIds()
|
|
6394
7095
|
}));
|
|
6395
7096
|
}
|
|
6396
7097
|
async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
@@ -6411,9 +7112,9 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6411
7112
|
);
|
|
6412
7113
|
const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
|
|
6413
7114
|
const judgeTools = resolveJudgeToolsForTask(options?.judgeTools, task2.judgeTools, absoluteDir);
|
|
7115
|
+
const embeddingRuntime = buildEmbeddingRuntime(options?.embedding, task2.embedding);
|
|
6414
7116
|
const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
|
|
6415
7117
|
const runChecks = async () => inlineChecks ? runTraceChecks({
|
|
6416
|
-
...judgeTools ? { judgeTools } : {},
|
|
6417
7118
|
snapshot: phase1.snapshot,
|
|
6418
7119
|
deliverables,
|
|
6419
7120
|
files,
|
|
@@ -6422,6 +7123,7 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6422
7123
|
...judgeTools ? { judgeTools } : {},
|
|
6423
7124
|
...judgeTracer ? { judgeTracer } : {},
|
|
6424
7125
|
...historyPlane ? { historyPlane } : {},
|
|
7126
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {},
|
|
6425
7127
|
moduleUrl,
|
|
6426
7128
|
displayFile: evalFileName
|
|
6427
7129
|
}) : loadAndRunFlowChecks(
|
|
@@ -6434,7 +7136,8 @@ async function evaluate(loaded, options, phase1, judgeTracer) {
|
|
|
6434
7136
|
...judgeConfig ? { judgeConfig } : {},
|
|
6435
7137
|
...judgeTools ? { judgeTools } : {},
|
|
6436
7138
|
...judgeTracer ? { judgeTracer } : {},
|
|
6437
|
-
...historyPlane ? { historyPlane } : {}
|
|
7139
|
+
...historyPlane ? { historyPlane } : {},
|
|
7140
|
+
...embeddingRuntime ? { embedding: embeddingRuntime } : {}
|
|
6438
7141
|
},
|
|
6439
7142
|
validationResults.brokenDeliverables
|
|
6440
7143
|
);
|
|
@@ -6994,13 +7697,23 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
6994
7697
|
if (params.provider) {
|
|
6995
7698
|
span.setAttribute("gen_ai.provider.name", params.provider);
|
|
6996
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
|
+
}
|
|
6997
7709
|
if (params.route) {
|
|
6998
7710
|
span.setAttribute("apo.llm.route", params.route);
|
|
6999
7711
|
}
|
|
7000
7712
|
const isTool = params.observation_type === "TOOL";
|
|
7713
|
+
const usesToolChannels = isTool || params.observation_type === "RETRIEVER";
|
|
7001
7714
|
if (params.input) {
|
|
7002
|
-
if (
|
|
7003
|
-
if (params.tool_name) {
|
|
7715
|
+
if (usesToolChannels) {
|
|
7716
|
+
if (isTool && params.tool_name) {
|
|
7004
7717
|
span.setAttribute("gen_ai.tool.name", params.tool_name);
|
|
7005
7718
|
}
|
|
7006
7719
|
span.setAttribute("gen_ai.tool.call.arguments", JSON.stringify(params.input));
|
|
@@ -7034,7 +7747,7 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7034
7747
|
const latency = params.latency_ms ?? round32(monotonicNowMs2() - active.startedAt);
|
|
7035
7748
|
active.span.setAttribute("latency_ms", latency);
|
|
7036
7749
|
if (params.output !== void 0) {
|
|
7037
|
-
if (active.observationType === "TOOL") {
|
|
7750
|
+
if (active.observationType === "TOOL" || active.observationType === "RETRIEVER") {
|
|
7038
7751
|
const payload = params.output && typeof params.output === "object" ? params.output : { value: params.output };
|
|
7039
7752
|
active.span.setAttribute("gen_ai.tool.call.result", JSON.stringify(payload));
|
|
7040
7753
|
} else if (params.output.text) {
|
|
@@ -7093,6 +7806,12 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7093
7806
|
if (params.provider) {
|
|
7094
7807
|
active.span.setAttribute("gen_ai.provider.name", params.provider);
|
|
7095
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
|
+
}
|
|
7096
7815
|
if (params.route) {
|
|
7097
7816
|
active.span.setAttribute("apo.llm.route", params.route);
|
|
7098
7817
|
}
|
|
@@ -7129,6 +7848,9 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
7129
7848
|
if (params.tags?.length) {
|
|
7130
7849
|
rootSpan.setAttribute("apo.run.tags", JSON.stringify(params.tags));
|
|
7131
7850
|
}
|
|
7851
|
+
if (params.environment) {
|
|
7852
|
+
rootSpan.setAttribute("apo.run.environment", params.environment);
|
|
7853
|
+
}
|
|
7132
7854
|
const rootMeta = params.rootSpan?.metadata;
|
|
7133
7855
|
const taskRunId = params.taskRunId ?? rootMeta?.["apo.task.run.id"] ?? params.run_metadata?.["agent_task_run_id"];
|
|
7134
7856
|
if (taskRunId) {
|
|
@@ -7424,15 +8146,55 @@ async function loadTaskRuntime(_taskDir) {
|
|
|
7424
8146
|
judge: resolveJudgeFromEnv(),
|
|
7425
8147
|
// Env/file layer for MCP evidence servers (APO_JUDGE_MCP) — seeds the
|
|
7426
8148
|
// run-level config, so explicit runTask({ judgeTools }) always wins.
|
|
7427
|
-
judgeTools: await resolveJudgeToolsFromEnv()
|
|
8149
|
+
judgeTools: await resolveJudgeToolsFromEnv(),
|
|
8150
|
+
embedding: resolveEmbeddingFromEnv()
|
|
7428
8151
|
};
|
|
7429
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
|
+
}
|
|
7430
8185
|
function resolveJudgeFromEnv() {
|
|
7431
|
-
const
|
|
7432
|
-
const
|
|
7433
|
-
const
|
|
7434
|
-
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;
|
|
7435
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
|
+
}
|
|
7436
8198
|
return {
|
|
7437
8199
|
model,
|
|
7438
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",
|
|
@@ -7453,6 +8215,13 @@ function resolveJudgeFromEnv() {
|
|
|
7453
8215
|
...process.env.APO_JUDGE_MODE?.trim() === "cascade" ? { mode: "cascade" } : {}
|
|
7454
8216
|
};
|
|
7455
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
|
+
}
|
|
7456
8225
|
async function runTaskDir(taskDir, options) {
|
|
7457
8226
|
const [loaded, runtime] = await Promise.all([
|
|
7458
8227
|
loadTask(taskDir),
|
|
@@ -7738,14 +8507,17 @@ export {
|
|
|
7738
8507
|
compactChecksForSubmission,
|
|
7739
8508
|
connectMcpServers,
|
|
7740
8509
|
contentSha256,
|
|
8510
|
+
cosineSimilarity,
|
|
7741
8511
|
createApoAnthropic,
|
|
7742
8512
|
createApoOpenAI,
|
|
7743
8513
|
createApoTracer,
|
|
8514
|
+
createEmbeddingClient,
|
|
7744
8515
|
defineAdapter,
|
|
7745
8516
|
defineTask,
|
|
7746
8517
|
describe2 as describe,
|
|
7747
8518
|
detectTranscriptSource,
|
|
7748
8519
|
discoverAgentTaskDirs,
|
|
8520
|
+
embeddingSettingsSignature,
|
|
7749
8521
|
equals,
|
|
7750
8522
|
evaluateComputeExpression,
|
|
7751
8523
|
expandSecretPlaceholders,
|
|
@@ -7777,6 +8549,7 @@ export {
|
|
|
7777
8549
|
resetTaskRegistry,
|
|
7778
8550
|
resetTaskSteers,
|
|
7779
8551
|
resetTaskTurn,
|
|
8552
|
+
resolveEmbeddingConfig,
|
|
7780
8553
|
resolveMcpServerPaths,
|
|
7781
8554
|
resolveOtlpTracesUrl,
|
|
7782
8555
|
resolveTurn,
|