oh-langfuse 0.1.51 → 0.1.53
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/bin/cli.js +2 -0
- package/langfuse_hook.py +1 -1
- package/package.json +1 -1
- package/scripts/auto-update-runtime.mjs +14 -2
- package/scripts/codex-langfuse-setup.mjs +3 -3
- package/scripts/langfuse-setup.mjs +3 -3
- package/scripts/opencode-langfuse-setup.mjs +30 -4
- package/scripts/real-self-verify.mjs +60 -17
package/bin/cli.js
CHANGED
|
@@ -830,6 +830,7 @@ async function main() {
|
|
|
830
830
|
noSetEnv: !!args["no-set-env"],
|
|
831
831
|
skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
|
|
832
832
|
skipCheck: !!args["skip-check"],
|
|
833
|
+
startupStatus: !!(args["startup-status"] || args.startupStatus),
|
|
833
834
|
cmd: args.cmd || "",
|
|
834
835
|
configOverrides: {
|
|
835
836
|
baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
|
|
@@ -870,6 +871,7 @@ async function main() {
|
|
|
870
871
|
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
871
872
|
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
872
873
|
...(options.skipCheck ? ["--skip-check"] : []),
|
|
874
|
+
...(options.startupStatus ? ["--startup-status"] : []),
|
|
873
875
|
...(options.yes ? ["--yes"] : []),
|
|
874
876
|
], { ...options, quiet: true });
|
|
875
877
|
}
|
package/langfuse_hook.py
CHANGED
package/package.json
CHANGED
|
@@ -93,6 +93,16 @@ function printUpdateCommand(target, updateCommand) {
|
|
|
93
93
|
console.log(`${paint("[CMD]", t.bold, t.cyan)} ${updateCommand}`);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function shouldPrintStartupStatus(args, env = process.env) {
|
|
97
|
+
const raw = String(env.OH_LANGFUSE_AUTO_UPDATE_STATUS || "").trim().toLowerCase();
|
|
98
|
+
return !!(args["startup-status"] || args.startupStatus || /^(1|true|yes|on)$/i.test(raw));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function printAlreadyCurrent(target, version) {
|
|
102
|
+
const label = targetLabel(target);
|
|
103
|
+
console.log(paint(`[OK] ${label} Langfuse \u5df2\u662f\u6700\u65b0\uff1a${packageJson.name}@${version}`, t.bold, t.green));
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
function runUpdate(target, args) {
|
|
97
107
|
const updateArgs = ["-y", "oh-langfuse@latest", "update", target];
|
|
98
108
|
if (args["skip-check"]) updateArgs.push("--skip-check");
|
|
@@ -134,8 +144,10 @@ async function main() {
|
|
|
134
144
|
const record = getRuntimeInstallRecord(target);
|
|
135
145
|
const installedVersion = record?.packageVersion || record?.version || "";
|
|
136
146
|
const needsUpdate = installedVersion ? isNewerVersion(latest, installedVersion) : isNewerVersion(latest, packageJson.version);
|
|
137
|
-
if (!needsUpdate
|
|
138
|
-
|
|
147
|
+
if (!needsUpdate) {
|
|
148
|
+
if (shouldPrintStartupStatus(args)) printAlreadyCurrent(target, installedVersion || packageJson.version);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
139
151
|
|
|
140
152
|
const message = installedVersion
|
|
141
153
|
? `oh-langfuse ${target} runtime update available: ${installedVersion} -> ${latest}.`
|
|
@@ -89,7 +89,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
89
89
|
const helperDir = autoUpdateHelperDir();
|
|
90
90
|
ensureDir(helperDir);
|
|
91
91
|
const runtimePath = autoUpdateRuntimePath();
|
|
92
|
-
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check"];
|
|
92
|
+
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
|
|
93
93
|
|
|
94
94
|
if (process.platform === "win32") {
|
|
95
95
|
const helper = path.join(helperDir, `oh-langfuse-auto-update-${target}.cmd`);
|
|
@@ -97,7 +97,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
97
97
|
"@echo off",
|
|
98
98
|
"REM Auto-generated by scripts/codex-langfuse-setup.mjs",
|
|
99
99
|
`if exist ${cmdQuote(runtimePath)} (`,
|
|
100
|
-
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check %*`,
|
|
100
|
+
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check --startup-status %*`,
|
|
101
101
|
") else (",
|
|
102
102
|
` call npx.cmd ${fallbackArgs.map(cmdQuote).join(" ")} %*`,
|
|
103
103
|
")",
|
|
@@ -114,7 +114,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
114
114
|
"# Auto-generated by scripts/codex-langfuse-setup.mjs",
|
|
115
115
|
"set +e",
|
|
116
116
|
`if [ -f ${shQuote(runtimePath)} ]; then`,
|
|
117
|
-
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check "$@"`,
|
|
117
|
+
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check --startup-status "$@"`,
|
|
118
118
|
"else",
|
|
119
119
|
` npx ${fallbackArgs.map(shQuote).join(" ")} "$@"`,
|
|
120
120
|
"fi",
|
|
@@ -161,7 +161,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
161
161
|
const helperDir = autoUpdateHelperDir();
|
|
162
162
|
ensureDir(helperDir);
|
|
163
163
|
const runtimePath = autoUpdateRuntimePath();
|
|
164
|
-
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check"];
|
|
164
|
+
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
|
|
165
165
|
|
|
166
166
|
if (process.platform === "win32") {
|
|
167
167
|
const helper = path.join(helperDir, `oh-langfuse-auto-update-${target}.cmd`);
|
|
@@ -169,7 +169,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
169
169
|
"@echo off",
|
|
170
170
|
"REM Auto-generated by scripts/langfuse-setup.mjs",
|
|
171
171
|
`if exist ${cmdQuote(runtimePath)} (`,
|
|
172
|
-
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check %*`,
|
|
172
|
+
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check --startup-status %*`,
|
|
173
173
|
") else (",
|
|
174
174
|
` call npx.cmd ${fallbackArgs.map(cmdQuote).join(" ")} %*`,
|
|
175
175
|
")",
|
|
@@ -186,7 +186,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
186
186
|
"# Auto-generated by scripts/langfuse-setup.mjs",
|
|
187
187
|
"set +e",
|
|
188
188
|
`if [ -f ${shQuote(runtimePath)} ]; then`,
|
|
189
|
-
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check "$@"`,
|
|
189
|
+
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check --startup-status "$@"`,
|
|
190
190
|
"else",
|
|
191
191
|
` npx ${fallbackArgs.map(shQuote).join(" ")} "$@"`,
|
|
192
192
|
"fi",
|
|
@@ -312,15 +312,17 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
312
312
|
"const skillAgentPath = (detectedBy) => {",
|
|
313
313
|
" if (detectedBy === 'tool_call') return 'opencode_skill_tool';",
|
|
314
314
|
" if (detectedBy === 'slash_command') return 'opencode_slash_prompt';",
|
|
315
|
+
" if (detectedBy === 'natural_language_request') return 'opencode_prompt_skill_name';",
|
|
315
316
|
" if (detectedBy === 'skill_file_path') return 'skill_file_path';",
|
|
316
317
|
" return detectedBy || 'metadata';",
|
|
317
318
|
"};",
|
|
318
319
|
"const skillInvocationMode = (detectedBy) => {",
|
|
319
320
|
" if (detectedBy === 'slash_command') return 'explicit_request';",
|
|
321
|
+
" if (detectedBy === 'natural_language_request') return 'implicit_request';",
|
|
320
322
|
" if (detectedBy === 'tool_call' || detectedBy === 'plugin_event') return 'implicit';",
|
|
321
323
|
" return 'detected';",
|
|
322
324
|
"};",
|
|
323
|
-
"const skillEventType = (detectedBy) => detectedBy === 'slash_command' ? 'requested' : (detectedBy === 'tool_call' || detectedBy === 'plugin_event' ? 'invoked' : 'detected');",
|
|
325
|
+
"const skillEventType = (detectedBy) => (detectedBy === 'slash_command' || detectedBy === 'natural_language_request') ? 'requested' : (detectedBy === 'tool_call' || detectedBy === 'plugin_event' ? 'invoked' : 'detected');",
|
|
324
326
|
"const skillIdSegment = (name) => String(name || 'unknown').trim().replace(/[^A-Za-z0-9_.:-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96) || 'unknown';",
|
|
325
327
|
"",
|
|
326
328
|
"const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');",
|
|
@@ -373,6 +375,18 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
373
375
|
" return out;",
|
|
374
376
|
"};",
|
|
375
377
|
"",
|
|
378
|
+
"const detectPromptSkillRequests = (haystack, skills) => {",
|
|
379
|
+
" const text = String(haystack || '');",
|
|
380
|
+
" if (!text.trim()) return [];",
|
|
381
|
+
" const out = [];",
|
|
382
|
+
" for (const skillName of skills) {",
|
|
383
|
+
" const escaped = escapeRegExp(skillName);",
|
|
384
|
+
" const pattern = new RegExp(`(?:请\\\\s*)?(?:使用|调用|启用|采用)\\\\s*[\\\"'“”‘’]?${escaped}(?=$|[\\\\s,,。;;::、])|\\\\b(?:use|invoke|run|apply)\\\\s+[\\\"']?${escaped}(?=$|[\\\\s,,。;;::])`, 'i');",
|
|
385
|
+
" if (pattern.test(text)) out.push({ name: skillName, skill_namespace: skillNamespace(skillName), detected_by: 'natural_language_request', skill_call_id: '' });",
|
|
386
|
+
" }",
|
|
387
|
+
" return out;",
|
|
388
|
+
"};",
|
|
389
|
+
"",
|
|
376
390
|
"const detectOpencodeSkillUsages = (source, knownSkills = []) => {",
|
|
377
391
|
" const skills = normalizeSkillNames(knownSkills);",
|
|
378
392
|
" if (skills.length === 0) return [];",
|
|
@@ -393,6 +407,12 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
393
407
|
" }",
|
|
394
408
|
" found.push(...explicit);",
|
|
395
409
|
" const haystack = collectStrings(source).join('\\n');",
|
|
410
|
+
" const promptSeen = new Set(explicit.map((usage) => usage.name));",
|
|
411
|
+
" for (const usage of detectPromptSkillRequests(haystack, skills)) {",
|
|
412
|
+
" if (promptSeen.has(usage.name)) continue;",
|
|
413
|
+
" promptSeen.add(usage.name);",
|
|
414
|
+
" found.push(usage);",
|
|
415
|
+
" }",
|
|
396
416
|
" const pathSeen = new Set();",
|
|
397
417
|
" for (const match of haystack.matchAll(/(?:^|[\"'\\s])(?:[A-Za-z]:)?[^\"'\\n\\r]*[\\\\/]+([^\\\\/\"'\\n\\r]+)[\\\\/]+SKILL\\.md(?=$|[\"'\\s])/gi)) {",
|
|
398
418
|
" const skillName = match[1];",
|
|
@@ -495,9 +515,11 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
495
515
|
" sdk.start();",
|
|
496
516
|
" const metricsTracer = trace.getTracer('oh-langfuse-opencode-metrics');",
|
|
497
517
|
" const knownSkillNames = await collectKnownSkillNames();",
|
|
518
|
+
" const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
|
|
498
519
|
" const messageTextById = new Map();",
|
|
499
520
|
" const skillUsagesByMessageId = new Map();",
|
|
500
521
|
" const skillUsagesBySessionId = new Map();",
|
|
522
|
+
" const startupSkillSessionIds = new Set();",
|
|
501
523
|
" const toolCallIdsByMessageId = new Map();",
|
|
502
524
|
" const toolCallIdsBySessionId = new Map();",
|
|
503
525
|
" const toolResultIdsByMessageId = new Map();",
|
|
@@ -566,6 +588,10 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
566
588
|
" const partType = part?.type ?? '';",
|
|
567
589
|
" const sessionId = pickEventString(part?.sessionID, part?.sessionId, payload?.sessionID, payload?.sessionId, payload?.session?.id, event?.sessionID, event?.sessionId);",
|
|
568
590
|
" const messageId = pickEventString(part?.messageID, part?.messageId, payload?.messageID, payload?.messageId, payload?.message?.id, event?.messageID, event?.messageId);",
|
|
591
|
+
" if (sessionId && startupSkillUsages.length && !startupSkillSessionIds.has(sessionId)) {",
|
|
592
|
+
" startupSkillSessionIds.add(sessionId);",
|
|
593
|
+
" rememberSkillUsages(skillUsagesBySessionId, sessionId, startupSkillUsages);",
|
|
594
|
+
" }",
|
|
569
595
|
" const skillDetectionSources = [part, payload, part?.state?.input, part?.input, payload?.input, part?.state?.metadata, payload?.metadata, part?.state?.file, part?.file];",
|
|
570
596
|
" const eventSkillUsages = detectOpencodeSkillUsages(skillDetectionSources, knownSkillNames);",
|
|
571
597
|
" rememberSkillUsages(skillUsagesByMessageId, messageId, eventSkillUsages);",
|
|
@@ -708,7 +734,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
708
734
|
const helperDir = autoUpdateHelperDir();
|
|
709
735
|
ensureDir(helperDir);
|
|
710
736
|
const runtimePath = autoUpdateRuntimePath();
|
|
711
|
-
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check"];
|
|
737
|
+
const fallbackArgs = ["-y", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
|
|
712
738
|
|
|
713
739
|
if (process.platform === "win32") {
|
|
714
740
|
const helper = path.join(helperDir, `oh-langfuse-auto-update-${target}.cmd`);
|
|
@@ -716,7 +742,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
716
742
|
"@echo off",
|
|
717
743
|
"REM Auto-generated by scripts/opencode-langfuse-setup.mjs",
|
|
718
744
|
`if exist ${cmdQuote(runtimePath)} (`,
|
|
719
|
-
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check %*`,
|
|
745
|
+
` call ${cmdQuote(process.execPath)} ${cmdQuote(runtimePath)} ${target} --skip-check --startup-status %*`,
|
|
720
746
|
") else (",
|
|
721
747
|
` call npx.cmd ${fallbackArgs.map(cmdQuote).join(" ")} %*`,
|
|
722
748
|
")",
|
|
@@ -733,7 +759,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
733
759
|
"# Auto-generated by scripts/opencode-langfuse-setup.mjs",
|
|
734
760
|
"set +e",
|
|
735
761
|
`if [ -f ${shQuote(runtimePath)} ]; then`,
|
|
736
|
-
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check "$@"`,
|
|
762
|
+
` ${shQuote(process.execPath)} ${shQuote(runtimePath)} ${shQuote(target)} --skip-check --startup-status "$@"`,
|
|
737
763
|
"else",
|
|
738
764
|
` npx ${fallbackArgs.map(shQuote).join(" ")} "$@"`,
|
|
739
765
|
"fi",
|
|
@@ -339,6 +339,12 @@ function hasMetadataKey(item, key) {
|
|
|
339
339
|
return metadataValue(item, key) !== undefined;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
function metricInteractionId(item, target) {
|
|
343
|
+
return target === "opencode"
|
|
344
|
+
? directMetadataValue(item, "interaction_id") || metadataValue(item, "interaction_id")
|
|
345
|
+
: metadataValue(item, "interaction_id");
|
|
346
|
+
}
|
|
347
|
+
|
|
342
348
|
function isAgentTurnObservation(item) {
|
|
343
349
|
if (item?.name === "Agent Turn" || metadataValue(item, "interaction_count") === 1 || metadataValue(item, "interaction_count") === "1") {
|
|
344
350
|
return true;
|
|
@@ -363,33 +369,76 @@ async function observationsForTrace(config, traceId, since) {
|
|
|
363
369
|
return fallback.filter((item) => item.traceId === traceId || item.trace_id === traceId);
|
|
364
370
|
}
|
|
365
371
|
|
|
366
|
-
|
|
372
|
+
function mergeMetricCandidates(items) {
|
|
373
|
+
const out = [];
|
|
374
|
+
const seen = new Set();
|
|
375
|
+
for (const item of items || []) {
|
|
376
|
+
if (!item || typeof item !== "object") continue;
|
|
377
|
+
const key = [idOf(item), item.name || "", item.traceId || item.trace_id || ""].join(":");
|
|
378
|
+
if (seen.has(key)) continue;
|
|
379
|
+
seen.add(key);
|
|
380
|
+
out.push(item);
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function recentMetricCandidates(config, since) {
|
|
386
|
+
const baseParams = { limit: 100, fromTimestamp: since.toISOString() };
|
|
387
|
+
const candidates = [];
|
|
388
|
+
for (const pathname of ["/traces"]) {
|
|
389
|
+
for (const params of [{ ...baseParams, userId: config.userId }, baseParams]) {
|
|
390
|
+
try {
|
|
391
|
+
candidates.push(...dataArray(await langfuseGetLenient(config, pathname, params)));
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (error.name === "AbortError" || error.status === 404) continue;
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return mergeMetricCandidates(candidates);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function verifyMetricObservations(config, found, { since, target, marker = "" }) {
|
|
367
402
|
const traceId = traceIdOfFound(found);
|
|
368
|
-
let observations = await observationsForTrace(config, traceId, since);
|
|
403
|
+
let observations = mergeMetricCandidates([...(await observationsForTrace(config, traceId, since)), found?.item]);
|
|
369
404
|
|
|
370
405
|
if (!observations.length && Array.isArray(found?.item?.observations)) {
|
|
371
|
-
observations = found.item.observations.filter((item) => typeof item === "object");
|
|
406
|
+
observations = mergeMetricCandidates(found.item.observations.filter((item) => typeof item === "object"));
|
|
407
|
+
} else if (Array.isArray(found?.item?.observations)) {
|
|
408
|
+
observations = mergeMetricCandidates([
|
|
409
|
+
...observations,
|
|
410
|
+
...found.item.observations.filter((item) => typeof item === "object"),
|
|
411
|
+
]);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let skillUseObservations = observations.filter((item) => item?.name === "Skill Use");
|
|
415
|
+
let interactions = observations.filter(isAgentTurnObservation);
|
|
416
|
+
|
|
417
|
+
if (!interactions.length) {
|
|
418
|
+
const recentRelated = (await recentMetricCandidates(config, since)).filter((item) => marker && containsMarker(item, marker));
|
|
419
|
+
observations = mergeMetricCandidates([...observations, ...recentRelated]);
|
|
420
|
+
skillUseObservations = observations.filter((item) => item?.name === "Skill Use");
|
|
421
|
+
interactions = observations.filter(isAgentTurnObservation);
|
|
372
422
|
}
|
|
373
423
|
|
|
374
|
-
const skillUseObservations = observations.filter((item) => item?.name === "Skill Use");
|
|
375
424
|
if (skillUseObservations.length) {
|
|
376
|
-
throw new Error(`Metric verification failed for ${target}: Skill Use observations should not be emitted
|
|
425
|
+
throw new Error(`Metric verification failed for ${target}: Skill Use observations should not be emitted as standalone metrics.`);
|
|
377
426
|
}
|
|
378
427
|
|
|
379
|
-
const interactions = observations.filter(isAgentTurnObservation);
|
|
380
428
|
if (!interactions.length) {
|
|
381
429
|
throw new Error(`Metric verification failed for ${target}: Agent Turn observation was not found for trace ${traceId || found.id}.`);
|
|
382
430
|
}
|
|
383
431
|
|
|
384
432
|
const byInteractionId = new Map();
|
|
433
|
+
const seenInteractionIds = new Set();
|
|
385
434
|
for (const item of interactions) {
|
|
386
|
-
const interactionId = target
|
|
387
|
-
? directMetadataValue(item, "interaction_id") || metadataValue(item, "interaction_id")
|
|
388
|
-
: metadataValue(item, "interaction_id");
|
|
435
|
+
const interactionId = metricInteractionId(item, target);
|
|
389
436
|
if (!interactionId) {
|
|
390
437
|
throw new Error(`Metric verification failed for ${target}: Agent Turn is missing interaction_id.`);
|
|
391
438
|
}
|
|
392
|
-
|
|
439
|
+
if (seenInteractionIds.has(interactionId)) continue;
|
|
440
|
+
seenInteractionIds.add(interactionId);
|
|
441
|
+
byInteractionId.set(interactionId, 1);
|
|
393
442
|
for (const key of ["user_id", "token_metrics_available", "tool_call_count", "skill_use_count", "metrics_schema_version"]) {
|
|
394
443
|
if (!hasMetadataKey(item, key)) {
|
|
395
444
|
throw new Error(`Metric verification failed for ${target}: Agent Turn is missing ${key}.`);
|
|
@@ -415,12 +464,6 @@ async function verifyMetricObservations(config, found, { since, target }) {
|
|
|
415
464
|
}
|
|
416
465
|
}
|
|
417
466
|
|
|
418
|
-
for (const [interactionId, count] of byInteractionId.entries()) {
|
|
419
|
-
if (count !== 1) {
|
|
420
|
-
throw new Error(`Metric verification failed for ${target}: interaction_id ${interactionId} appeared ${count} times.`);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
467
|
return { traceId, interactionCount: interactions.length };
|
|
425
468
|
}
|
|
426
469
|
|
|
@@ -558,7 +601,7 @@ async function main() {
|
|
|
558
601
|
|
|
559
602
|
const found = await pollLangfuse(config, marker, { ...args, since, target });
|
|
560
603
|
console.log(`[OK] Langfuse marker found for ${target}: ${found.kind} ${found.id || ""}`.trim());
|
|
561
|
-
const metrics = await verifyMetricObservations(config, found, { since, target });
|
|
604
|
+
const metrics = await verifyMetricObservations(config, found, { since, target, marker });
|
|
562
605
|
console.log(`[OK] Langfuse metrics found for ${target}: Agent Turn x${metrics.interactionCount}`);
|
|
563
606
|
results.push({ target, marker, langfuse: { kind: found.kind, id: found.id || "", traceId: metrics.traceId }, metrics });
|
|
564
607
|
}
|