@struct-ai/sdk 0.1.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/LICENSE +201 -0
- package/README.md +168 -0
- package/dist/commonjs/content-capture.d.ts +10 -0
- package/dist/commonjs/content-capture.js +23 -0
- package/dist/commonjs/context.d.ts +30 -0
- package/dist/commonjs/context.js +94 -0
- package/dist/commonjs/core.d.ts +120 -0
- package/dist/commonjs/core.js +408 -0
- package/dist/commonjs/events.d.ts +12 -0
- package/dist/commonjs/events.js +103 -0
- package/dist/commonjs/index.d.ts +6 -0
- package/dist/commonjs/index.js +10 -0
- package/dist/commonjs/integrations/anthropic-content.d.ts +24 -0
- package/dist/commonjs/integrations/anthropic-content.js +178 -0
- package/dist/commonjs/integrations/anthropic.d.ts +34 -0
- package/dist/commonjs/integrations/anthropic.js +377 -0
- package/dist/commonjs/integrations/index.d.ts +5 -0
- package/dist/commonjs/integrations/index.js +47 -0
- package/dist/commonjs/integrations/langchain-callback.d.ts +134 -0
- package/dist/commonjs/integrations/langchain-callback.js +723 -0
- package/dist/commonjs/integrations/langchain-content.d.ts +35 -0
- package/dist/commonjs/integrations/langchain-content.js +307 -0
- package/dist/commonjs/integrations/langchain.d.ts +11 -0
- package/dist/commonjs/integrations/langchain.js +100 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/semconv.d.ts +54 -0
- package/dist/commonjs/semconv.js +74 -0
- package/dist/commonjs/truncation.d.ts +10 -0
- package/dist/commonjs/truncation.js +103 -0
- package/dist/esm/content-capture.d.ts +10 -0
- package/dist/esm/content-capture.js +17 -0
- package/dist/esm/context.d.ts +30 -0
- package/dist/esm/context.js +80 -0
- package/dist/esm/core.d.ts +120 -0
- package/dist/esm/core.js +403 -0
- package/dist/esm/events.d.ts +12 -0
- package/dist/esm/events.js +99 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/integrations/anthropic-content.d.ts +24 -0
- package/dist/esm/integrations/anthropic-content.js +168 -0
- package/dist/esm/integrations/anthropic.d.ts +34 -0
- package/dist/esm/integrations/anthropic.js +370 -0
- package/dist/esm/integrations/index.d.ts +5 -0
- package/dist/esm/integrations/index.js +42 -0
- package/dist/esm/integrations/langchain-callback.d.ts +134 -0
- package/dist/esm/integrations/langchain-callback.js +719 -0
- package/dist/esm/integrations/langchain-content.d.ts +35 -0
- package/dist/esm/integrations/langchain-content.js +294 -0
- package/dist/esm/integrations/langchain.d.ts +11 -0
- package/dist/esm/integrations/langchain.js +95 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/semconv.d.ts +54 -0
- package/dist/esm/semconv.js +71 -0
- package/dist/esm/truncation.d.ts +10 -0
- package/dist/esm/truncation.js +96 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/content-capture.d.ts +10 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/context.d.ts +30 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/core.d.ts +120 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/events.d.ts +12 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/index.d.ts +6 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/anthropic-content.d.ts +24 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/anthropic.d.ts +34 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/index.d.ts +5 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain-callback.d.ts +134 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain-content.d.ts +35 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain.d.ts +11 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/package.json +3 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/semconv.d.ts +54 -0
- package/dist/node_modules/@struct-ai/sdk/dist/commonjs/truncation.d.ts +10 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/content-capture.d.ts +10 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/context.d.ts +30 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/core.d.ts +120 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/events.d.ts +12 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/index.d.ts +6 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/anthropic-content.d.ts +24 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/anthropic.d.ts +34 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/index.d.ts +5 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain-callback.d.ts +134 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain-content.d.ts +35 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain.d.ts +11 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/package.json +3 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/semconv.d.ts +54 -0
- package/dist/node_modules/@struct-ai/sdk/dist/esm/truncation.d.ts +10 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/commonjs/package.json +3 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/esm/package.json +3 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/package.json +103 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/package.json +118 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/package.json +916 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/package.json +188 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api/package.json +96 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api-logs/package.json +91 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/core/package.json +96 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-logs-otlp-proto/package.json +108 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-trace-otlp-proto/package.json +105 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/resources/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-logs/package.json +109 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-base/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-node/package.json +76 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/package.json +140 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/nock/package.json +89 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/tshy/package.json +81 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/typescript/package.json +120 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/vitest/package.json +239 -0
- package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/core.d.ts +244 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/error.d.ts +54 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/index.d.ts +188 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/package.json +103 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/pagination.d.ts +26 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/resource.d.ts +6 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/streaming.d.ts +41 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/uploads.d.ts +75 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/version.d.ts +2 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/experimental.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/index.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/package.json +118 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/agents.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/caches.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/chat_history.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/context.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/documents.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/embeddings.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/example_selectors.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/index.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/indexing.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/load.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/memory.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/messages.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/output_parsers.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/outputs.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/package.json +916 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/prompt_values.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/prompts.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/retrievers.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/runnables.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/singletons.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/stores.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/structured_query.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/tools.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/vectorstores.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/index.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/package.json +188 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/prebuilt.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/pregel.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/remote.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/web.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/zod.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api/package.json +96 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api-logs/package.json +91 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/core/package.json +96 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-logs-otlp-proto/package.json +108 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-trace-otlp-proto/package.json +105 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/resources/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-logs/package.json +109 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-base/package.json +100 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-node/package.json +76 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/assert.d.ts +1062 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/async_hooks.d.ts +605 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/buffer.buffer.d.ts +471 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/buffer.d.ts +1936 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/child_process.d.ts +1475 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/cluster.d.ts +577 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/console.d.ts +452 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/constants.d.ts +21 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/crypto.d.ts +4590 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/dgram.d.ts +597 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/dns.d.ts +871 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/domain.d.ts +170 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/events.d.ts +977 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/fs.d.ts +4375 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/globals.d.ts +172 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/globals.typedarray.d.ts +38 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/http.d.ts +2049 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/http2.d.ts +2631 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/https.d.ts +578 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/index.d.ts +93 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/inspector.generated.d.ts +3966 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/module.d.ts +539 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/net.d.ts +1031 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/os.d.ts +506 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/package.json +140 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/path.d.ts +200 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/perf_hooks.d.ts +961 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/process.d.ts +1961 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/punycode.d.ts +117 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/querystring.d.ts +152 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/readline.d.ts +589 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/repl.d.ts +430 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/sea.d.ts +153 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/stream.d.ts +1698 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/test.d.ts +1787 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/timers.d.ts +286 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/tls.d.ts +1259 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/trace_events.d.ts +197 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/tty.d.ts +208 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/url.d.ts +964 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/util.d.ts +2331 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/v8.d.ts +809 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/vm.d.ts +1001 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/wasi.d.ts +181 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/worker_threads.d.ts +715 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/zlib.d.ts +598 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/nock/package.json +89 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/tshy/package.json +81 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/typescript/package.json +120 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/config.d.ts +3 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/coverage.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/environments.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/globals.d.ts +22 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/import-meta.d.ts +5 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/importMeta.d.ts +4 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/jsdom.d.ts +6 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/mocker.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/node.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/optional-runtime-types.d.ts +6 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/optional-types.d.ts +7 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/package.json +239 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/reporters.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/runners.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/snapshot.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/suite.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/worker.d.ts +1 -0
- package/dist/node_modules/@struct-ai/sdk/package.json +100 -0
- package/package.json +100 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { context as otelContext, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
3
|
+
import { getAgentSpan, getSessionId, popPendingToolCallId, pushPendingToolCalls, } from "../context.js";
|
|
4
|
+
import { safe } from "../core.js";
|
|
5
|
+
import { ERROR_TYPE, EVENT_NAMES, EVENT_NAME, GEN_AI, LANGCHAIN_FINISH_REASON_MAP, ROLE_TO_EVENT_NAME, STRUCT, } from "../semconv.js";
|
|
6
|
+
import { safeJsonStringify, truncateAndSerialize, truncateParts, } from "../truncation.js";
|
|
7
|
+
import { detectProvider, langchainMessageToRoleAndParts, langchainToInputMessages, langchainToOutputMessages, lastUserMessageParts, } from "./langchain-content.js";
|
|
8
|
+
/**
|
|
9
|
+
* LangChain CallbackHandler — creates OTel spans from LangChain callbacks.
|
|
10
|
+
*
|
|
11
|
+
* Parent-child linkage comes from `parentRunId` fields LangChain passes to us,
|
|
12
|
+
* NOT from OTel's async context. This survives LangGraph's internal task
|
|
13
|
+
* shuffling, which is why the callback API is the industry-standard integration
|
|
14
|
+
* point (LangSmith, OpenInference, Traceloop, Langfuse all use it).
|
|
15
|
+
*
|
|
16
|
+
* thread_id vs gen_ai.conversation.id
|
|
17
|
+
* -----------------------------------
|
|
18
|
+
* These are distinct concepts and Struct keeps them separate:
|
|
19
|
+
*
|
|
20
|
+
* - `config.configurable.thread_id` is LangGraph's checkpoint identifier,
|
|
21
|
+
* keying a conversation's state in the checkpointer. Multiple turns of
|
|
22
|
+
* the same conversation reuse one thread_id so history accumulates.
|
|
23
|
+
*
|
|
24
|
+
* - `gen_ai.conversation.id` is the OTel GenAI-spec conversation identifier
|
|
25
|
+
* and Struct's UI grouping unit — one value = one entry in the sessions
|
|
26
|
+
* list. For the top-level agent we auto-derive it from thread_id, so a
|
|
27
|
+
* stable thread_id across multi-turn chats collapses into one session.
|
|
28
|
+
*
|
|
29
|
+
* For SUBAGENTS (an agent invoked from inside another's tool body) we
|
|
30
|
+
* deliberately assign a DIFFERENT conversation.id — either from the subagent's
|
|
31
|
+
* own thread_id if supplied, or a fresh UUID. The resulting session is
|
|
32
|
+
* linked back to the outer agent's session via the
|
|
33
|
+
* `struct.agent.parent_session_id` span attribute (what powers "Spawned
|
|
34
|
+
* by" / subagent navigation in the UI). Without this split the subagent's
|
|
35
|
+
* spans would collapse into the outer session, burying delegation.
|
|
36
|
+
*
|
|
37
|
+
* LangChain quirk (handled automatically): when `agent.invoke(...)` runs
|
|
38
|
+
* nested inside a parent call, LangChain's config-merge inherits the
|
|
39
|
+
* parent's `metadata.thread_id` onto the child even if its config provided
|
|
40
|
+
* a different one. We detect that by comparing against the nearest agent
|
|
41
|
+
* ancestor's session; if they match, treat it as "inherited, not
|
|
42
|
+
* user-intended" and assign a fresh UUID to the subagent.
|
|
43
|
+
*
|
|
44
|
+
* End-user guidance:
|
|
45
|
+
* - Use thread_id per conversation; multi-turn chats reuse it.
|
|
46
|
+
* - For a subagent call, pass a DIFFERENT thread_id (or omit it and let
|
|
47
|
+
* LangGraph generate one). Each subagent then surfaces as its own
|
|
48
|
+
* session in the UI, linked back via parent_session_id.
|
|
49
|
+
*/
|
|
50
|
+
export class StructCallbackHandler {
|
|
51
|
+
sdk;
|
|
52
|
+
tracer;
|
|
53
|
+
logger;
|
|
54
|
+
name = "struct";
|
|
55
|
+
lc_namespace = ["struct", "callbacks"];
|
|
56
|
+
lc_serializable = false;
|
|
57
|
+
awaitHandlers = false;
|
|
58
|
+
ignoreLLM = false;
|
|
59
|
+
ignoreChain = false;
|
|
60
|
+
ignoreAgent = false;
|
|
61
|
+
ignoreRetriever = false;
|
|
62
|
+
ignoreCustomEvent = true;
|
|
63
|
+
/**
|
|
64
|
+
* LangChain reads `raiseError` to decide whether a callback throw should
|
|
65
|
+
* abort the user's run. We keep this `false` because every callback method
|
|
66
|
+
* is wrapped in `safe()` and never throws — but a `true` here would tell
|
|
67
|
+
* LangChain to surface our (impossible) throws into the user's call.
|
|
68
|
+
* Locked by `test_handler_raiseError_is_false`.
|
|
69
|
+
*/
|
|
70
|
+
raiseError = false;
|
|
71
|
+
runs = new Map();
|
|
72
|
+
internalLogger;
|
|
73
|
+
constructor(sdk, tracer, logger) {
|
|
74
|
+
this.sdk = sdk;
|
|
75
|
+
this.tracer = tracer;
|
|
76
|
+
this.logger = logger;
|
|
77
|
+
this.internalLogger = sdk.getInternalLogger();
|
|
78
|
+
}
|
|
79
|
+
// ─── Chain / Agent (Pregel, AgentExecutor, custom Runnables) ─────────────
|
|
80
|
+
handleChainStart = (chain, inputs, runId, parentRunId, _tags, metadata, runType, runName) => {
|
|
81
|
+
// Non-agent chain (RunnableSequence, ChannelWrite, Branch, internal
|
|
82
|
+
// LangGraph nodes): don't create a span, but record the runId so
|
|
83
|
+
// downstream children with parentRunId pointing here can still find
|
|
84
|
+
// their effective parent span.
|
|
85
|
+
if (!isAgentChain(chain, runType, runName)) {
|
|
86
|
+
// Skipped chains inherit session from parent (so chat/tool spans
|
|
87
|
+
// created under them stay tied to the enclosing agent's session).
|
|
88
|
+
const sessionId = this.resolveSessionId(parentRunId, metadata);
|
|
89
|
+
const { parentSpan } = this.resolveParent(parentRunId);
|
|
90
|
+
this.runs.set(runId, {
|
|
91
|
+
span: undefined,
|
|
92
|
+
effectiveParentSpan: parentSpan,
|
|
93
|
+
sessionId,
|
|
94
|
+
nearestAgentSessionId: this.inheritedAgentSessionId(parentRunId),
|
|
95
|
+
kind: "skipped-chain",
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const agentName = runName ??
|
|
100
|
+
extractClassName(chain) ??
|
|
101
|
+
(typeof inputs === "object" && inputs
|
|
102
|
+
? inputs.name
|
|
103
|
+
: undefined) ??
|
|
104
|
+
"agent";
|
|
105
|
+
// Agent-start: each agent invocation gets its OWN gen_ai.conversation.id.
|
|
106
|
+
// Prefer config.configurable.thread_id, then a fresh UUID. Never inherit
|
|
107
|
+
// from the parent agent — subagents should appear as separate sessions
|
|
108
|
+
// in the UI, linked via ``struct.agent.parent_session_id``.
|
|
109
|
+
//
|
|
110
|
+
// Convention (documented publicly):
|
|
111
|
+
// * thread_id is a LangGraph checkpoint identifier. Multiple turns
|
|
112
|
+
// of one conversation reuse a thread_id → we group them into one
|
|
113
|
+
// session.
|
|
114
|
+
// * Subagents should be given a distinct thread_id (or none at all,
|
|
115
|
+
// letting LangGraph generate one). That way a subagent surfaces
|
|
116
|
+
// as its own entry in the sessions list.
|
|
117
|
+
//
|
|
118
|
+
// LangChain quirk (handled below): when an invoke runs nested inside
|
|
119
|
+
// a parent's call (e.g. the tool body invokes a subagent), LangChain
|
|
120
|
+
// inherits the parent's ``metadata.thread_id`` into the child run's
|
|
121
|
+
// metadata — even when the child config supplied its own. Treat that
|
|
122
|
+
// inheritance as "no thread_id" and assign a fresh UUID.
|
|
123
|
+
//
|
|
124
|
+
// For the struct.agent.parent_session_id attribute, we need the
|
|
125
|
+
// NEAREST agent ancestor's session — not just the immediate parent
|
|
126
|
+
// run, which might be a tool span. That's what
|
|
127
|
+
// ``inheritedAgentSessionId`` walks.
|
|
128
|
+
const parentAgentSessionId = this.inheritedAgentSessionId(parentRunId);
|
|
129
|
+
const sessionId = this.resolveAgentSessionId(metadata, parentAgentSessionId);
|
|
130
|
+
if (process.env.STRUCT_SDK_DEBUG === "1") {
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.error("[struct-sdk] agent-start", agentName, "metadata.thread_id=", metadata?.thread_id, "parent.agent.session=", parentAgentSessionId, "→ resolved session=", sessionId);
|
|
133
|
+
}
|
|
134
|
+
const { parentSpan, parentSpanIsAgent } = this.resolveParent(parentRunId);
|
|
135
|
+
void parentSpanIsAgent;
|
|
136
|
+
const parentCtx = parentSpan
|
|
137
|
+
? trace.setSpan(otelContext.active(), parentSpan)
|
|
138
|
+
: otelContext.active();
|
|
139
|
+
// Span creation can fail (custom tracer / broken context). Skip
|
|
140
|
+
// run-state insertion when it does — end/error callbacks already
|
|
141
|
+
// tolerate missing run state via the early `_runs.get(...) === undefined`
|
|
142
|
+
// short-circuit at the top of each method.
|
|
143
|
+
let span;
|
|
144
|
+
safe(() => {
|
|
145
|
+
span = this.tracer.startSpan(`invoke_agent ${agentName}`, { kind: SpanKind.INTERNAL }, parentCtx);
|
|
146
|
+
}, "langchain.handleChainStart.create_span", this.internalLogger);
|
|
147
|
+
if (span === undefined)
|
|
148
|
+
return;
|
|
149
|
+
const startedSpan = span;
|
|
150
|
+
safe(() => {
|
|
151
|
+
startedSpan.setAttribute(GEN_AI.OPERATION_NAME, "invoke_agent");
|
|
152
|
+
startedSpan.setAttribute(GEN_AI.PROVIDER_NAME, "langchain");
|
|
153
|
+
startedSpan.setAttribute(GEN_AI.AGENT_NAME, String(agentName));
|
|
154
|
+
// Do NOT set gen_ai.agent.id from sessionId — that conflates agent
|
|
155
|
+
// identity (spec: stable agent-definition id) with a per-invocation
|
|
156
|
+
// session. LangChain doesn't surface a stable agent-id to us, so we
|
|
157
|
+
// leave the attribute unset. Callers who use struct.agent() directly
|
|
158
|
+
// can pass their own agentId via the scope options.
|
|
159
|
+
startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
|
|
160
|
+
// Link subagents back to the parent agent's session. Use the nearest
|
|
161
|
+
// agent ancestor (NOT the immediate parent run) — a subagent spawned
|
|
162
|
+
// from inside a tool has the tool as its immediate parent, but we want
|
|
163
|
+
// to point at the outer agent's session.
|
|
164
|
+
if (parentAgentSessionId && parentAgentSessionId !== sessionId) {
|
|
165
|
+
startedSpan.setAttribute(STRUCT.AGENT_PARENT_SESSION_ID, parentAgentSessionId);
|
|
166
|
+
}
|
|
167
|
+
// User prompt propagation — set gen_ai.input.messages on the agent
|
|
168
|
+
// span so the waterfall UI shows the question without drilling into
|
|
169
|
+
// chat spans.
|
|
170
|
+
if (this.sdk.emitSpanContent) {
|
|
171
|
+
const messages = extractChainInputMessages(inputs);
|
|
172
|
+
if (messages && messages.length > 0) {
|
|
173
|
+
startedSpan.setAttribute(GEN_AI.INPUT_MESSAGES, langchainToInputMessages(messages));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}, "langchain.handleChainStart.set_attrs", this.internalLogger);
|
|
177
|
+
this.runs.set(runId, {
|
|
178
|
+
span: startedSpan,
|
|
179
|
+
effectiveParentSpan: startedSpan,
|
|
180
|
+
sessionId,
|
|
181
|
+
// This agent IS itself the nearest agent ancestor for everything nested inside.
|
|
182
|
+
nearestAgentSessionId: sessionId,
|
|
183
|
+
kind: "chain",
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
handleChainEnd = (_outputs, runId) => {
|
|
187
|
+
const r = this.runs.get(runId);
|
|
188
|
+
this.runs.delete(runId);
|
|
189
|
+
if (!r || !r.span)
|
|
190
|
+
return;
|
|
191
|
+
const span = r.span;
|
|
192
|
+
safe(() => span.setStatus({ code: SpanStatusCode.OK }), "langchain.handleChainEnd.set_status", this.internalLogger);
|
|
193
|
+
safe(() => span.end(), "langchain.handleChainEnd.span_end", this.internalLogger);
|
|
194
|
+
};
|
|
195
|
+
handleChainError = (err, runId) => {
|
|
196
|
+
const r = this.runs.get(runId);
|
|
197
|
+
this.runs.delete(runId);
|
|
198
|
+
if (!r || !r.span)
|
|
199
|
+
return;
|
|
200
|
+
const span = r.span;
|
|
201
|
+
safe(() => recordError(span, err), "langchain.handleChainError.record_error", this.internalLogger);
|
|
202
|
+
safe(() => span.end(), "langchain.handleChainError.span_end", this.internalLogger);
|
|
203
|
+
};
|
|
204
|
+
// ─── LLM / Chat model ────────────────────────────────────────────────────
|
|
205
|
+
handleChatModelStart = (llm, messages, runId, parentRunId, extraParams, _tags, metadata, runName) => {
|
|
206
|
+
const provider = detectProviderFromSerialized(llm);
|
|
207
|
+
// Note: we emit a chat span from the callback handler even when a
|
|
208
|
+
// provider-direct SDK monkey-patch is also active. The two integration
|
|
209
|
+
// layers often run against different @anthropic-ai/sdk instances due to
|
|
210
|
+
// pnpm hoisting; skipping here would silently drop the only chat span.
|
|
211
|
+
// Duplicate-span de-dup is done server-side if ever needed.
|
|
212
|
+
const model = extractParam(extraParams, "model") ??
|
|
213
|
+
extractParam(extraParams?.invocation_params, "model") ??
|
|
214
|
+
extractParam(extraParams?.invocation_params, "modelName") ??
|
|
215
|
+
runName ??
|
|
216
|
+
"unknown";
|
|
217
|
+
const { parentSpan } = this.resolveParent(parentRunId);
|
|
218
|
+
const parentCtx = parentSpan
|
|
219
|
+
? trace.setSpan(otelContext.active(), parentSpan)
|
|
220
|
+
: otelContext.active();
|
|
221
|
+
let span;
|
|
222
|
+
safe(() => {
|
|
223
|
+
span = this.tracer.startSpan(`chat ${model}`, { kind: SpanKind.CLIENT }, parentCtx);
|
|
224
|
+
}, "langchain.handleChatModelStart.create_span", this.internalLogger);
|
|
225
|
+
if (span === undefined)
|
|
226
|
+
return;
|
|
227
|
+
const startedSpan = span;
|
|
228
|
+
const sessionId = this.resolveSessionId(parentRunId, metadata);
|
|
229
|
+
safe(() => {
|
|
230
|
+
startedSpan.setAttribute(GEN_AI.OPERATION_NAME, "chat");
|
|
231
|
+
startedSpan.setAttribute(GEN_AI.PROVIDER_NAME, provider);
|
|
232
|
+
startedSpan.setAttribute(GEN_AI.REQUEST_MODEL, String(model));
|
|
233
|
+
startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
|
|
234
|
+
setRequestAttrsFromInvocation(startedSpan, extraParams?.invocation_params);
|
|
235
|
+
const flat = messages.flat();
|
|
236
|
+
if (flat.length > 0) {
|
|
237
|
+
startedSpan.setAttribute(STRUCT.INPUT_MESSAGE_COUNT, flat.length);
|
|
238
|
+
if (this.sdk.emitEvents && this.logger) {
|
|
239
|
+
emitMessageEvents(this.logger, flat, provider, sessionId, startedSpan);
|
|
240
|
+
}
|
|
241
|
+
if (this.sdk.emitSpanContent) {
|
|
242
|
+
startedSpan.setAttribute(GEN_AI.INPUT_MESSAGES, langchainToInputMessages(flat));
|
|
243
|
+
}
|
|
244
|
+
// Propagate the most recent user message onto the nearest agent span.
|
|
245
|
+
const ancestorAgentSpan = this.resolveParent(parentRunId).parentSpan ?? getAgentSpan();
|
|
246
|
+
if (ancestorAgentSpan)
|
|
247
|
+
this.propagateUserPrompt(ancestorAgentSpan, flat);
|
|
248
|
+
}
|
|
249
|
+
}, "langchain.handleChatModelStart.set_attrs", this.internalLogger);
|
|
250
|
+
this.runs.set(runId, {
|
|
251
|
+
span: startedSpan,
|
|
252
|
+
effectiveParentSpan: startedSpan,
|
|
253
|
+
sessionId,
|
|
254
|
+
nearestAgentSessionId: this.inheritedAgentSessionId(parentRunId),
|
|
255
|
+
kind: "llm",
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
handleLLMStart = (llm, prompts, runId, parentRunId, extraParams, tags, metadata, runName) => {
|
|
259
|
+
// Treat completion-style LLMs as chat with a single user message.
|
|
260
|
+
const messages = prompts.map((p) => [
|
|
261
|
+
{ _getType: () => "human", content: p },
|
|
262
|
+
]);
|
|
263
|
+
this.handleChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, runName);
|
|
264
|
+
};
|
|
265
|
+
handleLLMEnd = (output, runId) => {
|
|
266
|
+
const r = this.runs.get(runId);
|
|
267
|
+
this.runs.delete(runId);
|
|
268
|
+
if (!r || !r.span)
|
|
269
|
+
return;
|
|
270
|
+
const span = r.span;
|
|
271
|
+
const sessionId = r.sessionId;
|
|
272
|
+
safe(() => {
|
|
273
|
+
const gen = output.generations?.[0]?.[0];
|
|
274
|
+
const message = gen?.message;
|
|
275
|
+
if (!message)
|
|
276
|
+
return;
|
|
277
|
+
setLlmResponseAttrs(span, this.sdk, this.logger, message, span.attributes?.[GEN_AI.PROVIDER_NAME], sessionId);
|
|
278
|
+
}, "langchain.handleLLMEnd.set_response_attrs", this.internalLogger);
|
|
279
|
+
safe(() => {
|
|
280
|
+
const gen = output.generations?.[0]?.[0];
|
|
281
|
+
const message = gen?.message;
|
|
282
|
+
if (!message)
|
|
283
|
+
return;
|
|
284
|
+
// Bridge tool_calls into the pending queue so @struct.tool() can autofill.
|
|
285
|
+
const toolCalls = message.tool_calls;
|
|
286
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0)
|
|
287
|
+
return;
|
|
288
|
+
const pairs = [];
|
|
289
|
+
for (const tc of toolCalls) {
|
|
290
|
+
if (tc && typeof tc === "object") {
|
|
291
|
+
const n = tc.name;
|
|
292
|
+
const id = tc.id;
|
|
293
|
+
if (typeof n === "string" && typeof id === "string" && n && id) {
|
|
294
|
+
pairs.push([n, id]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (pairs.length > 0)
|
|
299
|
+
pushPendingToolCalls(pairs);
|
|
300
|
+
}, "langchain.handleLLMEnd.record_pending_tool_calls", this.internalLogger);
|
|
301
|
+
safe(() => span.setStatus({ code: SpanStatusCode.OK }), "langchain.handleLLMEnd.set_status", this.internalLogger);
|
|
302
|
+
safe(() => span.end(), "langchain.handleLLMEnd.span_end", this.internalLogger);
|
|
303
|
+
};
|
|
304
|
+
handleLLMError = (err, runId) => {
|
|
305
|
+
const r = this.runs.get(runId);
|
|
306
|
+
this.runs.delete(runId);
|
|
307
|
+
if (!r || !r.span)
|
|
308
|
+
return;
|
|
309
|
+
const span = r.span;
|
|
310
|
+
safe(() => recordError(span, err), "langchain.handleLLMError.record_error", this.internalLogger);
|
|
311
|
+
safe(() => span.end(), "langchain.handleLLMError.span_end", this.internalLogger);
|
|
312
|
+
};
|
|
313
|
+
// ─── Tool ────────────────────────────────────────────────────────────────
|
|
314
|
+
handleToolStart = (tool, input, runId, parentRunId, _tags, metadata, runName) => {
|
|
315
|
+
const toolName = runName ??
|
|
316
|
+
(typeof tool === "object" && tool?.name) ??
|
|
317
|
+
extractClassName(tool) ??
|
|
318
|
+
"tool";
|
|
319
|
+
const { parentSpan } = this.resolveParent(parentRunId);
|
|
320
|
+
const parentCtx = parentSpan
|
|
321
|
+
? trace.setSpan(otelContext.active(), parentSpan)
|
|
322
|
+
: otelContext.active();
|
|
323
|
+
let span;
|
|
324
|
+
safe(() => {
|
|
325
|
+
span = this.tracer.startSpan(`execute_tool ${toolName}`, { kind: SpanKind.INTERNAL }, parentCtx);
|
|
326
|
+
}, "langchain.handleToolStart.create_span", this.internalLogger);
|
|
327
|
+
if (span === undefined)
|
|
328
|
+
return;
|
|
329
|
+
const startedSpan = span;
|
|
330
|
+
const sessionId = this.resolveSessionId(parentRunId, metadata);
|
|
331
|
+
safe(() => {
|
|
332
|
+
startedSpan.setAttribute(GEN_AI.OPERATION_NAME, "execute_tool");
|
|
333
|
+
startedSpan.setAttribute(GEN_AI.PROVIDER_NAME, "langchain");
|
|
334
|
+
startedSpan.setAttribute(GEN_AI.TOOL_NAME, String(toolName));
|
|
335
|
+
// Tool call id from metadata (LangChain passes it there for ToolCall inputs)
|
|
336
|
+
// or fallback to the pending queue populated by the LLM's tool_calls.
|
|
337
|
+
const callId = metadata?.tool_call_id ??
|
|
338
|
+
extractToolCallIdFromInput(input) ??
|
|
339
|
+
popPendingToolCallId(String(toolName));
|
|
340
|
+
if (callId)
|
|
341
|
+
startedSpan.setAttribute(GEN_AI.TOOL_CALL_ID, callId);
|
|
342
|
+
startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
|
|
343
|
+
if (this.sdk.captureContent && input !== undefined) {
|
|
344
|
+
startedSpan.setAttribute(GEN_AI.TOOL_CALL_ARGUMENTS, safeJsonStringify(input).slice(0, 8192));
|
|
345
|
+
}
|
|
346
|
+
}, "langchain.handleToolStart.set_attrs", this.internalLogger);
|
|
347
|
+
this.runs.set(runId, {
|
|
348
|
+
span: startedSpan,
|
|
349
|
+
effectiveParentSpan: startedSpan,
|
|
350
|
+
sessionId,
|
|
351
|
+
nearestAgentSessionId: this.inheritedAgentSessionId(parentRunId),
|
|
352
|
+
kind: "tool",
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
handleToolEnd = (output, runId) => {
|
|
356
|
+
const r = this.runs.get(runId);
|
|
357
|
+
this.runs.delete(runId);
|
|
358
|
+
if (!r || !r.span)
|
|
359
|
+
return;
|
|
360
|
+
const span = r.span;
|
|
361
|
+
safe(() => {
|
|
362
|
+
if (this.sdk.captureContent && output != null) {
|
|
363
|
+
span.setAttribute(GEN_AI.TOOL_CALL_RESULT, safeJsonStringify(output).slice(0, 8192));
|
|
364
|
+
}
|
|
365
|
+
}, "langchain.handleToolEnd.set_result", this.internalLogger);
|
|
366
|
+
safe(() => span.setStatus({ code: SpanStatusCode.OK }), "langchain.handleToolEnd.set_status", this.internalLogger);
|
|
367
|
+
safe(() => span.end(), "langchain.handleToolEnd.span_end", this.internalLogger);
|
|
368
|
+
};
|
|
369
|
+
handleToolError = (err, runId) => {
|
|
370
|
+
const r = this.runs.get(runId);
|
|
371
|
+
this.runs.delete(runId);
|
|
372
|
+
if (!r || !r.span)
|
|
373
|
+
return;
|
|
374
|
+
const span = r.span;
|
|
375
|
+
safe(() => recordError(span, err), "langchain.handleToolError.record_error", this.internalLogger);
|
|
376
|
+
safe(() => span.end(), "langchain.handleToolError.span_end", this.internalLogger);
|
|
377
|
+
};
|
|
378
|
+
// ─── Retriever ───────────────────────────────────────────────────────────
|
|
379
|
+
handleRetrieverStart = (retriever, query, runId, parentRunId, _tags, metadata, runName) => {
|
|
380
|
+
const name = runName ??
|
|
381
|
+
retriever?.name ??
|
|
382
|
+
extractClassName(retriever) ??
|
|
383
|
+
"retriever";
|
|
384
|
+
const { parentSpan } = this.resolveParent(parentRunId);
|
|
385
|
+
const parentCtx = parentSpan
|
|
386
|
+
? trace.setSpan(otelContext.active(), parentSpan)
|
|
387
|
+
: otelContext.active();
|
|
388
|
+
let span;
|
|
389
|
+
safe(() => {
|
|
390
|
+
span = this.tracer.startSpan(`retrieval ${name}`, { kind: SpanKind.INTERNAL }, parentCtx);
|
|
391
|
+
}, "langchain.handleRetrieverStart.create_span", this.internalLogger);
|
|
392
|
+
if (span === undefined)
|
|
393
|
+
return;
|
|
394
|
+
const startedSpan = span;
|
|
395
|
+
const sessionId = this.resolveSessionId(parentRunId, metadata);
|
|
396
|
+
safe(() => {
|
|
397
|
+
startedSpan.setAttribute(GEN_AI.OPERATION_NAME, "retrieval");
|
|
398
|
+
startedSpan.setAttribute(GEN_AI.PROVIDER_NAME, "langchain");
|
|
399
|
+
startedSpan.setAttribute(GEN_AI.DATA_SOURCE_ID, String(name));
|
|
400
|
+
startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
|
|
401
|
+
if (this.sdk.captureContent && typeof query === "string") {
|
|
402
|
+
startedSpan.setAttribute(GEN_AI.RETRIEVAL_QUERY_TEXT, query.slice(0, 4096));
|
|
403
|
+
}
|
|
404
|
+
}, "langchain.handleRetrieverStart.set_attrs", this.internalLogger);
|
|
405
|
+
this.runs.set(runId, {
|
|
406
|
+
span: startedSpan,
|
|
407
|
+
effectiveParentSpan: startedSpan,
|
|
408
|
+
sessionId,
|
|
409
|
+
nearestAgentSessionId: this.inheritedAgentSessionId(parentRunId),
|
|
410
|
+
kind: "retriever",
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
handleRetrieverEnd = (documents, runId) => {
|
|
414
|
+
const r = this.runs.get(runId);
|
|
415
|
+
this.runs.delete(runId);
|
|
416
|
+
if (!r || !r.span)
|
|
417
|
+
return;
|
|
418
|
+
const span = r.span;
|
|
419
|
+
safe(() => {
|
|
420
|
+
if (Array.isArray(documents)) {
|
|
421
|
+
span.setAttribute(GEN_AI.RETRIEVAL_DOCUMENTS, documents.length);
|
|
422
|
+
}
|
|
423
|
+
}, "langchain.handleRetrieverEnd.set_doc_count", this.internalLogger);
|
|
424
|
+
safe(() => span.setStatus({ code: SpanStatusCode.OK }), "langchain.handleRetrieverEnd.set_status", this.internalLogger);
|
|
425
|
+
safe(() => span.end(), "langchain.handleRetrieverEnd.span_end", this.internalLogger);
|
|
426
|
+
};
|
|
427
|
+
handleRetrieverError = (err, runId) => {
|
|
428
|
+
const r = this.runs.get(runId);
|
|
429
|
+
this.runs.delete(runId);
|
|
430
|
+
if (!r || !r.span)
|
|
431
|
+
return;
|
|
432
|
+
const span = r.span;
|
|
433
|
+
safe(() => recordError(span, err), "langchain.handleRetrieverError.record_error", this.internalLogger);
|
|
434
|
+
safe(() => span.end(), "langchain.handleRetrieverError.span_end", this.internalLogger);
|
|
435
|
+
};
|
|
436
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
437
|
+
resolveParent(parentRunId) {
|
|
438
|
+
if (parentRunId) {
|
|
439
|
+
const p = this.runs.get(parentRunId);
|
|
440
|
+
if (p) {
|
|
441
|
+
// Use the effective parent span, which correctly skips over
|
|
442
|
+
// filtered chains (RunnableSequence, ChannelWrite, ...).
|
|
443
|
+
return {
|
|
444
|
+
parentSpan: p.effectiveParentSpan,
|
|
445
|
+
parentSpanIsAgent: p.kind === "chain",
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return { parentSpan: getAgentSpan(), parentSpanIsAgent: true };
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Conversation-id resolution for chat/tool/retriever spans — INHERIT from
|
|
453
|
+
* the parent agent so everything rolls up under one gen_ai.conversation.id.
|
|
454
|
+
*/
|
|
455
|
+
resolveSessionId(parentRunId, metadata) {
|
|
456
|
+
if (parentRunId) {
|
|
457
|
+
const p = this.runs.get(parentRunId);
|
|
458
|
+
if (p?.sessionId)
|
|
459
|
+
return p.sessionId;
|
|
460
|
+
}
|
|
461
|
+
const threadId = metadata?.thread_id;
|
|
462
|
+
if (typeof threadId === "string" && threadId.length > 0)
|
|
463
|
+
return threadId;
|
|
464
|
+
const ambient = getSessionId();
|
|
465
|
+
if (ambient)
|
|
466
|
+
return ambient;
|
|
467
|
+
return randomUUID();
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Walk the run tree to find the nearest ``invoke_agent`` ancestor's
|
|
471
|
+
* gen_ai.conversation.id.
|
|
472
|
+
*
|
|
473
|
+
* The immediate ``parentRunId`` might be a tool, LLM, retriever, or a
|
|
474
|
+
* filtered RunnableSequence — none of which are agents themselves. We
|
|
475
|
+
* populate ``nearestAgentSessionId`` at each run's creation so any
|
|
476
|
+
* descendant can read it in O(1).
|
|
477
|
+
*
|
|
478
|
+
* Used to set ``struct.agent.parent_session_id`` on nested ``invoke_agent``
|
|
479
|
+
* spans (our UI's "Spawned by" / "Subagents" linkage).
|
|
480
|
+
*/
|
|
481
|
+
inheritedAgentSessionId(parentRunId) {
|
|
482
|
+
if (!parentRunId)
|
|
483
|
+
return undefined;
|
|
484
|
+
return this.runs.get(parentRunId)?.nearestAgentSessionId;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Conversation-id resolution for AGENT spans — each agent invocation gets
|
|
488
|
+
* its own gen_ai.conversation.id. We deliberately do NOT inherit from the
|
|
489
|
+
* parent run, so subagents surface as separate entries in the sessions
|
|
490
|
+
* list. Parent linkage is preserved via `struct.agent.parent_session_id`.
|
|
491
|
+
*
|
|
492
|
+
* LangChain inherits `metadata.thread_id` from the parent invoke's config
|
|
493
|
+
* when a nested invoke runs inside it (even if the child config supplied
|
|
494
|
+
* its own). We detect that inheritance by comparing against the parent's
|
|
495
|
+
* resolved session and ignore the inherited value.
|
|
496
|
+
*/
|
|
497
|
+
resolveAgentSessionId(metadata, parentSessionId) {
|
|
498
|
+
const threadId = metadata?.thread_id;
|
|
499
|
+
if (typeof threadId === "string" && threadId.length > 0) {
|
|
500
|
+
if (parentSessionId && threadId === parentSessionId) {
|
|
501
|
+
// Inherited from parent — treat as unset, assign fresh.
|
|
502
|
+
return randomUUID();
|
|
503
|
+
}
|
|
504
|
+
return threadId;
|
|
505
|
+
}
|
|
506
|
+
const ambient = getSessionId();
|
|
507
|
+
if (ambient)
|
|
508
|
+
return ambient;
|
|
509
|
+
return randomUUID();
|
|
510
|
+
}
|
|
511
|
+
propagateUserPrompt(parentSpan, messages) {
|
|
512
|
+
try {
|
|
513
|
+
const attrs = parentSpan
|
|
514
|
+
.attributes;
|
|
515
|
+
if (attrs && attrs[GEN_AI.INPUT_MESSAGES])
|
|
516
|
+
return;
|
|
517
|
+
const parts = lastUserMessageParts(messages);
|
|
518
|
+
if (!parts)
|
|
519
|
+
return;
|
|
520
|
+
parentSpan.setAttribute(GEN_AI.INPUT_MESSAGES, truncateAndSerialize([{ role: "user", parts }]));
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
/* never fail telemetry */
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
528
|
+
/**
|
|
529
|
+
* Only promote explicit agent classes to `invoke_agent`. LangGraph fires
|
|
530
|
+
* chain-start for every internal node (`ChannelWrite`, `Branch`, `prompt`,
|
|
531
|
+
* `tools`, `__start__`, etc.); those are plumbing, not user-meaningful
|
|
532
|
+
* agent invocations, so we drop them to avoid flooding the waterfall.
|
|
533
|
+
*
|
|
534
|
+
* Users of non-standard agent frameworks can opt in by setting
|
|
535
|
+
* `runType: "agent"` in the Runnable config.
|
|
536
|
+
*/
|
|
537
|
+
const AGENT_CLASSES = new Set([
|
|
538
|
+
"AgentExecutor",
|
|
539
|
+
"CompiledStateGraph",
|
|
540
|
+
"CompiledGraph",
|
|
541
|
+
"Pregel",
|
|
542
|
+
"LangGraph",
|
|
543
|
+
]);
|
|
544
|
+
function isAgentChain(chain, runType, _runName) {
|
|
545
|
+
if (runType === "agent")
|
|
546
|
+
return true;
|
|
547
|
+
const cls = extractClassName(chain);
|
|
548
|
+
return !!cls && AGENT_CLASSES.has(cls);
|
|
549
|
+
}
|
|
550
|
+
function extractClassName(obj) {
|
|
551
|
+
if (!obj)
|
|
552
|
+
return undefined;
|
|
553
|
+
if (Array.isArray(obj.id) && obj.id.length > 0) {
|
|
554
|
+
const last = obj.id[obj.id.length - 1];
|
|
555
|
+
if (typeof last === "string")
|
|
556
|
+
return last;
|
|
557
|
+
}
|
|
558
|
+
if (typeof obj.name === "string")
|
|
559
|
+
return obj.name;
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
function detectProviderFromSerialized(llm) {
|
|
563
|
+
const cls = extractClassName(llm) ?? "";
|
|
564
|
+
const fake = { constructor: { name: cls }, _llmType: undefined };
|
|
565
|
+
return detectProvider(fake);
|
|
566
|
+
}
|
|
567
|
+
function extractParam(obj, key) {
|
|
568
|
+
if (!obj || typeof obj !== "object")
|
|
569
|
+
return undefined;
|
|
570
|
+
const v = obj[key];
|
|
571
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
572
|
+
}
|
|
573
|
+
function setRequestAttrsFromInvocation(span, invocation) {
|
|
574
|
+
if (!invocation || typeof invocation !== "object")
|
|
575
|
+
return;
|
|
576
|
+
const m = invocation;
|
|
577
|
+
const mapping = [
|
|
578
|
+
["temperature", GEN_AI.REQUEST_TEMPERATURE],
|
|
579
|
+
["maxTokens", GEN_AI.REQUEST_MAX_TOKENS],
|
|
580
|
+
["max_tokens", GEN_AI.REQUEST_MAX_TOKENS],
|
|
581
|
+
["topP", GEN_AI.REQUEST_TOP_P],
|
|
582
|
+
["top_p", GEN_AI.REQUEST_TOP_P],
|
|
583
|
+
["topK", GEN_AI.REQUEST_TOP_K],
|
|
584
|
+
["top_k", GEN_AI.REQUEST_TOP_K],
|
|
585
|
+
["frequencyPenalty", GEN_AI.REQUEST_FREQUENCY_PENALTY],
|
|
586
|
+
["presencePenalty", GEN_AI.REQUEST_PRESENCE_PENALTY],
|
|
587
|
+
];
|
|
588
|
+
for (const [src, dst] of mapping) {
|
|
589
|
+
const v = m[src];
|
|
590
|
+
if (typeof v === "number")
|
|
591
|
+
span.setAttribute(dst, v);
|
|
592
|
+
}
|
|
593
|
+
const stop = m.stop ?? m.stopSequences ?? m.stop_sequences;
|
|
594
|
+
if (Array.isArray(stop) && stop.length > 0) {
|
|
595
|
+
span.setAttribute(GEN_AI.REQUEST_STOP_SEQUENCES, stop);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function setLlmResponseAttrs(span, sdk, logger, message, provider, sessionId) {
|
|
599
|
+
if (!message || typeof message !== "object")
|
|
600
|
+
return;
|
|
601
|
+
const m = message;
|
|
602
|
+
const usage = m.usage_metadata;
|
|
603
|
+
if (usage) {
|
|
604
|
+
if (typeof usage.input_tokens === "number")
|
|
605
|
+
span.setAttribute(GEN_AI.USAGE_INPUT_TOKENS, usage.input_tokens);
|
|
606
|
+
if (typeof usage.output_tokens === "number")
|
|
607
|
+
span.setAttribute(GEN_AI.USAGE_OUTPUT_TOKENS, usage.output_tokens);
|
|
608
|
+
const cr = usage.input_token_details?.cache_read;
|
|
609
|
+
const cc = usage.input_token_details?.cache_creation;
|
|
610
|
+
if (typeof cr === "number" && cr > 0)
|
|
611
|
+
span.setAttribute(GEN_AI.USAGE_CACHE_READ_INPUT_TOKENS, cr);
|
|
612
|
+
if (typeof cc === "number" && cc > 0)
|
|
613
|
+
span.setAttribute(GEN_AI.USAGE_CACHE_CREATION_INPUT_TOKENS, cc);
|
|
614
|
+
}
|
|
615
|
+
const respMeta = m.response_metadata ?? {};
|
|
616
|
+
const respModel = (typeof respMeta.model_name === "string" && respMeta.model_name) ||
|
|
617
|
+
(typeof respMeta.model === "string" && respMeta.model) ||
|
|
618
|
+
null;
|
|
619
|
+
if (respModel)
|
|
620
|
+
span.setAttribute(GEN_AI.RESPONSE_MODEL, respModel);
|
|
621
|
+
const finish = (typeof respMeta.finish_reason === "string" && respMeta.finish_reason) ||
|
|
622
|
+
(typeof respMeta.stop_reason === "string" && respMeta.stop_reason) ||
|
|
623
|
+
null;
|
|
624
|
+
if (finish) {
|
|
625
|
+
const mapped = LANGCHAIN_FINISH_REASON_MAP[finish] ?? finish;
|
|
626
|
+
span.setAttribute(GEN_AI.RESPONSE_FINISH_REASONS, [mapped]);
|
|
627
|
+
}
|
|
628
|
+
const respId = (typeof m.id === "string" && m.id) ||
|
|
629
|
+
(typeof respMeta.id === "string" && respMeta.id) ||
|
|
630
|
+
null;
|
|
631
|
+
if (respId)
|
|
632
|
+
span.setAttribute(GEN_AI.RESPONSE_ID, respId);
|
|
633
|
+
if (sdk.emitEvents && logger) {
|
|
634
|
+
emitChoiceEvent(logger, message, provider ?? "langchain", sessionId, span);
|
|
635
|
+
}
|
|
636
|
+
if (sdk.emitSpanContent) {
|
|
637
|
+
span.setAttribute(GEN_AI.OUTPUT_MESSAGES, langchainToOutputMessages(message));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// OTel logs convention for our emitted log events:
|
|
641
|
+
// - Body column = event tag string (human-readable signal)
|
|
642
|
+
// - LogAttributes["body"] = JSON-serialised structured payload
|
|
643
|
+
// Both emitMessageEvents and emitChoiceEvent bypass the central emitLogRecord
|
|
644
|
+
// helper in events.ts because they need to bind a specific span context (the
|
|
645
|
+
// callback handler creates spans directly and doesn't enter them with
|
|
646
|
+
// context.with). The flip is inlined at both sites.
|
|
647
|
+
function emitMessageEvents(logger, messages, provider, sessionId, span) {
|
|
648
|
+
// Bind the log record's context to the chat span so the exported LogRecord
|
|
649
|
+
// carries non-zero TraceId/SpanId. Using `otelContext.active()` here would
|
|
650
|
+
// only work if the caller had entered `context.with(spanCtx, ...)` first —
|
|
651
|
+
// the LangChain callback handler doesn't (it creates the span directly).
|
|
652
|
+
const logCtx = trace.setSpan(otelContext.active(), span);
|
|
653
|
+
for (let i = 0; i < messages.length; i++) {
|
|
654
|
+
const { role, parts } = langchainMessageToRoleAndParts(messages[i]);
|
|
655
|
+
const eventName = ROLE_TO_EVENT_NAME[role] ?? `gen_ai.${role}.message`;
|
|
656
|
+
const payload = safeJsonStringify({ role, parts: truncateParts(parts) });
|
|
657
|
+
logger.emit({
|
|
658
|
+
body: eventName,
|
|
659
|
+
context: logCtx,
|
|
660
|
+
attributes: {
|
|
661
|
+
[EVENT_NAME]: eventName,
|
|
662
|
+
body: payload,
|
|
663
|
+
[GEN_AI.SYSTEM]: provider,
|
|
664
|
+
[GEN_AI.MESSAGE_INDEX]: i,
|
|
665
|
+
[GEN_AI.CONVERSATION_ID]: sessionId,
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function emitChoiceEvent(logger, message, provider, sessionId, span) {
|
|
671
|
+
const { parts } = langchainMessageToRoleAndParts(message);
|
|
672
|
+
const respMeta = (message && typeof message === "object"
|
|
673
|
+
? message.response_metadata
|
|
674
|
+
: undefined) ?? {};
|
|
675
|
+
const finish = (typeof respMeta.finish_reason === "string" && respMeta.finish_reason) ||
|
|
676
|
+
(typeof respMeta.stop_reason === "string" && respMeta.stop_reason) ||
|
|
677
|
+
"stop";
|
|
678
|
+
const mapped = LANGCHAIN_FINISH_REASON_MAP[finish] ?? finish;
|
|
679
|
+
const payload = safeJsonStringify({
|
|
680
|
+
index: 0,
|
|
681
|
+
finish_reason: mapped,
|
|
682
|
+
message: { role: "assistant", parts: truncateParts(parts) },
|
|
683
|
+
});
|
|
684
|
+
logger.emit({
|
|
685
|
+
body: EVENT_NAMES.CHOICE,
|
|
686
|
+
context: trace.setSpan(otelContext.active(), span),
|
|
687
|
+
attributes: {
|
|
688
|
+
[EVENT_NAME]: EVENT_NAMES.CHOICE,
|
|
689
|
+
body: payload,
|
|
690
|
+
[GEN_AI.SYSTEM]: provider,
|
|
691
|
+
[GEN_AI.CONVERSATION_ID]: sessionId,
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
function extractChainInputMessages(inputs) {
|
|
696
|
+
if (!inputs || typeof inputs !== "object")
|
|
697
|
+
return undefined;
|
|
698
|
+
const msgs = inputs.messages;
|
|
699
|
+
if (Array.isArray(msgs))
|
|
700
|
+
return msgs;
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
function extractToolCallIdFromInput(input) {
|
|
704
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
705
|
+
const id = input.id;
|
|
706
|
+
if (typeof id === "string" && id.length > 0)
|
|
707
|
+
return id;
|
|
708
|
+
}
|
|
709
|
+
return undefined;
|
|
710
|
+
}
|
|
711
|
+
function recordError(span, err) {
|
|
712
|
+
const errorType = err instanceof Error ? err.constructor.name : typeof err;
|
|
713
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
714
|
+
span.setAttribute(ERROR_TYPE, errorType);
|
|
715
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
716
|
+
if (err instanceof Error)
|
|
717
|
+
span.recordException(err);
|
|
718
|
+
}
|
|
719
|
+
//# sourceMappingURL=langchain-callback.js.map
|