@librechat/agents 3.3.8 → 3.3.9
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/cjs/graphs/Graph.cjs +47 -13
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +35 -2
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +3 -2
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +18 -48
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +174 -29
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseConfig.cjs +12 -0
- package/dist/cjs/langfuseConfig.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeContext.cjs +23 -2
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeScope.cjs +39 -8
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +95 -0
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -0
- package/dist/cjs/langfuseTraceShaping.cjs +121 -4
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +39 -15
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +25 -5
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +3 -3
- package/dist/cjs/llm/invoke.cjs +5 -5
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/main.cjs +10 -10
- package/dist/cjs/messages/prune.cjs +13 -1
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +24 -12
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
- package/dist/cjs/run.cjs +47 -22
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/messageSerialization.cjs +6 -0
- package/dist/cjs/session/messageSerialization.cjs.map +1 -1
- package/dist/cjs/stream.cjs +21 -10
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +5 -0
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +253 -24
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +1 -1
- package/dist/cjs/tools/search/tool.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/cjs/utils/index.cjs +2 -2
- package/dist/esm/graphs/Graph.mjs +48 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +35 -2
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +3 -2
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +18 -48
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +176 -28
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseConfig.mjs +10 -1
- package/dist/esm/langfuseConfig.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeContext.mjs +21 -3
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeScope.mjs +39 -10
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +91 -0
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -0
- package/dist/esm/langfuseTraceShaping.mjs +121 -4
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +39 -15
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +25 -5
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +2 -2
- package/dist/esm/llm/invoke.mjs +5 -5
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/main.mjs +8 -8
- package/dist/esm/messages/prune.mjs +13 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +24 -12
- package/dist/esm/prompts/activityLabel.mjs.map +1 -1
- package/dist/esm/run.mjs +47 -22
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/messageSerialization.mjs +6 -0
- package/dist/esm/session/messageSerialization.mjs.map +1 -1
- package/dist/esm/stream.mjs +21 -10
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +5 -0
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +254 -25
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -1
- package/dist/esm/tools/search/tool.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/esm/utils/index.mjs +2 -2
- package/dist/types/graphs/Graph.d.ts +19 -0
- package/dist/types/hitl/askUserQuestion.d.ts +11 -1
- package/dist/types/langfuse.d.ts +16 -8
- package/dist/types/langfuseConfig.d.ts +6 -0
- package/dist/types/langfuseRuntimeContext.d.ts +27 -1
- package/dist/types/langfuseRuntimeScope.d.ts +17 -2
- package/dist/types/langfuseSpanRegistry.d.ts +17 -0
- package/dist/types/langfuseTraceShaping.d.ts +2 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -0
- package/dist/types/session/types.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +7 -1
- package/dist/types/types/hitl.d.ts +8 -0
- package/dist/types/types/tools.d.ts +30 -0
- package/package.json +7 -4
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +753 -0
- package/src/graphs/Graph.ts +69 -20
- package/src/graphs/MultiAgentGraph.ts +56 -2
- package/src/graphs/__tests__/composition.smoke.test.ts +4 -0
- package/src/hitl/askUserQuestion.ts +14 -1
- package/src/instrumentation.ts +35 -77
- package/src/langfuse.ts +320 -43
- package/src/langfuseConfig.ts +24 -0
- package/src/langfuseRuntimeContext.ts +43 -1
- package/src/langfuseRuntimeScope.ts +94 -21
- package/src/langfuseSpanRegistry.ts +131 -0
- package/src/langfuseTraceShaping.ts +194 -7
- package/src/llm/anthropic/utils/message_inputs.ts +70 -19
- package/src/llm/anthropic/utils/streaming-tool-input.test.ts +186 -11
- package/src/llm/bedrock/utils/message_inputs.test.ts +120 -4
- package/src/llm/bedrock/utils/message_inputs.ts +32 -7
- package/src/messages/prune.ts +12 -1
- package/src/prompts/activityLabel.ts +23 -6
- package/src/run.ts +76 -45
- package/src/scripts/activity-labels/captured.json +56 -0
- package/src/scripts/activity-labels/checks.cjs +205 -0
- package/src/scripts/activity-labels/corpus.cjs +473 -0
- package/src/scripts/activity-labels/report.cjs +203 -0
- package/src/scripts/activity-labels/rescore.cjs +102 -0
- package/src/scripts/activity-labels/run.ts +705 -0
- package/src/scripts/activity-labels/variants.ts +71 -0
- package/src/session/messageSerialization.ts +12 -1
- package/src/session/types.ts +1 -0
- package/src/specs/activity-label-prompt.test.ts +26 -10
- package/src/specs/agent-handoffs.test.ts +306 -0
- package/src/specs/langfuse-callbacks.test.ts +456 -0
- package/src/specs/langfuse-routing.integration.test.ts +138 -1
- package/src/specs/langfuse-span-registry.test.ts +70 -0
- package/src/specs/langfuse-trace-shaping.test.ts +294 -0
- package/src/specs/prune.test.ts +38 -1
- package/src/stream.ts +70 -6
- package/src/summarization/node.ts +5 -0
- package/src/tools/ToolNode.ts +400 -9
- package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +757 -0
- package/src/tools/__tests__/hitl.test.ts +58 -0
- package/src/types/hitl.ts +8 -0
- package/src/types/tools.ts +35 -1
|
@@ -31,8 +31,8 @@ function truncateForLabel(value, maxLength) {
|
|
|
31
31
|
* Reduces a committed label to bounded single-line data.
|
|
32
32
|
*
|
|
33
33
|
* Sections in this prompt are delimited by blank lines, so a label carrying
|
|
34
|
-
* embedded newlines could otherwise forge an apparent
|
|
35
|
-
* `
|
|
34
|
+
* embedded newlines could otherwise forge an apparent entries section or
|
|
35
|
+
* `Header:` cue. Unlike every other input here, previous labels re-enter
|
|
36
36
|
* the prompt on EVERY later batch, so one malformed result — plain model
|
|
37
37
|
* noncompliance, or injection surfacing through a tool result — would
|
|
38
38
|
* persistently steer unrelated later labels rather than affecting one. The
|
|
@@ -109,17 +109,29 @@ function buildActivityLabelPrompt({ entries, charLimit, thinkingExcerpts, lastAs
|
|
|
109
109
|
if (entries.length > 0) {
|
|
110
110
|
const shown = entries.slice(0, MAX_PROMPT_ENTRIES);
|
|
111
111
|
const omitted = entries.length - shown.length;
|
|
112
|
-
sections.push(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
sections.push(
|
|
113
|
+
/** Frames the list as reference material, not the thing to
|
|
114
|
+
* transcribe. Ported from LibreChat's fallback builder (its
|
|
115
|
+
* runtime.ts documents that without this the model "hands back a
|
|
116
|
+
* transcription" of the list) after the eval harness measured it
|
|
117
|
+
* across three independent sweeps: fewer template-redundancy and
|
|
118
|
+
* length violations than a bare `Tool calls:` heading, with no
|
|
119
|
+
* per-case regressions (agents #360). */
|
|
120
|
+
"What it called, and what came back (do not restate these):\n" + shown.map((entry) => {
|
|
121
|
+
const input = clip(serializeForLabel(entry.toolInput, charLimit), charLimit);
|
|
122
|
+
const redacted = redaction != null && shouldRedactTool(entry.toolName, redaction);
|
|
123
|
+
let outcome;
|
|
124
|
+
if (redacted) outcome = redaction.redactionText;
|
|
125
|
+
else if (entry.status === "error") outcome = `ERROR: ${clip(entry.error ?? "unknown error", charLimit)}`;
|
|
126
|
+
else outcome = clip(serializeForLabel(entry.toolOutput, charLimit), charLimit);
|
|
127
|
+
return `- ${entry.toolName}(${input}) → ${outcome}`;
|
|
128
|
+
}).join("\n") + (omitted > 0 ? `\n- …and ${omitted} more tool ${omitted === 1 ? "call" : "calls"}` : "")
|
|
129
|
+
);
|
|
121
130
|
}
|
|
122
|
-
|
|
131
|
+
/** The fallback builder's terminal cue, measured alongside the heading
|
|
132
|
+
* (same sweeps). The default system prompt already describes the
|
|
133
|
+
* output as "the header of a collapsed activity group". */
|
|
134
|
+
sections.push("Header:");
|
|
123
135
|
return sections.join("\n\n");
|
|
124
136
|
}
|
|
125
137
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activityLabel.mjs","names":[],"sources":["../../../src/prompts/activityLabel.ts"],"sourcesContent":["import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type { ActivityLabelToolEntry } from '@/types/activityLabel';\nimport { shouldRedactTool } from '@/langfuseToolOutputTracing';\n\n/**\n * Default system prompt for fast-model activity labeling.\n *\n * Style synthesized from Claude Code's tool-use summary prompt (git-subject\n * register, past tense, distinctive nouns) and claude.ai's observed group\n * headers (5–9 words describing a mixed reasoning + tool block, e.g.\n * \"Synthesized version data and curated comparative framework\").\n */\nexport const ACTIVITY_LABEL_PROMPT = `Write a short label describing what this block of agent activity accomplished. It appears as the header of a collapsed activity group in a chat UI.\n\nRules:\n- 5 to 9 words, past-tense verb first\n- Name the most distinctive subject (file, API, topic); drop articles and filler\n- Describe outcomes, not mechanics; if something failed, say so plainly\n- Output only the label — no quotes, no punctuation at the end, no preamble\n\nExamples:\n- Searched Node.js release notes and changelogs\n- Compared runtime versions across official sources\n- Fixed failing auth middleware tests\n- Read project config and dependency manifests\n- Attempted database migration, hit permission errors`;\n\n/** Truncates a serialized value for the label prompt. */\nexport function truncateForLabel(value: string, maxLength: number): string {\n if (value.length <= maxLength) {\n return value;\n }\n return value.slice(0, Math.max(0, maxLength - 1)) + '…';\n}\n\n/**\n * Reduces a committed label to bounded single-line data.\n *\n * Sections in this prompt are delimited by blank lines, so a label carrying\n * embedded newlines could otherwise forge an apparent `Tool calls:` or\n * `Label:` section. Unlike every other input here, previous labels re-enter\n * the prompt on EVERY later batch, so one malformed result — plain model\n * noncompliance, or injection surfacing through a tool result — would\n * persistently steer unrelated later labels rather than affecting one. The\n * clip bounds the same way `lastAssistantText` and reasoning excerpts are\n * bounded: oversized headers must not inflate later requests past the fast\n * model's window and starve the run of labels entirely.\n */\nfunction sanitizePreviousLabel(label: string): string {\n return truncateForLabel(label.replace(/\\s+/g, ' ').trim(), PREVIOUS_LABEL_LIMIT);\n}\n\nconst ABORT_SERIALIZATION = Symbol('abort-label-serialization');\n\n/**\n * Serializes a tool value for the prompt WITHOUT materializing huge JSON:\n * the output is clipped to a few hundred characters anyway, so a multi-\n * megabyte tool result must not be stringified in full on the label path.\n * Strings clip immediately; structured values serialize under a character\n * budget and degrade to a shape summary once it is exhausted.\n */\nfunction serializeForLabel(value: unknown, limit: number): string {\n if (value == null) {\n return '';\n }\n if (typeof value === 'string') {\n return value.length > limit ? value.slice(0, limit + 1) : value;\n }\n let budget = limit * 4;\n try {\n return (\n JSON.stringify(value, (_key, nested: unknown) => {\n if (budget <= 0) {\n throw ABORT_SERIALIZATION;\n }\n if (typeof nested === 'string') {\n const clipped =\n nested.length > limit ? nested.slice(0, limit) : nested;\n budget -= clipped.length;\n return clipped;\n }\n budget -= 8;\n return nested;\n }) ?? ''\n );\n } catch (error) {\n if (error === ABORT_SERIALIZATION) {\n return Array.isArray(value) ? `[Array(${value.length})]` : '[Object]';\n }\n return String(value);\n }\n}\n\nconst INPUT_CONTEXT_LIMIT = 200;\nconst MAX_THINKING_EXCERPTS = 4;\nconst MAX_PREVIOUS_LABELS = 3;\n/** Per-label bound. A header is 5-9 words; anything past this is\n * noncompliance or payload, and previous labels are the one input that\n * RE-ENTERS the prompt on every later batch of the run. */\nconst PREVIOUS_LABEL_LIMIT = 200;\n/** A label is 5-9 words; no batch needs more than this many entries to\n * produce one, and the cap keeps a 200-call programmatic batch from\n * building an enormous prompt out of per-field-bounded pieces. */\nconst MAX_PROMPT_ENTRIES = 12;\n\nexport type BuildActivityLabelPromptParams = {\n entries: ActivityLabelToolEntry[];\n charLimit: number;\n thinkingExcerpts?: string[];\n lastAssistantText?: string;\n /**\n * Headers already committed for earlier batches in this run, in run order\n * with the most recent last. Rendered ahead of the block context so the\n * label continues the run's story instead of restating a line the user is\n * already reading. Capped at {@link MAX_PREVIOUS_LABELS}.\n */\n previousLabels?: string[];\n /**\n * Resolved tool-output tracing policy. The label prompt becomes Langfuse\n * generation input, so outputs/errors excluded from tracing (global\n * disable or `redactedToolNames`) must never appear in it — the same\n * redaction the span processor applies to structured tool observations.\n */\n redaction?: ResolvedLangfuseToolOutputTracingConfig;\n};\n\n/**\n * Builds the user prompt for a fast-model activity label. Pure — exported\n * for direct testing of redaction and truncation behavior.\n */\nexport function buildActivityLabelPrompt({\n entries,\n charLimit,\n thinkingExcerpts,\n lastAssistantText,\n previousLabels,\n redaction,\n}: BuildActivityLabelPromptParams): string {\n const clip = truncateForLabel;\n /** Reasoning and intent text can quote tool output verbatim — including\n * output from EARLIER calls to a redacted tool that this batch does not\n * contain — so any active policy (global disable or a configured\n * redacted-name list) drops both wholesale. There is no reliable way to\n * scrub a quoted fragment out of free-form model prose. */\n const excerptsRedacted =\n redaction != null &&\n (redaction.enabled === false || redaction.redactedToolNames.size > 0);\n const sections: string[] = [];\n /** Previous labels are free-form model prose too, and per-agent overlays\n * mean an earlier header may have been generated under ANOTHER agent's\n * weaker policy — so they share the excerpts' wholesale drop rather than\n * letting a handoff leak a looser agent's phrasing into this trace. */\n if (!excerptsRedacted && previousLabels != null && previousLabels.length > 0) {\n const recent = previousLabels\n .slice(-MAX_PREVIOUS_LABELS)\n .map(sanitizePreviousLabel)\n /** A label that sanitizes to nothing carries no story to continue;\n * rendering it would leave a bare bullet implying a missing header. */\n .filter((label) => label.length > 0);\n if (recent.length > 0) {\n sections.push(\n 'Previous headers in this run (most recent last):\\n' +\n recent.map((label) => `- ${label}`).join('\\n')\n );\n }\n }\n /** Intent text is free-form assistant prose that can quote a redacted\n * tool result just as reasoning can, so it shares the excerpts' fate. */\n if (\n !excerptsRedacted &&\n lastAssistantText != null &&\n lastAssistantText.length > 0\n ) {\n sections.push(\n `Intent (assistant's last message): ${clip(lastAssistantText, INPUT_CONTEXT_LIMIT)}`\n );\n }\n if (\n !excerptsRedacted &&\n thinkingExcerpts != null &&\n thinkingExcerpts.length > 0\n ) {\n sections.push(\n 'Reasoning excerpts:\\n' +\n thinkingExcerpts\n .slice(0, MAX_THINKING_EXCERPTS)\n .map((excerpt) => `- ${clip(excerpt, charLimit)}`)\n .join('\\n')\n );\n }\n if (entries.length > 0) {\n const shown = entries.slice(0, MAX_PROMPT_ENTRIES);\n const omitted = entries.length - shown.length;\n sections.push(\n 'Tool calls:\\n' +\n shown\n .map((entry) => {\n const input = clip(\n serializeForLabel(entry.toolInput, charLimit),\n charLimit\n );\n const redacted =\n redaction != null && shouldRedactTool(entry.toolName, redaction);\n let outcome: string;\n if (redacted) {\n outcome = redaction.redactionText;\n } else if (entry.status === 'error') {\n outcome = `ERROR: ${clip(entry.error ?? 'unknown error', charLimit)}`;\n } else {\n outcome = clip(\n serializeForLabel(entry.toolOutput, charLimit),\n charLimit\n );\n }\n return `- ${entry.toolName}(${input}) → ${outcome}`;\n })\n .join('\\n') +\n (omitted > 0\n ? `\\n- …and ${omitted} more tool ${omitted === 1 ? 'call' : 'calls'}`\n : '')\n );\n }\n sections.push('Label:');\n return sections.join('\\n\\n');\n}\n"],"mappings":";;;;;;;;;;AAYA,MAAa,wBAAwB;;;;;;;;;;;;;;;AAgBrC,SAAgB,iBAAiB,OAAe,WAA2B;CACzE,IAAI,MAAM,UAAU,WAClB,OAAO;CAET,OAAO,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC,CAAC,IAAI;AACtD;;;;;;;;;;;;;;AAeA,SAAS,sBAAsB,OAAuB;CACpD,OAAO,iBAAiB,MAAM,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,oBAAoB;AACjF;AAEA,MAAM,sBAAsB,OAAO,2BAA2B;;;;;;;;AAS9D,SAAS,kBAAkB,OAAgB,OAAuB;CAChE,IAAI,SAAS,MACX,OAAO;CAET,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,SAAS,QAAQ,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI;CAE5D,IAAI,SAAS,QAAQ;CACrB,IAAI;EACF,OACE,KAAK,UAAU,QAAQ,MAAM,WAAoB;GAC/C,IAAI,UAAU,GACZ,MAAM;GAER,IAAI,OAAO,WAAW,UAAU;IAC9B,MAAM,UACJ,OAAO,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,IAAI;IACnD,UAAU,QAAQ;IAClB,OAAO;GACT;GACA,UAAU;GACV,OAAO;EACT,CAAC,KAAK;CAEV,SAAS,OAAO;EACd,IAAI,UAAU,qBACZ,OAAO,MAAM,QAAQ,KAAK,IAAI,UAAU,MAAM,OAAO,MAAM;EAE7D,OAAO,OAAO,KAAK;CACrB;AACF;AAEA,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;;;;AAK9B,MAAM,uBAAuB;;;;AAI7B,MAAM,qBAAqB;;;;;AA2B3B,SAAgB,yBAAyB,EACvC,SACA,WACA,kBACA,mBACA,gBACA,aACyC;CACzC,MAAM,OAAO;;;;;;CAMb,MAAM,mBACJ,aAAa,SACZ,UAAU,YAAY,SAAS,UAAU,kBAAkB,OAAO;CACrE,MAAM,WAAqB,CAAC;;;;;CAK5B,IAAI,CAAC,oBAAoB,kBAAkB,QAAQ,eAAe,SAAS,GAAG;EAC5E,MAAM,SAAS,eACZ,MAAM,EAAoB,CAAC,CAC3B,IAAI,qBAAqB,CAAC,CAG1B,QAAQ,UAAU,MAAM,SAAS,CAAC;EACrC,IAAI,OAAO,SAAS,GAClB,SAAS,KACP,uDACE,OAAO,KAAK,UAAU,KAAK,OAAO,CAAC,CAAC,KAAK,IAAI,CACjD;CAEJ;;;CAGA,IACE,CAAC,oBACD,qBAAqB,QACrB,kBAAkB,SAAS,GAE3B,SAAS,KACP,sCAAsC,KAAK,mBAAmB,mBAAmB,GACnF;CAEF,IACE,CAAC,oBACD,oBAAoB,QACpB,iBAAiB,SAAS,GAE1B,SAAS,KACP,0BACE,iBACG,MAAM,GAAG,qBAAqB,CAAC,CAC/B,KAAK,YAAY,KAAK,KAAK,SAAS,SAAS,GAAG,CAAC,CACjD,KAAK,IAAI,CAChB;CAEF,IAAI,QAAQ,SAAS,GAAG;EACtB,MAAM,QAAQ,QAAQ,MAAM,GAAG,kBAAkB;EACjD,MAAM,UAAU,QAAQ,SAAS,MAAM;EACvC,SAAS,KACP,kBACE,MACG,KAAK,UAAU;GACd,MAAM,QAAQ,KACZ,kBAAkB,MAAM,WAAW,SAAS,GAC5C,SACF;GACA,MAAM,WACJ,aAAa,QAAQ,iBAAiB,MAAM,UAAU,SAAS;GACjE,IAAI;GACJ,IAAI,UACF,UAAU,UAAU;QACf,IAAI,MAAM,WAAW,SAC1B,UAAU,UAAU,KAAK,MAAM,SAAS,iBAAiB,SAAS;QAElE,UAAU,KACR,kBAAkB,MAAM,YAAY,SAAS,GAC7C,SACF;GAEF,OAAO,KAAK,MAAM,SAAS,GAAG,MAAM,MAAM;EAC5C,CAAC,CAAC,CACD,KAAK,IAAI,KACX,UAAU,IACP,YAAY,QAAQ,aAAa,YAAY,IAAI,SAAS,YAC1D,GACR;CACF;CACA,SAAS,KAAK,QAAQ;CACtB,OAAO,SAAS,KAAK,MAAM;AAC7B"}
|
|
1
|
+
{"version":3,"file":"activityLabel.mjs","names":[],"sources":["../../../src/prompts/activityLabel.ts"],"sourcesContent":["import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type { ActivityLabelToolEntry } from '@/types/activityLabel';\nimport { shouldRedactTool } from '@/langfuseToolOutputTracing';\n\n/**\n * Default system prompt for fast-model activity labeling.\n *\n * Style synthesized from Claude Code's tool-use summary prompt (git-subject\n * register, past tense, distinctive nouns) and claude.ai's observed group\n * headers (5–9 words describing a mixed reasoning + tool block, e.g.\n * \"Synthesized version data and curated comparative framework\").\n */\nexport const ACTIVITY_LABEL_PROMPT = `Write a short label describing what this block of agent activity accomplished. It appears as the header of a collapsed activity group in a chat UI.\n\nRules:\n- 5 to 9 words, past-tense verb first\n- Name the most distinctive subject (file, API, topic); drop articles and filler\n- Describe outcomes, not mechanics; if something failed, say so plainly\n- Output only the label — no quotes, no punctuation at the end, no preamble\n\nExamples:\n- Searched Node.js release notes and changelogs\n- Compared runtime versions across official sources\n- Fixed failing auth middleware tests\n- Read project config and dependency manifests\n- Attempted database migration, hit permission errors`;\n\n/** Truncates a serialized value for the label prompt. */\nexport function truncateForLabel(value: string, maxLength: number): string {\n if (value.length <= maxLength) {\n return value;\n }\n return value.slice(0, Math.max(0, maxLength - 1)) + '…';\n}\n\n/**\n * Reduces a committed label to bounded single-line data.\n *\n * Sections in this prompt are delimited by blank lines, so a label carrying\n * embedded newlines could otherwise forge an apparent entries section or\n * `Header:` cue. Unlike every other input here, previous labels re-enter\n * the prompt on EVERY later batch, so one malformed result — plain model\n * noncompliance, or injection surfacing through a tool result — would\n * persistently steer unrelated later labels rather than affecting one. The\n * clip bounds the same way `lastAssistantText` and reasoning excerpts are\n * bounded: oversized headers must not inflate later requests past the fast\n * model's window and starve the run of labels entirely.\n */\nfunction sanitizePreviousLabel(label: string): string {\n return truncateForLabel(\n label.replace(/\\s+/g, ' ').trim(),\n PREVIOUS_LABEL_LIMIT\n );\n}\n\nconst ABORT_SERIALIZATION = Symbol('abort-label-serialization');\n\n/**\n * Serializes a tool value for the prompt WITHOUT materializing huge JSON:\n * the output is clipped to a few hundred characters anyway, so a multi-\n * megabyte tool result must not be stringified in full on the label path.\n * Strings clip immediately; structured values serialize under a character\n * budget and degrade to a shape summary once it is exhausted.\n */\nfunction serializeForLabel(value: unknown, limit: number): string {\n if (value == null) {\n return '';\n }\n if (typeof value === 'string') {\n return value.length > limit ? value.slice(0, limit + 1) : value;\n }\n let budget = limit * 4;\n try {\n return (\n JSON.stringify(value, (_key, nested: unknown) => {\n if (budget <= 0) {\n throw ABORT_SERIALIZATION;\n }\n if (typeof nested === 'string') {\n const clipped =\n nested.length > limit ? nested.slice(0, limit) : nested;\n budget -= clipped.length;\n return clipped;\n }\n budget -= 8;\n return nested;\n }) ?? ''\n );\n } catch (error) {\n if (error === ABORT_SERIALIZATION) {\n return Array.isArray(value) ? `[Array(${value.length})]` : '[Object]';\n }\n return String(value);\n }\n}\n\nconst INPUT_CONTEXT_LIMIT = 200;\nconst MAX_THINKING_EXCERPTS = 4;\nconst MAX_PREVIOUS_LABELS = 3;\n/** Per-label bound. A header is 5-9 words; anything past this is\n * noncompliance or payload, and previous labels are the one input that\n * RE-ENTERS the prompt on every later batch of the run. */\nconst PREVIOUS_LABEL_LIMIT = 200;\n/** A label is 5-9 words; no batch needs more than this many entries to\n * produce one, and the cap keeps a 200-call programmatic batch from\n * building an enormous prompt out of per-field-bounded pieces. */\nconst MAX_PROMPT_ENTRIES = 12;\n\nexport type BuildActivityLabelPromptParams = {\n entries: ActivityLabelToolEntry[];\n charLimit: number;\n thinkingExcerpts?: string[];\n lastAssistantText?: string;\n /**\n * Headers already committed for earlier batches in this run, in run order\n * with the most recent last. Rendered ahead of the block context so the\n * label continues the run's story instead of restating a line the user is\n * already reading. Capped at {@link MAX_PREVIOUS_LABELS}.\n */\n previousLabels?: string[];\n /**\n * Resolved tool-output tracing policy. The label prompt becomes Langfuse\n * generation input, so outputs/errors excluded from tracing (global\n * disable or `redactedToolNames`) must never appear in it — the same\n * redaction the span processor applies to structured tool observations.\n */\n redaction?: ResolvedLangfuseToolOutputTracingConfig;\n};\n\n/**\n * Builds the user prompt for a fast-model activity label. Pure — exported\n * for direct testing of redaction and truncation behavior.\n */\nexport function buildActivityLabelPrompt({\n entries,\n charLimit,\n thinkingExcerpts,\n lastAssistantText,\n previousLabels,\n redaction,\n}: BuildActivityLabelPromptParams): string {\n const clip = truncateForLabel;\n /** Reasoning and intent text can quote tool output verbatim — including\n * output from EARLIER calls to a redacted tool that this batch does not\n * contain — so any active policy (global disable or a configured\n * redacted-name list) drops both wholesale. There is no reliable way to\n * scrub a quoted fragment out of free-form model prose. */\n const excerptsRedacted =\n redaction != null &&\n (redaction.enabled === false || redaction.redactedToolNames.size > 0);\n const sections: string[] = [];\n /** Previous labels are free-form model prose too, and per-agent overlays\n * mean an earlier header may have been generated under ANOTHER agent's\n * weaker policy — so they share the excerpts' wholesale drop rather than\n * letting a handoff leak a looser agent's phrasing into this trace. */\n if (\n !excerptsRedacted &&\n previousLabels != null &&\n previousLabels.length > 0\n ) {\n const recent = previousLabels\n .slice(-MAX_PREVIOUS_LABELS)\n .map(sanitizePreviousLabel)\n /** A label that sanitizes to nothing carries no story to continue;\n * rendering it would leave a bare bullet implying a missing header. */\n .filter((label) => label.length > 0);\n if (recent.length > 0) {\n sections.push(\n 'Previous headers in this run (most recent last):\\n' +\n recent.map((label) => `- ${label}`).join('\\n')\n );\n }\n }\n /** Intent text is free-form assistant prose that can quote a redacted\n * tool result just as reasoning can, so it shares the excerpts' fate. */\n if (\n !excerptsRedacted &&\n lastAssistantText != null &&\n lastAssistantText.length > 0\n ) {\n sections.push(\n `Intent (assistant's last message): ${clip(lastAssistantText, INPUT_CONTEXT_LIMIT)}`\n );\n }\n if (\n !excerptsRedacted &&\n thinkingExcerpts != null &&\n thinkingExcerpts.length > 0\n ) {\n sections.push(\n 'Reasoning excerpts:\\n' +\n thinkingExcerpts\n .slice(0, MAX_THINKING_EXCERPTS)\n .map((excerpt) => `- ${clip(excerpt, charLimit)}`)\n .join('\\n')\n );\n }\n if (entries.length > 0) {\n const shown = entries.slice(0, MAX_PROMPT_ENTRIES);\n const omitted = entries.length - shown.length;\n sections.push(\n /** Frames the list as reference material, not the thing to\n * transcribe. Ported from LibreChat's fallback builder (its\n * runtime.ts documents that without this the model \"hands back a\n * transcription\" of the list) after the eval harness measured it\n * across three independent sweeps: fewer template-redundancy and\n * length violations than a bare `Tool calls:` heading, with no\n * per-case regressions (agents #360). */\n 'What it called, and what came back (do not restate these):\\n' +\n shown\n .map((entry) => {\n const input = clip(\n serializeForLabel(entry.toolInput, charLimit),\n charLimit\n );\n const redacted =\n redaction != null && shouldRedactTool(entry.toolName, redaction);\n let outcome: string;\n if (redacted) {\n outcome = redaction.redactionText;\n } else if (entry.status === 'error') {\n outcome = `ERROR: ${clip(entry.error ?? 'unknown error', charLimit)}`;\n } else {\n outcome = clip(\n serializeForLabel(entry.toolOutput, charLimit),\n charLimit\n );\n }\n return `- ${entry.toolName}(${input}) → ${outcome}`;\n })\n .join('\\n') +\n (omitted > 0\n ? `\\n- …and ${omitted} more tool ${omitted === 1 ? 'call' : 'calls'}`\n : '')\n );\n }\n /** The fallback builder's terminal cue, measured alongside the heading\n * (same sweeps). The default system prompt already describes the\n * output as \"the header of a collapsed activity group\". */\n sections.push('Header:');\n return sections.join('\\n\\n');\n}\n"],"mappings":";;;;;;;;;;AAYA,MAAa,wBAAwB;;;;;;;;;;;;;;;AAgBrC,SAAgB,iBAAiB,OAAe,WAA2B;CACzE,IAAI,MAAM,UAAU,WAClB,OAAO;CAET,OAAO,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC,CAAC,IAAI;AACtD;;;;;;;;;;;;;;AAeA,SAAS,sBAAsB,OAAuB;CACpD,OAAO,iBACL,MAAM,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK,GAChC,oBACF;AACF;AAEA,MAAM,sBAAsB,OAAO,2BAA2B;;;;;;;;AAS9D,SAAS,kBAAkB,OAAgB,OAAuB;CAChE,IAAI,SAAS,MACX,OAAO;CAET,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,SAAS,QAAQ,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI;CAE5D,IAAI,SAAS,QAAQ;CACrB,IAAI;EACF,OACE,KAAK,UAAU,QAAQ,MAAM,WAAoB;GAC/C,IAAI,UAAU,GACZ,MAAM;GAER,IAAI,OAAO,WAAW,UAAU;IAC9B,MAAM,UACJ,OAAO,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,IAAI;IACnD,UAAU,QAAQ;IAClB,OAAO;GACT;GACA,UAAU;GACV,OAAO;EACT,CAAC,KAAK;CAEV,SAAS,OAAO;EACd,IAAI,UAAU,qBACZ,OAAO,MAAM,QAAQ,KAAK,IAAI,UAAU,MAAM,OAAO,MAAM;EAE7D,OAAO,OAAO,KAAK;CACrB;AACF;AAEA,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;;;;AAK9B,MAAM,uBAAuB;;;;AAI7B,MAAM,qBAAqB;;;;;AA2B3B,SAAgB,yBAAyB,EACvC,SACA,WACA,kBACA,mBACA,gBACA,aACyC;CACzC,MAAM,OAAO;;;;;;CAMb,MAAM,mBACJ,aAAa,SACZ,UAAU,YAAY,SAAS,UAAU,kBAAkB,OAAO;CACrE,MAAM,WAAqB,CAAC;;;;;CAK5B,IACE,CAAC,oBACD,kBAAkB,QAClB,eAAe,SAAS,GACxB;EACA,MAAM,SAAS,eACZ,MAAM,EAAoB,CAAC,CAC3B,IAAI,qBAAqB,CAAC,CAG1B,QAAQ,UAAU,MAAM,SAAS,CAAC;EACrC,IAAI,OAAO,SAAS,GAClB,SAAS,KACP,uDACE,OAAO,KAAK,UAAU,KAAK,OAAO,CAAC,CAAC,KAAK,IAAI,CACjD;CAEJ;;;CAGA,IACE,CAAC,oBACD,qBAAqB,QACrB,kBAAkB,SAAS,GAE3B,SAAS,KACP,sCAAsC,KAAK,mBAAmB,mBAAmB,GACnF;CAEF,IACE,CAAC,oBACD,oBAAoB,QACpB,iBAAiB,SAAS,GAE1B,SAAS,KACP,0BACE,iBACG,MAAM,GAAG,qBAAqB,CAAC,CAC/B,KAAK,YAAY,KAAK,KAAK,SAAS,SAAS,GAAG,CAAC,CACjD,KAAK,IAAI,CAChB;CAEF,IAAI,QAAQ,SAAS,GAAG;EACtB,MAAM,QAAQ,QAAQ,MAAM,GAAG,kBAAkB;EACjD,MAAM,UAAU,QAAQ,SAAS,MAAM;EACvC,SAAS;;;;;;;;GAQP,iEACE,MACG,KAAK,UAAU;IACd,MAAM,QAAQ,KACZ,kBAAkB,MAAM,WAAW,SAAS,GAC5C,SACF;IACA,MAAM,WACJ,aAAa,QAAQ,iBAAiB,MAAM,UAAU,SAAS;IACjE,IAAI;IACJ,IAAI,UACF,UAAU,UAAU;SACf,IAAI,MAAM,WAAW,SAC1B,UAAU,UAAU,KAAK,MAAM,SAAS,iBAAiB,SAAS;SAElE,UAAU,KACR,kBAAkB,MAAM,YAAY,SAAS,GAC7C,SACF;IAEF,OAAO,KAAK,MAAM,SAAS,GAAG,MAAM,MAAM;GAC5C,CAAC,CAAC,CACD,KAAK,IAAI,KACX,UAAU,IACP,YAAY,QAAQ,aAAa,YAAY,IAAI,SAAS,YAC1D;EACR;CACF;;;;CAIA,SAAS,KAAK,SAAS;CACvB,OAAO,SAAS,KAAK,MAAM;AAC7B"}
|
package/dist/esm/run.mjs
CHANGED
|
@@ -2,15 +2,15 @@ import { getTraceIdSeed } from "./langfuseRuntimeContext.mjs";
|
|
|
2
2
|
import { hasToolOutputTracingConfig, resolveLangfuseConfig, resolveToolOutputTracingConfig } from "./langfuseConfig.mjs";
|
|
3
3
|
import { resolveLangfuseRuntimeScope, withLangfuseRuntimeScope } from "./langfuseRuntimeScope.mjs";
|
|
4
4
|
import { createLangfuseHandler, createLangfuseTraceMetadata, disposeLangfuseHandler, getLangfuseTraceName, isLangfuseCallbackHandler, withLangfuseAttributes } from "./langfuse.mjs";
|
|
5
|
-
import { ACTIVITY_LABEL_PROMPT, buildActivityLabelPrompt } from "./prompts/activityLabel.mjs";
|
|
6
|
-
import { appendCallbacks, findCallback } from "./utils/callbacks.mjs";
|
|
7
5
|
import "./common/constants.mjs";
|
|
8
6
|
import "./common/enum.mjs";
|
|
9
7
|
import "./common/index.mjs";
|
|
8
|
+
import { ACTIVITY_LABEL_PROMPT, buildActivityLabelPrompt } from "./prompts/activityLabel.mjs";
|
|
9
|
+
import { appendCallbacks, findCallback } from "./utils/callbacks.mjs";
|
|
10
10
|
import { createCompletionTitleRunnable, createTitleRunnable } from "./utils/title.mjs";
|
|
11
11
|
import { createTokenCounter, encodingForModel } from "./utils/tokens.mjs";
|
|
12
|
-
import { resolveMaxSeals } from "./llm/preempt.mjs";
|
|
13
12
|
import { initializeLangfuseTracing } from "./instrumentation.mjs";
|
|
13
|
+
import { resolveMaxSeals } from "./llm/preempt.mjs";
|
|
14
14
|
import { isOpenAILike } from "./utils/llm.mjs";
|
|
15
15
|
import { HandlerRegistry } from "./events.mjs";
|
|
16
16
|
import { initializeModel } from "./llm/init.mjs";
|
|
@@ -18,6 +18,7 @@ import { executeHooks } from "./hooks/executeHooks.mjs";
|
|
|
18
18
|
import "./hooks/index.mjs";
|
|
19
19
|
import { StandardGraph } from "./graphs/Graph.mjs";
|
|
20
20
|
import { MultiAgentGraph } from "./graphs/MultiAgentGraph.mjs";
|
|
21
|
+
import { nanoid } from "nanoid";
|
|
21
22
|
import { PromptTemplate } from "@langchain/core/prompts";
|
|
22
23
|
import { RunnableLambda } from "@langchain/core/runnables";
|
|
23
24
|
import { AzureChatOpenAI, ChatOpenAI } from "@langchain/openai";
|
|
@@ -540,13 +541,22 @@ var Run = class Run {
|
|
|
540
541
|
const traceName = config.runName ?? getLangfuseTraceName(traceMetadata);
|
|
541
542
|
const streamLangfuseConfig = this.getStreamLangfuseConfig(graph);
|
|
542
543
|
initializeLangfuseTracing(streamLangfuseConfig);
|
|
544
|
+
const streamRuntimeScope = resolveLangfuseRuntimeScope({
|
|
545
|
+
runLangfuse: streamLangfuseConfig,
|
|
546
|
+
langfuseOverlay: this.getStreamToolOutputTracingLangfuseConfig(graph),
|
|
547
|
+
traceIdSeed: streamLangfuseConfig?.deterministicTraceId === true ? this.id : void 0,
|
|
548
|
+
runId: graph.langfuseScopeRunId
|
|
549
|
+
});
|
|
543
550
|
const langfuseHandler = createLangfuseHandler({
|
|
544
551
|
langfuse: streamLangfuseConfig,
|
|
545
552
|
userId,
|
|
546
553
|
sessionId,
|
|
547
554
|
traceMetadata,
|
|
548
555
|
tags: ["librechat", "agent"],
|
|
549
|
-
traceIdSeed: streamLangfuseConfig?.deterministicTraceId === true ? this.id : void 0
|
|
556
|
+
traceIdSeed: streamLangfuseConfig?.deterministicTraceId === true ? this.id : void 0,
|
|
557
|
+
runId: graph.langfuseScopeRunId,
|
|
558
|
+
toolOutputTracing: streamRuntimeScope.toolOutputTracing,
|
|
559
|
+
traceName
|
|
550
560
|
});
|
|
551
561
|
if (langfuseHandler != null) {
|
|
552
562
|
config.runName = traceName;
|
|
@@ -710,11 +720,7 @@ var Run = class Run {
|
|
|
710
720
|
else if (this._haltedReason == null && graph.preemptIncomplete) this._haltedReason = "preempt_incomplete";
|
|
711
721
|
};
|
|
712
722
|
try {
|
|
713
|
-
await withLangfuseRuntimeScope(
|
|
714
|
-
runLangfuse: streamLangfuseConfig,
|
|
715
|
-
langfuseOverlay: this.getStreamToolOutputTracingLangfuseConfig(graph),
|
|
716
|
-
traceIdSeed: streamLangfuseConfig?.deterministicTraceId === true ? this.id : void 0
|
|
717
|
-
}), () => withLangfuseAttributes({
|
|
723
|
+
await withLangfuseRuntimeScope(streamRuntimeScope, () => withLangfuseAttributes({
|
|
718
724
|
langfuse: streamLangfuseConfig,
|
|
719
725
|
userId,
|
|
720
726
|
sessionId,
|
|
@@ -967,19 +973,35 @@ var Run = class Run {
|
|
|
967
973
|
}
|
|
968
974
|
async generateTitle({ provider, inputText, contentParts, titlePrompt, clientOptions, chainOptions, skipLanguage, titleMethod = "completion", titlePromptTemplate }) {
|
|
969
975
|
let titleLangfuseHandler;
|
|
970
|
-
let titleLangfuseConfig;
|
|
971
976
|
let titleUserId;
|
|
972
977
|
let titleSessionId;
|
|
973
978
|
const titleContext = this.Graph == null ? void 0 : this.Graph.agentContexts.get(this.Graph.defaultAgentId);
|
|
979
|
+
const titleLangfuseConfig = resolveLangfuseConfig(this.langfuse, titleContext?.langfuse);
|
|
974
980
|
const traceMetadata = createLangfuseTraceMetadata({
|
|
975
981
|
messageId: "title-" + this.id,
|
|
976
982
|
agentName: titleContext?.name
|
|
977
983
|
});
|
|
978
984
|
const titleRunName = getLangfuseTraceName(traceMetadata, "LibreChat Title");
|
|
985
|
+
/** Scope identity carries an opaque per-execution component: public run
|
|
986
|
+
* ids are unrestricted, so a purely derived id (`title-<runId>`) could
|
|
987
|
+
* collide with an ordinary concurrent run literally named that way and
|
|
988
|
+
* defeat foreign-scope rejection. */
|
|
989
|
+
const titleScopeRunId = `title:${this.id}:${nanoid()}`;
|
|
990
|
+
/** Seed policy mirrors `generateActivityLabel`:
|
|
991
|
+
* `runWithLangfuseRuntimeContext` SPREADS the surrounding context, so an
|
|
992
|
+
* absent seed INHERITS an active parent run's and collapses the title
|
|
993
|
+
* into that run's trace. Seeded when determinism is opted into OR a
|
|
994
|
+
* parent seed is live; otherwise unseeded, matching the other paths. */
|
|
995
|
+
const inheritedTraceSeed = getTraceIdSeed();
|
|
996
|
+
const titleRuntimeScope = resolveLangfuseRuntimeScope({
|
|
997
|
+
runLangfuse: this.langfuse,
|
|
998
|
+
langfuseOverlay: titleContext?.langfuse,
|
|
999
|
+
traceIdSeed: titleLangfuseConfig?.deterministicTraceId === true || inheritedTraceSeed != null ? "title-" + this.id : void 0,
|
|
1000
|
+
runId: titleScopeRunId
|
|
1001
|
+
});
|
|
979
1002
|
if (chainOptions != null) {
|
|
980
1003
|
titleUserId = typeof chainOptions.configurable?.user_id === "string" ? chainOptions.configurable.user_id : void 0;
|
|
981
1004
|
titleSessionId = typeof chainOptions.configurable?.thread_id === "string" ? chainOptions.configurable.thread_id : void 0;
|
|
982
|
-
titleLangfuseConfig = resolveLangfuseConfig(this.langfuse, titleContext?.langfuse);
|
|
983
1005
|
initializeLangfuseTracing(titleLangfuseConfig);
|
|
984
1006
|
titleLangfuseHandler = createLangfuseHandler({
|
|
985
1007
|
langfuse: titleLangfuseConfig,
|
|
@@ -987,7 +1009,10 @@ var Run = class Run {
|
|
|
987
1009
|
sessionId: titleSessionId,
|
|
988
1010
|
traceMetadata,
|
|
989
1011
|
tags: ["librechat", "title"],
|
|
990
|
-
traceIdSeed: titleLangfuseConfig?.deterministicTraceId === true ? "title-" + this.id : void 0
|
|
1012
|
+
traceIdSeed: titleLangfuseConfig?.deterministicTraceId === true ? "title-" + this.id : void 0,
|
|
1013
|
+
runId: titleScopeRunId,
|
|
1014
|
+
toolOutputTracing: titleRuntimeScope.toolOutputTracing,
|
|
1015
|
+
traceName: chainOptions.runName ?? titleRunName
|
|
991
1016
|
});
|
|
992
1017
|
if (titleLangfuseHandler != null) chainOptions.callbacks = appendCallbacks(chainOptions.callbacks, [titleLangfuseHandler]);
|
|
993
1018
|
}
|
|
@@ -1033,18 +1058,12 @@ var Run = class Run {
|
|
|
1033
1058
|
}, runtimeConfig));
|
|
1034
1059
|
try {
|
|
1035
1060
|
try {
|
|
1036
|
-
return await withLangfuseRuntimeScope(
|
|
1037
|
-
runLangfuse: this.langfuse,
|
|
1038
|
-
langfuseOverlay: titleContext?.langfuse
|
|
1039
|
-
}), () => invokeTitleChain(invokeConfig));
|
|
1061
|
+
return await withLangfuseRuntimeScope(titleRuntimeScope, () => invokeTitleChain(invokeConfig));
|
|
1040
1062
|
} catch (_e) {
|
|
1041
1063
|
const langfuseHandler = findCallback(invokeConfig.callbacks, isLangfuseCallbackHandler);
|
|
1042
1064
|
const { callbacks: _cb, ...rest } = invokeConfig;
|
|
1043
1065
|
const safeConfig = Object.assign({}, rest, { callbacks: langfuseHandler ? [langfuseHandler] : [] });
|
|
1044
|
-
return await withLangfuseRuntimeScope(
|
|
1045
|
-
runLangfuse: this.langfuse,
|
|
1046
|
-
langfuseOverlay: titleContext?.langfuse
|
|
1047
|
-
}), () => invokeTitleChain(safeConfig));
|
|
1066
|
+
return await withLangfuseRuntimeScope(titleRuntimeScope, () => invokeTitleChain(safeConfig));
|
|
1048
1067
|
}
|
|
1049
1068
|
} finally {
|
|
1050
1069
|
await disposeLangfuseHandler(titleLangfuseHandler);
|
|
@@ -1096,10 +1115,13 @@ var Run = class Run {
|
|
|
1096
1115
|
* otherwise unseeded, matching the other generation paths. */
|
|
1097
1116
|
const inheritedTraceSeed = getTraceIdSeed();
|
|
1098
1117
|
const labelTraceSeed = labelLangfuseConfig?.deterministicTraceId === true || inheritedTraceSeed != null ? traceSeed ?? `activity-label-${this.id}-${labelSeq}` : void 0;
|
|
1118
|
+
/** Opaque per-execution component: see `titleScopeRunId`. */
|
|
1119
|
+
const labelScopeRunId = `activity-label:${this.id}:${labelSeq}:${nanoid()}`;
|
|
1099
1120
|
const labelRuntimeScope = resolveLangfuseRuntimeScope({
|
|
1100
1121
|
runLangfuse: this.langfuse,
|
|
1101
1122
|
langfuseOverlay: labelContext?.langfuse,
|
|
1102
|
-
traceIdSeed: labelTraceSeed
|
|
1123
|
+
traceIdSeed: labelTraceSeed,
|
|
1124
|
+
runId: labelScopeRunId
|
|
1103
1125
|
});
|
|
1104
1126
|
/** Handler only when a session id resolved from
|
|
1105
1127
|
* `chainOptions.configurable.thread_id`: without it the label call has
|
|
@@ -1115,7 +1137,10 @@ var Run = class Run {
|
|
|
1115
1137
|
sessionId: labelSessionId,
|
|
1116
1138
|
traceMetadata,
|
|
1117
1139
|
tags: ["librechat", "activity-label"],
|
|
1118
|
-
traceIdSeed: labelLangfuseConfig?.deterministicTraceId === true ? labelTraceSeed : void 0
|
|
1140
|
+
traceIdSeed: labelLangfuseConfig?.deterministicTraceId === true ? labelTraceSeed : void 0,
|
|
1141
|
+
runId: labelScopeRunId,
|
|
1142
|
+
toolOutputTracing: labelRuntimeScope.toolOutputTracing,
|
|
1143
|
+
traceName: labelChainOptions.runName ?? labelRunName
|
|
1119
1144
|
});
|
|
1120
1145
|
if (labelLangfuseHandler != null) labelChainOptions.callbacks = appendCallbacks(labelChainOptions.callbacks, [labelLangfuseHandler]);
|
|
1121
1146
|
/** The label prompt becomes Langfuse generation input, so the resolved
|