openlit 1.11.0 → 1.13.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.
Files changed (192) hide show
  1. package/README.md +1 -1
  2. package/dist/config.d.ts +12 -4
  3. package/dist/config.js +7 -17
  4. package/dist/config.js.map +1 -1
  5. package/dist/evals/llm/anthropic.js +10 -6
  6. package/dist/evals/llm/anthropic.js.map +1 -1
  7. package/dist/evals/llm/openai.js +9 -5
  8. package/dist/evals/llm/openai.js.map +1 -1
  9. package/dist/features/vault.js +1 -1
  10. package/dist/features/vault.js.map +1 -1
  11. package/dist/helpers.d.ts +93 -1
  12. package/dist/helpers.js +271 -8
  13. package/dist/helpers.js.map +1 -1
  14. package/dist/index.d.ts +6 -5
  15. package/dist/index.js +95 -50
  16. package/dist/index.js.map +1 -1
  17. package/dist/instrumentation/__tests__/anthropic-wrapper.test.js +215 -27
  18. package/dist/instrumentation/__tests__/anthropic-wrapper.test.js.map +1 -1
  19. package/dist/instrumentation/__tests__/base-wrapper.test.js +19 -23
  20. package/dist/instrumentation/__tests__/base-wrapper.test.js.map +1 -1
  21. package/dist/instrumentation/__tests__/bedrock-trace-comparison.test.d.ts +1 -0
  22. package/dist/instrumentation/__tests__/bedrock-trace-comparison.test.js +422 -0
  23. package/dist/instrumentation/__tests__/bedrock-trace-comparison.test.js.map +1 -0
  24. package/dist/instrumentation/__tests__/chroma-trace-comparison.test.js +1 -1
  25. package/dist/instrumentation/__tests__/chroma-trace-comparison.test.js.map +1 -1
  26. package/dist/instrumentation/__tests__/cohere-wrapper.test.js +150 -25
  27. package/dist/instrumentation/__tests__/cohere-wrapper.test.js.map +1 -1
  28. package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js +152 -33
  29. package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js.map +1 -1
  30. package/dist/instrumentation/__tests__/groq-trace-comparison.test.js +391 -45
  31. package/dist/instrumentation/__tests__/groq-trace-comparison.test.js.map +1 -1
  32. package/dist/instrumentation/__tests__/huggingface-trace-comparison.test.d.ts +2 -2
  33. package/dist/instrumentation/__tests__/huggingface-trace-comparison.test.js +323 -31
  34. package/dist/instrumentation/__tests__/huggingface-trace-comparison.test.js.map +1 -1
  35. package/dist/instrumentation/__tests__/langchain-wrapper.test.d.ts +1 -0
  36. package/dist/instrumentation/__tests__/langchain-wrapper.test.js +282 -0
  37. package/dist/instrumentation/__tests__/langchain-wrapper.test.js.map +1 -0
  38. package/dist/instrumentation/__tests__/milvus-trace-comparison.test.js +1 -1
  39. package/dist/instrumentation/__tests__/milvus-trace-comparison.test.js.map +1 -1
  40. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.d.ts +0 -3
  41. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js +275 -68
  42. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js.map +1 -1
  43. package/dist/instrumentation/__tests__/openai-wrapper.test.js +7 -9
  44. package/dist/instrumentation/__tests__/openai-wrapper.test.js.map +1 -1
  45. package/dist/instrumentation/__tests__/qdrant-trace-comparison.test.js +1 -1
  46. package/dist/instrumentation/__tests__/qdrant-trace-comparison.test.js.map +1 -1
  47. package/dist/instrumentation/__tests__/replicate-trace-comparison.test.d.ts +2 -1
  48. package/dist/instrumentation/__tests__/replicate-trace-comparison.test.js +209 -21
  49. package/dist/instrumentation/__tests__/replicate-trace-comparison.test.js.map +1 -1
  50. package/dist/instrumentation/__tests__/together-trace-comparison.test.js +231 -51
  51. package/dist/instrumentation/__tests__/together-trace-comparison.test.js.map +1 -1
  52. package/dist/instrumentation/__tests__/vercel-ai-trace-comparison.test.d.ts +8 -0
  53. package/dist/instrumentation/__tests__/vercel-ai-trace-comparison.test.js +446 -0
  54. package/dist/instrumentation/__tests__/vercel-ai-trace-comparison.test.js.map +1 -0
  55. package/dist/instrumentation/anthropic/index.d.ts +2 -3
  56. package/dist/instrumentation/anthropic/index.js.map +1 -1
  57. package/dist/instrumentation/anthropic/wrapper.d.ts +1 -3
  58. package/dist/instrumentation/anthropic/wrapper.js +211 -91
  59. package/dist/instrumentation/anthropic/wrapper.js.map +1 -1
  60. package/dist/instrumentation/azure-ai-inference/index.d.ts +11 -0
  61. package/dist/instrumentation/azure-ai-inference/index.js +76 -0
  62. package/dist/instrumentation/azure-ai-inference/index.js.map +1 -0
  63. package/dist/instrumentation/azure-ai-inference/wrapper.d.ts +42 -0
  64. package/dist/instrumentation/azure-ai-inference/wrapper.js +515 -0
  65. package/dist/instrumentation/azure-ai-inference/wrapper.js.map +1 -0
  66. package/dist/instrumentation/base-wrapper.d.ts +2 -1
  67. package/dist/instrumentation/base-wrapper.js +35 -23
  68. package/dist/instrumentation/base-wrapper.js.map +1 -1
  69. package/dist/instrumentation/bedrock/wrapper.d.ts +21 -3
  70. package/dist/instrumentation/bedrock/wrapper.js +318 -265
  71. package/dist/instrumentation/bedrock/wrapper.js.map +1 -1
  72. package/dist/instrumentation/chroma/wrapper.js +1 -1
  73. package/dist/instrumentation/chroma/wrapper.js.map +1 -1
  74. package/dist/instrumentation/claude-agent-sdk/index.d.ts +23 -0
  75. package/dist/instrumentation/claude-agent-sdk/index.js +83 -0
  76. package/dist/instrumentation/claude-agent-sdk/index.js.map +1 -0
  77. package/dist/instrumentation/claude-agent-sdk/wrapper.d.ts +13 -0
  78. package/dist/instrumentation/claude-agent-sdk/wrapper.js +1031 -0
  79. package/dist/instrumentation/claude-agent-sdk/wrapper.js.map +1 -0
  80. package/dist/instrumentation/cohere/index.d.ts +2 -3
  81. package/dist/instrumentation/cohere/index.js.map +1 -1
  82. package/dist/instrumentation/cohere/wrapper.d.ts +1 -1
  83. package/dist/instrumentation/cohere/wrapper.js +215 -56
  84. package/dist/instrumentation/cohere/wrapper.js.map +1 -1
  85. package/dist/instrumentation/cursor-sdk/index.d.ts +21 -0
  86. package/dist/instrumentation/cursor-sdk/index.js +58 -0
  87. package/dist/instrumentation/cursor-sdk/index.js.map +1 -0
  88. package/dist/instrumentation/cursor-sdk/wrapper.d.ts +17 -0
  89. package/dist/instrumentation/cursor-sdk/wrapper.js +689 -0
  90. package/dist/instrumentation/cursor-sdk/wrapper.js.map +1 -0
  91. package/dist/instrumentation/google-adk/index.d.ts +57 -0
  92. package/dist/instrumentation/google-adk/index.js +371 -0
  93. package/dist/instrumentation/google-adk/index.js.map +1 -0
  94. package/dist/instrumentation/google-adk/utils.d.ts +45 -0
  95. package/dist/instrumentation/google-adk/utils.js +663 -0
  96. package/dist/instrumentation/google-adk/utils.js.map +1 -0
  97. package/dist/instrumentation/google-adk/wrapper.d.ts +11 -0
  98. package/dist/instrumentation/google-adk/wrapper.js +391 -0
  99. package/dist/instrumentation/google-adk/wrapper.js.map +1 -0
  100. package/dist/instrumentation/google-ai/wrapper.d.ts +7 -4
  101. package/dist/instrumentation/google-ai/wrapper.js +197 -61
  102. package/dist/instrumentation/google-ai/wrapper.js.map +1 -1
  103. package/dist/instrumentation/groq/wrapper.js +137 -65
  104. package/dist/instrumentation/groq/wrapper.js.map +1 -1
  105. package/dist/instrumentation/huggingface/wrapper.js +241 -39
  106. package/dist/instrumentation/huggingface/wrapper.js.map +1 -1
  107. package/dist/instrumentation/index.d.ts +2 -2
  108. package/dist/instrumentation/index.js +66 -6
  109. package/dist/instrumentation/index.js.map +1 -1
  110. package/dist/instrumentation/langchain/index.d.ts +0 -7
  111. package/dist/instrumentation/langchain/index.js +2 -20
  112. package/dist/instrumentation/langchain/index.js.map +1 -1
  113. package/dist/instrumentation/langchain/wrapper.d.ts +35 -0
  114. package/dist/instrumentation/langchain/wrapper.js +1098 -184
  115. package/dist/instrumentation/langchain/wrapper.js.map +1 -1
  116. package/dist/instrumentation/langgraph/index.d.ts +12 -0
  117. package/dist/instrumentation/langgraph/index.js +99 -0
  118. package/dist/instrumentation/langgraph/index.js.map +1 -0
  119. package/dist/instrumentation/langgraph/wrapper.d.ts +20 -0
  120. package/dist/instrumentation/langgraph/wrapper.js +619 -0
  121. package/dist/instrumentation/langgraph/wrapper.js.map +1 -0
  122. package/dist/instrumentation/llamaindex/index.d.ts +31 -6
  123. package/dist/instrumentation/llamaindex/index.js +180 -61
  124. package/dist/instrumentation/llamaindex/index.js.map +1 -1
  125. package/dist/instrumentation/llamaindex/wrapper.d.ts +15 -3
  126. package/dist/instrumentation/llamaindex/wrapper.js +670 -179
  127. package/dist/instrumentation/llamaindex/wrapper.js.map +1 -1
  128. package/dist/instrumentation/milvus/wrapper.js +1 -1
  129. package/dist/instrumentation/milvus/wrapper.js.map +1 -1
  130. package/dist/instrumentation/mistral/wrapper.js +154 -79
  131. package/dist/instrumentation/mistral/wrapper.js.map +1 -1
  132. package/dist/instrumentation/ollama/index.js +33 -4
  133. package/dist/instrumentation/ollama/index.js.map +1 -1
  134. package/dist/instrumentation/ollama/wrapper.d.ts +28 -2
  135. package/dist/instrumentation/ollama/wrapper.js +432 -48
  136. package/dist/instrumentation/ollama/wrapper.js.map +1 -1
  137. package/dist/instrumentation/openai/index.d.ts +2 -3
  138. package/dist/instrumentation/openai/index.js.map +1 -1
  139. package/dist/instrumentation/openai/wrapper.js +293 -194
  140. package/dist/instrumentation/openai/wrapper.js.map +1 -1
  141. package/dist/instrumentation/openai-agents/index.d.ts +20 -0
  142. package/dist/instrumentation/openai-agents/index.js +174 -0
  143. package/dist/instrumentation/openai-agents/index.js.map +1 -0
  144. package/dist/instrumentation/openai-agents/processor.d.ts +35 -0
  145. package/dist/instrumentation/openai-agents/processor.js +249 -0
  146. package/dist/instrumentation/openai-agents/processor.js.map +1 -0
  147. package/dist/instrumentation/openai-agents/utils.d.ts +20 -0
  148. package/dist/instrumentation/openai-agents/utils.js +624 -0
  149. package/dist/instrumentation/openai-agents/utils.js.map +1 -0
  150. package/dist/instrumentation/pinecone/wrapper.js +2 -2
  151. package/dist/instrumentation/pinecone/wrapper.js.map +1 -1
  152. package/dist/instrumentation/qdrant/wrapper.js +1 -1
  153. package/dist/instrumentation/qdrant/wrapper.js.map +1 -1
  154. package/dist/instrumentation/replicate/wrapper.js +103 -21
  155. package/dist/instrumentation/replicate/wrapper.js.map +1 -1
  156. package/dist/instrumentation/strands/index.d.ts +21 -0
  157. package/dist/instrumentation/strands/index.js +83 -0
  158. package/dist/instrumentation/strands/index.js.map +1 -0
  159. package/dist/instrumentation/strands/processor.d.ts +45 -0
  160. package/dist/instrumentation/strands/processor.js +545 -0
  161. package/dist/instrumentation/strands/processor.js.map +1 -0
  162. package/dist/instrumentation/strands/utils.d.ts +24 -0
  163. package/dist/instrumentation/strands/utils.js +360 -0
  164. package/dist/instrumentation/strands/utils.js.map +1 -0
  165. package/dist/instrumentation/together/wrapper.js +125 -51
  166. package/dist/instrumentation/together/wrapper.js.map +1 -1
  167. package/dist/instrumentation/vercel-ai/wrapper.d.ts +28 -2
  168. package/dist/instrumentation/vercel-ai/wrapper.js +314 -164
  169. package/dist/instrumentation/vercel-ai/wrapper.js.map +1 -1
  170. package/dist/llm/anthropic.js +10 -6
  171. package/dist/llm/anthropic.js.map +1 -1
  172. package/dist/llm/openai.js +9 -5
  173. package/dist/llm/openai.js.map +1 -1
  174. package/dist/otel/__tests__/metrics.test.js +16 -27
  175. package/dist/otel/__tests__/metrics.test.js.map +1 -1
  176. package/dist/otel/events.d.ts +11 -0
  177. package/dist/otel/events.js +74 -0
  178. package/dist/otel/events.js.map +1 -0
  179. package/dist/otel/metrics.d.ts +5 -6
  180. package/dist/otel/metrics.js +66 -48
  181. package/dist/otel/metrics.js.map +1 -1
  182. package/dist/otel/tracing.d.ts +6 -2
  183. package/dist/otel/tracing.js +71 -24
  184. package/dist/otel/tracing.js.map +1 -1
  185. package/dist/otel/utils.d.ts +11 -0
  186. package/dist/otel/utils.js +34 -0
  187. package/dist/otel/utils.js.map +1 -0
  188. package/dist/semantic-convention.d.ts +49 -5
  189. package/dist/semantic-convention.js +56 -8
  190. package/dist/semantic-convention.js.map +1 -1
  191. package/dist/types.d.ts +58 -22
  192. package/package.json +41 -9
@@ -1,108 +1,766 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runWithFrameworkLlm = exports.OpenLITCallbackHandler = void 0;
6
40
  const api_1 = require("@opentelemetry/api");
41
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
7
42
  const config_1 = __importDefault(require("../../config"));
8
- const helpers_1 = __importDefault(require("../../helpers"));
43
+ const helpers_1 = __importStar(require("../../helpers"));
44
+ Object.defineProperty(exports, "runWithFrameworkLlm", { enumerable: true, get: function () { return helpers_1.runWithFrameworkLlm; } });
45
+ const constant_1 = require("../../constant");
9
46
  const semantic_convention_1 = __importDefault(require("../../semantic-convention"));
10
47
  const base_wrapper_1 = __importDefault(require("../base-wrapper"));
11
- // Singleton handler — injected once into every CallbackManager created
48
+ // ---------------------------------------------------------------------------
49
+ // Provider detection (mirrors Python PROVIDER_MAP)
50
+ // ---------------------------------------------------------------------------
51
+ const PROVIDER_MAP = {
52
+ anthropic: 'anthropic',
53
+ azure: 'azure',
54
+ bedrock: 'aws.bedrock',
55
+ bedrock_converse: 'aws.bedrock',
56
+ cohere: 'cohere',
57
+ google: 'google',
58
+ google_genai: 'google',
59
+ google_vertexai: 'google',
60
+ groq: 'groq',
61
+ mistralai: 'mistral_ai',
62
+ ollama: 'ollama',
63
+ openai: 'openai',
64
+ together: 'together',
65
+ vertexai: 'google',
66
+ fireworks: 'fireworks',
67
+ perplexity: 'perplexity',
68
+ huggingface: 'huggingface',
69
+ deepinfra: 'deepinfra',
70
+ anyscale: 'anyscale',
71
+ };
72
+ function detectProvider(serialized) {
73
+ if (!serialized)
74
+ return semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN;
75
+ const classId = serialized.id || [];
76
+ if (Array.isArray(classId)) {
77
+ const classPath = classId.join('.').toLowerCase();
78
+ for (const [key, val] of Object.entries(PROVIDER_MAP)) {
79
+ if (classPath.includes(key))
80
+ return val;
81
+ }
82
+ }
83
+ return semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Model name extraction (mirrors Python extract_model_name)
87
+ // ---------------------------------------------------------------------------
88
+ const MODEL_PATHS_BY_ID = [
89
+ ['ChatGoogleGenerativeAI', ['kwargs', 'model'], 'serialized'],
90
+ ['ChatVertexAI', ['kwargs', 'model_name'], 'serialized'],
91
+ ['ChatMistralAI', ['kwargs', 'model'], 'serialized'],
92
+ ['OpenAI', ['invocation_params', 'model_name'], 'kwargs'],
93
+ ['ChatOpenAI', ['invocation_params', 'model_name'], 'kwargs'],
94
+ ['AzureChatOpenAI', ['invocation_params', 'model'], 'kwargs'],
95
+ ['AzureChatOpenAI', ['invocation_params', 'model_name'], 'kwargs'],
96
+ ['AzureChatOpenAI', ['invocation_params', 'azure_deployment'], 'kwargs'],
97
+ ['HuggingFacePipeline', ['invocation_params', 'model_id'], 'kwargs'],
98
+ ['BedrockChat', ['kwargs', 'model_id'], 'serialized'],
99
+ ['Bedrock', ['kwargs', 'model_id'], 'serialized'],
100
+ ['BedrockLLM', ['kwargs', 'model_id'], 'serialized'],
101
+ ['ChatBedrock', ['kwargs', 'model_id'], 'serialized'],
102
+ ['ChatBedrockConverse', ['kwargs', 'model_id'], 'serialized'],
103
+ ['LlamaCpp', ['invocation_params', 'model_path'], 'kwargs'],
104
+ ['WatsonxLLM', ['invocation_params', 'model_id'], 'kwargs'],
105
+ ];
106
+ const MODEL_PATTERNS = [
107
+ ['ChatAnthropic', 'model', 'anthropic'],
108
+ ['Anthropic', 'model', 'anthropic'],
109
+ ['ChatTongyi', 'model_name', null],
110
+ ['ChatCohere', 'model', null],
111
+ ['Cohere', 'model', null],
112
+ ['HuggingFaceHub', 'model', null],
113
+ ['ChatAnyscale', 'model_name', null],
114
+ ['TextGen', 'model', 'text-gen'],
115
+ ['Ollama', 'model', null],
116
+ ['OllamaLLM', 'model', null],
117
+ ['ChatOllama', 'model', null],
118
+ ['ChatFireworks', 'model', null],
119
+ ['ChatPerplexity', 'model', null],
120
+ ['VLLM', 'model', null],
121
+ ['Xinference', 'model_uid', null],
122
+ ['ChatOCIGenAI', 'model_id', null],
123
+ ['DeepInfra', 'model_id', null],
124
+ ];
125
+ const FALLBACK_PATHS = [
126
+ [['kwargs', 'model_name'], 'serialized'],
127
+ [['kwargs', 'model'], 'serialized'],
128
+ [['kwargs', 'model_id'], 'serialized'],
129
+ [['invocation_params', 'model_name'], 'kwargs'],
130
+ [['invocation_params', 'model'], 'kwargs'],
131
+ [['invocation_params', 'model_id'], 'kwargs'],
132
+ ];
133
+ function _extractByPath(serialized, kwargs, keys, selectFrom) {
134
+ let obj = selectFrom === 'kwargs' ? kwargs : serialized;
135
+ if (obj == null)
136
+ return null;
137
+ for (const key of keys) {
138
+ if (typeof obj === 'object' && obj !== null) {
139
+ obj = obj[key];
140
+ }
141
+ else {
142
+ return null;
143
+ }
144
+ if (obj == null)
145
+ return null;
146
+ }
147
+ return obj ? String(obj) : null;
148
+ }
149
+ function _getClassName(serialized) {
150
+ if (!serialized)
151
+ return null;
152
+ const id = serialized.id;
153
+ if (Array.isArray(id) && id.length > 0)
154
+ return id[id.length - 1];
155
+ return null;
156
+ }
157
+ function _extractModelFromRepr(serialized, pattern) {
158
+ if (!serialized)
159
+ return null;
160
+ const repr = serialized.repr || '';
161
+ if (repr) {
162
+ const re = new RegExp(`${pattern}='(.*?)'`);
163
+ const match = re.exec(repr);
164
+ if (match)
165
+ return match[1];
166
+ }
167
+ return null;
168
+ }
169
+ function extractModelName(serialized, kwargs) {
170
+ const className = _getClassName(serialized);
171
+ for (const [modelId, keys, selectFrom] of MODEL_PATHS_BY_ID) {
172
+ if (className === modelId) {
173
+ const result = _extractByPath(serialized, kwargs, keys, selectFrom);
174
+ if (result)
175
+ return result;
176
+ }
177
+ }
178
+ for (const [modelId, pattern, defaultVal] of MODEL_PATTERNS) {
179
+ if (className === modelId) {
180
+ const result = _extractModelFromRepr(serialized, pattern);
181
+ if (result)
182
+ return result;
183
+ if (defaultVal)
184
+ return defaultVal;
185
+ }
186
+ }
187
+ for (const [keys, selectFrom] of FALLBACK_PATHS) {
188
+ const result = _extractByPath(serialized, kwargs, keys, selectFrom);
189
+ if (result)
190
+ return result;
191
+ }
192
+ return className || 'unknown';
193
+ }
194
+ function extractModelParameters(kwargs) {
195
+ const params = {};
196
+ const ip = kwargs?.invocation_params || {};
197
+ const paramKeys = [
198
+ 'temperature', 'max_tokens', 'max_completion_tokens',
199
+ 'top_p', 'top_k', 'frequency_penalty', 'presence_penalty',
200
+ 'request_timeout', 'stop_sequences', 'seed',
201
+ ];
202
+ for (const key of paramKeys) {
203
+ if (ip[key] != null)
204
+ params[key] = ip[key];
205
+ }
206
+ return params;
207
+ }
208
+ // ---------------------------------------------------------------------------
209
+ // Chain type detection (mirrors Python)
210
+ // ---------------------------------------------------------------------------
211
+ const SKIP_CHAIN_CLASS_PREFIXES = new Set([
212
+ 'RunnableSequence', 'RunnableParallel', 'RunnableLambda',
213
+ 'RunnablePassthrough', 'RunnableAssign', 'RunnablePick',
214
+ 'RunnableBranch', 'RunnableEach', 'Prompt', 'PromptTemplate',
215
+ 'ChatPromptTemplate', 'MessagesPlaceholder',
216
+ 'SystemMessagePromptTemplate', 'HumanMessagePromptTemplate',
217
+ 'AIMessagePromptTemplate', 'BasePromptTemplate',
218
+ 'StrOutputParser', 'JsonOutputParser', 'PydanticOutputParser',
219
+ ]);
220
+ function isInternalChain(serialized, name) {
221
+ if (serialized?.id) {
222
+ const classPath = serialized.id;
223
+ if (Array.isArray(classPath) && classPath.length > 0) {
224
+ const cn = String(classPath[classPath.length - 1]);
225
+ if (SKIP_CHAIN_CLASS_PREFIXES.has(cn))
226
+ return true;
227
+ if (cn.startsWith('Runnable'))
228
+ return true;
229
+ }
230
+ }
231
+ if (SKIP_CHAIN_CLASS_PREFIXES.has(name))
232
+ return true;
233
+ return false;
234
+ }
235
+ function detectObservationType(serialized, callbackType, name) {
236
+ if (callbackType === 'tool')
237
+ return 'tool';
238
+ if (callbackType === 'retriever')
239
+ return 'retriever';
240
+ if (callbackType === 'llm')
241
+ return 'generation';
242
+ if (callbackType === 'chain') {
243
+ if (serialized?.id) {
244
+ const classPath = serialized.id;
245
+ if (classPath.some((part) => String(part).toLowerCase().includes('agent'))) {
246
+ return 'agent';
247
+ }
248
+ }
249
+ if (name && name.toLowerCase().includes('agent'))
250
+ return 'agent';
251
+ return 'chain';
252
+ }
253
+ return 'span';
254
+ }
255
+ // ---------------------------------------------------------------------------
256
+ // Message formatting helpers (mirrors Python utils.py)
257
+ // ---------------------------------------------------------------------------
258
+ const ROLE_MAP = {
259
+ system: 'system',
260
+ human: 'user',
261
+ ai: 'assistant',
262
+ tool: 'tool',
263
+ function: 'tool',
264
+ };
265
+ function buildInputMessagesFromLangChain(messages) {
266
+ try {
267
+ const structured = [];
268
+ for (const msgList of messages) {
269
+ for (const msg of msgList) {
270
+ const role = msg._getType?.() || msg.type || 'user';
271
+ const content = msg.content ?? String(msg);
272
+ const otelRole = ROLE_MAP[role] || 'user';
273
+ structured.push({ role: otelRole, parts: buildParts(content) });
274
+ }
275
+ }
276
+ return structured;
277
+ }
278
+ catch {
279
+ return [];
280
+ }
281
+ }
282
+ function buildInputMessagesFromPrompts(prompts) {
283
+ try {
284
+ return prompts.map(p => ({
285
+ role: 'user',
286
+ parts: [{ type: 'text', content: typeof p === 'string' ? p : String(p) }],
287
+ }));
288
+ }
289
+ catch {
290
+ return [];
291
+ }
292
+ }
293
+ function buildInputMessages(messagesOrPrompts) {
294
+ if (!messagesOrPrompts)
295
+ return [];
296
+ if (Array.isArray(messagesOrPrompts) && messagesOrPrompts.length > 0) {
297
+ const first = messagesOrPrompts[0];
298
+ if (typeof first === 'string')
299
+ return buildInputMessagesFromPrompts(messagesOrPrompts);
300
+ if (Array.isArray(first) && first.length > 0 && first[0]?.content !== undefined) {
301
+ return buildInputMessagesFromLangChain(messagesOrPrompts);
302
+ }
303
+ return buildInputMessagesFromPrompts(messagesOrPrompts);
304
+ }
305
+ return [];
306
+ }
307
+ function buildParts(content) {
308
+ if (typeof content === 'string')
309
+ return [{ type: 'text', content }];
310
+ if (Array.isArray(content)) {
311
+ const parts = [];
312
+ for (const part of content) {
313
+ if (typeof part === 'string') {
314
+ parts.push({ type: 'text', content: part });
315
+ }
316
+ else if (typeof part === 'object' && part !== null) {
317
+ const ptype = part.type || 'text';
318
+ if (ptype === 'text') {
319
+ parts.push({ type: 'text', content: part.text || '' });
320
+ }
321
+ else if (ptype === 'image_url') {
322
+ const url = part.image_url;
323
+ if (typeof url === 'string')
324
+ parts.push({ type: 'image', url });
325
+ else if (url?.url)
326
+ parts.push({ type: 'image', url: url.url });
327
+ }
328
+ else {
329
+ parts.push({ type: ptype, content: String(part) });
330
+ }
331
+ }
332
+ }
333
+ return parts.length > 0 ? parts : [{ type: 'text', content: '' }];
334
+ }
335
+ return [{ type: 'text', content: String(content) }];
336
+ }
337
+ function shouldCaptureMessageContent() {
338
+ return config_1.default.captureMessageContent ?? config_1.default.traceContent ?? true;
339
+ }
340
+ function normalizeToolCalls(rawToolCalls) {
341
+ return rawToolCalls.map((call) => ({
342
+ id: call?.id || call?.tool_call_id || '',
343
+ type: call?.type || 'function',
344
+ name: call?.name || call?.function?.name || '',
345
+ arguments: call?.args ?? call?.arguments ?? call?.function?.arguments ?? {},
346
+ }));
347
+ }
348
+ function stringifyToolCallArgument(value) {
349
+ if (typeof value === 'string')
350
+ return value;
351
+ try {
352
+ return JSON.stringify(value ?? {});
353
+ }
354
+ catch {
355
+ return '[unserializable]';
356
+ }
357
+ }
358
+ // ---------------------------------------------------------------------------
359
+ // Conversation ID extraction (mirrors Python _resolve_conversation_id)
360
+ // ---------------------------------------------------------------------------
361
+ function resolveConversationId(metadata) {
362
+ if (!metadata)
363
+ return null;
364
+ for (const key of ['thread_id', 'conversation_id', 'session_id']) {
365
+ if (metadata[key])
366
+ return String(metadata[key]);
367
+ }
368
+ const configurable = metadata.configurable || {};
369
+ for (const key of ['thread_id', 'conversation_id']) {
370
+ if (configurable[key])
371
+ return String(configurable[key]);
372
+ }
373
+ return null;
374
+ }
375
+ // ---------------------------------------------------------------------------
376
+ // Token calculation (approximate, mirrors Python general_tokens)
377
+ // ---------------------------------------------------------------------------
378
+ function generalTokens(text) {
379
+ return Math.ceil(text.length / 2);
380
+ }
381
+ // ---------------------------------------------------------------------------
382
+ // Callback Handler
383
+ // ---------------------------------------------------------------------------
12
384
  let handlerInstance = null;
13
385
  class OpenLITCallbackHandler {
14
386
  constructor(tracer) {
15
387
  this.name = 'openlit_callback_handler';
16
388
  this.lc_serializable = false;
17
- // LangChain checks this to decide if it should be copied to child runs
18
389
  this.awaitHandlers = false;
19
390
  this.spans = new Map();
391
+ this.skippedRuns = new Map();
20
392
  this.tracer = tracer;
21
393
  }
22
- // ---- Model name helpers --------------------------------------------------
23
- _extractModelName(llm) {
24
- if (!llm)
25
- return 'unknown';
26
- const id = llm.id || [];
27
- const className = id[id.length - 1] || '';
28
- // Try various well-known invocation_params / kwargs paths
29
- const kw = llm.kwargs || {};
30
- const ip = kw.invocation_params || {};
31
- return (ip.model_name || ip.model || ip.model_id ||
32
- kw.model_name || kw.model || kw.model_id ||
33
- className || 'unknown');
34
- }
35
- _detectProvider(llm) {
36
- if (!llm)
37
- return semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN;
38
- const classPath = (llm.id || []).join('.').toLowerCase();
39
- const providerMap = {
40
- openai: semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI,
41
- anthropic: semantic_convention_1.default.GEN_AI_SYSTEM_ANTHROPIC,
42
- bedrock: semantic_convention_1.default.GEN_AI_SYSTEM_AWS_BEDROCK,
43
- google: semantic_convention_1.default.GEN_AI_SYSTEM_VERTEXAI,
44
- cohere: semantic_convention_1.default.GEN_AI_SYSTEM_COHERE,
45
- mistral: semantic_convention_1.default.GEN_AI_SYSTEM_MISTRAL,
46
- };
47
- for (const [key, val] of Object.entries(providerMap)) {
48
- if (classPath.includes(key))
49
- return val;
394
+ // ---- Helpers -----------------------------------------------------------
395
+ _getNameFromCallback(serialized, kwargs) {
396
+ if (kwargs?.name)
397
+ return kwargs.name;
398
+ if (serialized) {
399
+ if (serialized.kwargs?.name)
400
+ return serialized.kwargs.name;
401
+ if (serialized.name)
402
+ return serialized.name;
403
+ if (serialized.id) {
404
+ const id = serialized.id;
405
+ if (Array.isArray(id) && id.length > 0)
406
+ return id[id.length - 1];
407
+ }
50
408
  }
51
- return semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN;
409
+ return 'unknown';
410
+ }
411
+ _resolveParentRunId(parentRunId) {
412
+ const visited = new Set();
413
+ let current = parentRunId;
414
+ while (current && this.skippedRuns.has(current)) {
415
+ if (visited.has(current))
416
+ break;
417
+ visited.add(current);
418
+ current = this.skippedRuns.get(current);
419
+ }
420
+ return current;
52
421
  }
53
- // ---- Parent context -------------------------------------------------------
54
422
  _getParentContext(parentRunId) {
55
- if (!parentRunId)
423
+ const resolved = this._resolveParentRunId(parentRunId);
424
+ if (!resolved)
56
425
  return undefined;
57
- const holder = this.spans.get(parentRunId);
426
+ const holder = this.spans.get(resolved);
58
427
  if (!holder)
59
428
  return undefined;
60
429
  return api_1.trace.setSpan(api_1.context.active(), holder.span);
61
430
  }
62
- // ---- LLM callbacks -------------------------------------------------------
63
- handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, _tags, metadata, name) {
431
+ _createSpan(runId, parentRunId, spanName, kind = api_1.SpanKind.INTERNAL) {
432
+ const resolved = this._resolveParentRunId(parentRunId);
433
+ let parentContext;
434
+ if (resolved && this.spans.has(resolved)) {
435
+ parentContext = api_1.trace.setSpan(api_1.context.active(), this.spans.get(resolved).span);
436
+ }
437
+ return this.tracer.startSpan(spanName, { kind }, parentContext);
438
+ }
439
+ _newHolder(span, parentRunId) {
440
+ return {
441
+ span,
442
+ startTime: Date.now(),
443
+ modelName: 'unknown',
444
+ modelParameters: {},
445
+ provider: '',
446
+ serverAddress: '',
447
+ serverPort: 0,
448
+ parentRunId,
449
+ children: [],
450
+ streamingContent: [],
451
+ tokenTimestamps: [],
452
+ inputTokens: 0,
453
+ outputTokens: 0,
454
+ cacheReadInputTokens: 0,
455
+ cacheCreationInputTokens: 0,
456
+ promptContent: '',
457
+ inputMessagesRaw: null,
458
+ prompts: [],
459
+ inputMessagesStructured: [],
460
+ systemInstructions: null,
461
+ toolDefinitions: null,
462
+ toolCalls: null,
463
+ finishReason: 'stop',
464
+ isAgentChain: false,
465
+ responseId: null,
466
+ suppressionActive: false,
467
+ };
468
+ }
469
+ _setCommonAttributes(span, operationType) {
470
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, operationType);
471
+ span.setAttribute(semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME, constant_1.SDK_NAME);
472
+ span.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment || 'default');
473
+ span.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName || 'default');
474
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION);
475
+ }
476
+ _setModelParameters(span, params) {
477
+ if (params.temperature != null)
478
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, params.temperature);
479
+ if (params.max_tokens != null)
480
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, params.max_tokens);
481
+ if (params.max_completion_tokens != null)
482
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, params.max_completion_tokens);
483
+ if (params.top_p != null)
484
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, params.top_p);
485
+ if (params.top_k != null)
486
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_K, params.top_k);
487
+ if (params.frequency_penalty != null)
488
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY, params.frequency_penalty);
489
+ if (params.presence_penalty != null)
490
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY, params.presence_penalty);
491
+ if (params.seed != null)
492
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SEED, Number(params.seed));
493
+ const stop = params.stop || params.stop_sequences;
494
+ if (stop) {
495
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_STOP_SEQUENCES, Array.isArray(stop) ? stop : [stop]);
496
+ }
497
+ }
498
+ _endSpan(runId, error) {
499
+ const holder = this.spans.get(runId);
500
+ if (!holder)
501
+ return;
502
+ for (const childId of holder.children || []) {
503
+ const child = this.spans.get(childId);
504
+ if (child) {
505
+ try {
506
+ child.span.end();
507
+ }
508
+ catch { /* already ended */ }
509
+ }
510
+ }
511
+ if (error) {
512
+ holder.span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error });
513
+ }
514
+ else {
515
+ holder.span.setStatus({ code: api_1.SpanStatusCode.OK });
516
+ }
517
+ holder.span.end();
518
+ this.spans.delete(runId);
519
+ }
520
+ // ---- Chain Callbacks ---------------------------------------------------
521
+ handleChainStart(chain, inputs, runId, parentRunId, _tags, metadata, _runType, name) {
522
+ try {
523
+ const resolvedName = this._getNameFromCallback(chain, { name });
524
+ const obsType = detectObservationType(chain, 'chain', resolvedName);
525
+ if (obsType !== 'agent' && isInternalChain(chain, resolvedName)) {
526
+ this.skippedRuns.set(runId, parentRunId);
527
+ return;
528
+ }
529
+ if (obsType !== 'agent' && (0, helpers_1.isLangGraphActive)() && !parentRunId) {
530
+ this.skippedRuns.set(runId, parentRunId);
531
+ return;
532
+ }
533
+ if (obsType !== 'agent' && parentRunId) {
534
+ this.skippedRuns.set(runId, parentRunId);
535
+ return;
536
+ }
537
+ const operationType = obsType === 'agent'
538
+ ? semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT
539
+ : semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FRAMEWORK;
540
+ const spanName = obsType === 'agent'
541
+ ? `invoke_agent ${resolvedName}`
542
+ : `invoke_workflow ${resolvedName}`;
543
+ const span = this._createSpan(runId, parentRunId, spanName);
544
+ const holder = this._newHolder(span, parentRunId);
545
+ if (obsType === 'agent')
546
+ holder.isAgentChain = true;
547
+ this.spans.set(runId, holder);
548
+ if (parentRunId) {
549
+ const parentResolved = this._resolveParentRunId(parentRunId);
550
+ if (parentResolved && this.spans.has(parentResolved)) {
551
+ this.spans.get(parentResolved).children.push(runId);
552
+ }
553
+ }
554
+ span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN);
555
+ this._setCommonAttributes(span, operationType);
556
+ if (obsType === 'agent') {
557
+ span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_NAME, resolvedName);
558
+ span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_ID, runId);
559
+ }
560
+ else {
561
+ span.setAttribute(semantic_convention_1.default.GEN_AI_WORKFLOW_NAME, resolvedName);
562
+ }
563
+ const convId = resolveConversationId(metadata);
564
+ if (convId)
565
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, convId);
566
+ if (config_1.default.captureMessageContent && inputs) {
567
+ try {
568
+ const inputStr = typeof inputs === 'string' ? inputs : JSON.stringify(inputs);
569
+ span.setAttribute(semantic_convention_1.default.GEN_AI_WORKFLOW_INPUT, inputStr.slice(0, 2000));
570
+ }
571
+ catch { /* non-blocking */ }
572
+ }
573
+ (0, helpers_1.applyCustomSpanAttributes)(span);
574
+ }
575
+ catch { /* non-blocking */ }
576
+ }
577
+ handleChainEnd(outputs, runId) {
578
+ try {
579
+ this.skippedRuns.delete(runId);
580
+ const holder = this.spans.get(runId);
581
+ if (!holder)
582
+ return;
583
+ const duration = (Date.now() - holder.startTime) / 1000;
584
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration);
585
+ if (config_1.default.captureMessageContent && outputs) {
586
+ try {
587
+ const outputStr = typeof outputs === 'string' ? outputs : JSON.stringify(outputs);
588
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_WORKFLOW_OUTPUT, outputStr.slice(0, 2000));
589
+ }
590
+ catch { /* non-blocking */ }
591
+ }
592
+ this._endSpan(runId);
593
+ }
594
+ catch { /* non-blocking */ }
595
+ }
596
+ handleChainError(error, runId) {
597
+ try {
598
+ this.skippedRuns.delete(runId);
599
+ if (this.spans.has(runId)) {
600
+ const span = this.spans.get(runId).span;
601
+ const errorType = error?.constructor?.name || '_OTHER';
602
+ span.setAttribute(semantic_convention_1.default.ERROR_TYPE, errorType);
603
+ }
604
+ this._endSpan(runId, String(error));
605
+ }
606
+ catch { /* non-blocking */ }
607
+ }
608
+ // ---- LLM Callbacks -----------------------------------------------------
609
+ handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, _tags, metadata, kwargs) {
610
+ try {
611
+ const modelName = extractModelName(llm, kwargs || {});
612
+ const modelParams = extractModelParameters(kwargs || {});
613
+ const provider = detectProvider(llm);
614
+ const spanName = `chat ${modelName}`;
615
+ const span = this._createSpan(runId, parentRunId, spanName, api_1.SpanKind.CLIENT);
616
+ const holder = this._newHolder(span, parentRunId);
617
+ holder.modelName = modelName;
618
+ holder.modelParameters = modelParams;
619
+ holder.provider = provider;
620
+ holder.suppressionActive = true;
621
+ (0, helpers_1.setFrameworkLlmActive)();
622
+ (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(api_1.context.active(), span));
623
+ this.spans.set(runId, holder);
624
+ if (parentRunId) {
625
+ const parentResolved = this._resolveParentRunId(parentRunId);
626
+ if (parentResolved && this.spans.has(parentResolved)) {
627
+ this.spans.get(parentResolved).children.push(runId);
628
+ }
629
+ }
630
+ span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, provider);
631
+ this._setCommonAttributes(span, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
632
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MODEL, modelName);
633
+ this._setModelParameters(span, modelParams);
634
+ const convId = resolveConversationId(metadata);
635
+ if (convId)
636
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, convId);
637
+ // Server address from invocation_params or provider defaults
638
+ const ip = kwargs?.invocation_params || {};
639
+ const apiBase = ip.api_base || ip.base_url;
640
+ if (apiBase) {
641
+ try {
642
+ const url = new URL(apiBase);
643
+ holder.serverAddress = url.hostname || '';
644
+ holder.serverPort = url.port ? Number(url.port) : 443;
645
+ }
646
+ catch { /* ignore */ }
647
+ }
648
+ if (!holder.serverAddress) {
649
+ const [defaultHost, defaultPort] = (0, helpers_1.getServerAddressForProvider)(provider);
650
+ if (defaultHost) {
651
+ holder.serverAddress = defaultHost;
652
+ holder.serverPort = defaultPort;
653
+ }
654
+ }
655
+ if (messages) {
656
+ const formatted = [];
657
+ for (const msgList of messages) {
658
+ for (const msg of msgList) {
659
+ const role = msg._getType?.() || msg.type || 'unknown';
660
+ const content = msg.content ?? String(msg);
661
+ formatted.push(`${role}: ${content}`);
662
+ }
663
+ }
664
+ const promptStr = formatted.join('\n');
665
+ holder.promptContent = promptStr;
666
+ holder.inputTokens = generalTokens(promptStr);
667
+ holder.inputMessagesRaw = messages;
668
+ if (config_1.default.captureMessageContent) {
669
+ // System instructions
670
+ const sysInstructions = [];
671
+ for (const msgList of messages) {
672
+ for (const msg of msgList) {
673
+ const role = msg._getType?.() || msg.type || '';
674
+ if (role === 'system') {
675
+ const content = msg.content ?? '';
676
+ if (content)
677
+ sysInstructions.push({ type: 'text', content: String(content) });
678
+ }
679
+ }
680
+ }
681
+ if (sysInstructions.length > 0) {
682
+ holder.systemInstructions = sysInstructions;
683
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SYSTEM_INSTRUCTIONS, JSON.stringify(sysInstructions));
684
+ }
685
+ // Tool definitions
686
+ const tools = ip.tools || ip.functions;
687
+ if (tools && Array.isArray(tools)) {
688
+ holder.toolDefinitions = tools;
689
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_DEFINITIONS, JSON.stringify(tools));
690
+ }
691
+ // Structured input messages
692
+ try {
693
+ holder.inputMessagesStructured = buildInputMessages(messages);
694
+ }
695
+ catch { /* non-blocking */ }
696
+ }
697
+ }
698
+ (0, helpers_1.applyCustomSpanAttributes)(span);
699
+ }
700
+ catch { /* non-blocking */ }
701
+ }
702
+ handleLLMStart(serialized, prompts, runId, parentRunId, _extraParams, _tags, metadata, kwargs) {
64
703
  try {
65
- const modelName = this._extractModelName(llm);
66
- const provider = this._detectProvider(llm);
704
+ const modelName = extractModelName(serialized, kwargs || {});
705
+ const modelParams = extractModelParameters(kwargs || {});
706
+ const provider = detectProvider(serialized);
67
707
  const spanName = `chat ${modelName}`;
68
- const parentCtx = this._getParentContext(parentRunId);
69
- const span = this.tracer.startSpan(spanName, { kind: api_1.SpanKind.CLIENT }, parentCtx);
70
- span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME, provider);
708
+ const span = this._createSpan(runId, parentRunId, spanName, api_1.SpanKind.CLIENT);
709
+ const holder = this._newHolder(span, parentRunId);
710
+ holder.modelName = modelName;
711
+ holder.modelParameters = modelParams;
712
+ holder.provider = provider;
713
+ holder.prompts = prompts || [];
714
+ holder.suppressionActive = true;
715
+ (0, helpers_1.setFrameworkLlmActive)();
716
+ (0, helpers_1.setFrameworkParentContext)(api_1.trace.setSpan(api_1.context.active(), span));
717
+ this.spans.set(runId, holder);
718
+ if (parentRunId) {
719
+ const parentResolved = this._resolveParentRunId(parentRunId);
720
+ if (parentResolved && this.spans.has(parentResolved)) {
721
+ this.spans.get(parentResolved).children.push(runId);
722
+ }
723
+ }
71
724
  span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, provider);
72
- span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
73
- span.setAttribute(semantic_convention_1.default.GEN_AI_ENVIRONMENT, config_1.default.environment || '');
74
- span.setAttribute(semantic_convention_1.default.GEN_AI_APPLICATION_NAME, config_1.default.applicationName || '');
725
+ this._setCommonAttributes(span, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
75
726
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MODEL, modelName);
76
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, false);
77
- // Model parameters from invocation_params
78
- const ip = llm?.kwargs?.invocation_params || {};
79
- if (ip.temperature != null)
80
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, ip.temperature);
81
- if (ip.max_tokens != null)
82
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, ip.max_tokens);
83
- if (ip.top_p != null)
84
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, ip.top_p);
85
- if (config_1.default.traceContent && messages?.length > 0) {
86
- const flatMessages = messages.flat().map((m) => ({
87
- role: m._getType?.() || m.type || m.role || 'user',
88
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
89
- }));
90
- span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, helpers_1.default.buildInputMessages(flatMessages));
91
- }
92
- this.spans.set(runId, {
93
- span,
94
- startTime: Date.now(),
95
- modelName,
96
- parentRunId,
97
- streamingContent: [],
98
- tokenTimestamps: [],
99
- promptTokens: 0,
100
- completionTokens: 0,
101
- });
727
+ this._setModelParameters(span, modelParams);
728
+ const convId = resolveConversationId(metadata);
729
+ if (convId)
730
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, convId);
731
+ // Server address
732
+ const ip = kwargs?.invocation_params || {};
733
+ const apiBase = ip.api_base || ip.base_url;
734
+ if (apiBase) {
735
+ try {
736
+ const url = new URL(apiBase);
737
+ holder.serverAddress = url.hostname || '';
738
+ holder.serverPort = url.port ? Number(url.port) : 443;
739
+ }
740
+ catch { /* ignore */ }
741
+ }
742
+ if (!holder.serverAddress) {
743
+ const [defaultHost, defaultPort] = (0, helpers_1.getServerAddressForProvider)(provider);
744
+ if (defaultHost) {
745
+ holder.serverAddress = defaultHost;
746
+ holder.serverPort = defaultPort;
747
+ }
748
+ }
749
+ if (prompts && prompts.length > 0) {
750
+ const promptStr = prompts.join('\n');
751
+ holder.inputTokens = generalTokens(promptStr);
752
+ if (config_1.default.captureMessageContent) {
753
+ try {
754
+ holder.inputMessagesStructured = buildInputMessages(prompts);
755
+ }
756
+ catch { /* non-blocking */ }
757
+ }
758
+ }
759
+ (0, helpers_1.applyCustomSpanAttributes)(span);
102
760
  }
103
761
  catch { /* non-blocking */ }
104
762
  }
105
- handleLLMNewToken(token, _idx, runId) {
763
+ handleLLMNewToken(token, _idx, runId, _chunk) {
106
764
  try {
107
765
  const holder = this.spans.get(runId);
108
766
  if (!holder)
@@ -113,194 +771,451 @@ class OpenLITCallbackHandler {
113
771
  holder.tokenTimestamps.push(now);
114
772
  if (token)
115
773
  holder.streamingContent.push(token);
116
- // Mark as streaming
117
- holder.span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, true);
118
774
  }
119
775
  catch { /* non-blocking */ }
120
776
  }
121
777
  handleLLMEnd(output, runId) {
122
- this._finalizeLLMSpan(runId, output, undefined);
123
- }
124
- handleLLMError(error, runId) {
125
778
  try {
126
779
  const holder = this.spans.get(runId);
127
780
  if (!holder)
128
781
  return;
129
- helpers_1.default.handleException(holder.span, error);
130
- holder.span.end();
131
- this.spans.delete(runId);
132
- }
133
- catch { /* non-blocking */ }
134
- }
135
- async _finalizeLLMSpan(runId, output, _error) {
136
- try {
137
- const holder = this.spans.get(runId);
138
- if (!holder)
139
- return;
140
- const { span, startTime, modelName, streamingContent, tokenTimestamps, firstTokenTime } = holder;
782
+ const { span, startTime, modelName, modelParameters = {}, streamingContent = [], tokenTimestamps = [], firstTokenTime, } = holder;
141
783
  const endTime = Date.now();
784
+ const duration = (endTime - startTime) / 1000;
785
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration);
142
786
  const isStreaming = streamingContent.length > 0;
143
- // Extract tokens from output
144
- let promptTokens = 0;
145
- let completionTokens = 0;
146
- let completionContent = '';
147
- let finishReason = 'stop';
787
+ let ttft = 0;
788
+ let tbt = 0;
789
+ if (isStreaming) {
790
+ if (firstTokenTime)
791
+ ttft = (firstTokenTime - startTime) / 1000;
792
+ if (tokenTimestamps.length > 1) {
793
+ const diffs = tokenTimestamps.slice(1).map((t, i) => t - tokenTimestamps[i]);
794
+ tbt = diffs.reduce((a, b) => a + b, 0) / diffs.length / 1000;
795
+ }
796
+ }
797
+ else {
798
+ ttft = duration;
799
+ }
800
+ // Extract tokens and content from output
801
+ let inputTokens = holder.inputTokens;
802
+ let outputTokens = 0;
803
+ let completionContent = streamingContent.join('');
148
804
  let responseModel = modelName;
805
+ let responseId = null;
806
+ // From llm_output (top-level)
149
807
  if (output?.llm_output) {
150
808
  const lu = output.llm_output;
151
809
  const tu = lu.token_usage || lu.usage || {};
152
- promptTokens = tu.prompt_tokens || tu.input_tokens || 0;
153
- completionTokens = tu.completion_tokens || tu.output_tokens || 0;
810
+ inputTokens = tu.prompt_tokens || tu.input_tokens || inputTokens;
811
+ outputTokens = tu.completion_tokens || tu.output_tokens || outputTokens;
154
812
  responseModel = lu.model_name || lu.model || modelName;
813
+ responseId = lu.id || null;
814
+ // Cache token extraction
815
+ const promptDetails = tu.prompt_tokens_details || tu.input_tokens_details || {};
816
+ let cached = promptDetails.cached_tokens || 0;
817
+ const inputDetails = tu.input_tokens_details || {};
818
+ let creation = inputDetails.cache_creation_tokens || 0;
819
+ const langchainInput = tu.input_token_details || {};
820
+ if (!cached)
821
+ cached = langchainInput.cache_read || 0;
822
+ if (!creation)
823
+ creation = langchainInput.cache_creation || 0;
824
+ holder.cacheReadInputTokens = cached;
825
+ holder.cacheCreationInputTokens = creation;
155
826
  }
827
+ // From generations
156
828
  const generations = output?.generations || [];
157
829
  for (const genList of generations) {
158
830
  for (const gen of (Array.isArray(genList) ? genList : [genList])) {
159
831
  const msg = gen?.message || gen;
160
- // From usage_metadata (standard LangChain)
832
+ // Usage from usage_metadata
161
833
  const um = msg?.usage_metadata;
162
834
  if (um) {
163
- if (!promptTokens)
164
- promptTokens = um.input_tokens || um.prompt_tokens || 0;
165
- if (!completionTokens)
166
- completionTokens = um.output_tokens || um.completion_tokens || 0;
835
+ if (!inputTokens)
836
+ inputTokens = um.input_tokens || um.prompt_tokens || 0;
837
+ if (!outputTokens)
838
+ outputTokens = um.output_tokens || um.completion_tokens || 0;
839
+ // Cache tokens from usage_metadata
840
+ const pd = um.prompt_tokens_details || um.input_tokens_details || {};
841
+ if (!holder.cacheReadInputTokens)
842
+ holder.cacheReadInputTokens = pd.cached_tokens || 0;
843
+ const langchainInput = um.input_token_details || {};
844
+ if (!holder.cacheReadInputTokens)
845
+ holder.cacheReadInputTokens = langchainInput.cache_read || 0;
846
+ if (!holder.cacheCreationInputTokens)
847
+ holder.cacheCreationInputTokens = langchainInput.cache_creation || 0;
848
+ }
849
+ // Token usage from response_metadata
850
+ if (msg?.response_metadata) {
851
+ const rm = msg.response_metadata;
852
+ const tokenUsage = rm.token_usage;
853
+ if (tokenUsage) {
854
+ inputTokens = tokenUsage.prompt_tokens || tokenUsage.input_tokens || inputTokens;
855
+ outputTokens = tokenUsage.completion_tokens || tokenUsage.output_tokens || outputTokens;
856
+ }
857
+ if (rm.usage) {
858
+ inputTokens = rm.usage.inputTokens || rm.usage.input_tokens || inputTokens;
859
+ outputTokens = rm.usage.outputTokens || rm.usage.output_tokens || outputTokens;
860
+ }
861
+ if (rm['amazon-bedrock-invocationMetrics']) {
862
+ const bm = rm['amazon-bedrock-invocationMetrics'];
863
+ inputTokens = bm.inputTokenCount || inputTokens;
864
+ outputTokens = bm.outputTokenCount || outputTokens;
865
+ }
167
866
  }
168
867
  // Content
169
- const content = gen?.text || msg?.content || '';
170
- if (content && typeof content === 'string' && content.length > completionContent.length) {
171
- completionContent = content;
868
+ const genContent = gen?.text || msg?.content;
869
+ if (genContent && typeof genContent === 'string' && genContent.length > completionContent.length) {
870
+ completionContent = genContent;
172
871
  }
173
872
  // Finish reason
174
873
  const fr = gen?.generationInfo?.finish_reason || msg?.response_metadata?.finish_reason;
175
874
  if (fr)
176
- finishReason = fr;
875
+ holder.finishReason = fr;
876
+ // Tool calls. Keep last-writer-wins semantics across generations so
877
+ // choices are not merged into one assistant message.
878
+ const tc = msg?.tool_calls || msg?.additional_kwargs?.tool_calls || gen?.message?.tool_calls;
879
+ if (tc && Array.isArray(tc) && tc.length > 0) {
880
+ holder.toolCalls = normalizeToolCalls(tc);
881
+ }
177
882
  }
178
883
  }
179
- if (isStreaming) {
180
- completionContent = streamingContent.join('');
181
- }
182
- const totalTokens = promptTokens + completionTokens;
183
- const duration = (endTime - startTime) / 1000;
184
- const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
185
- const cost = helpers_1.default.getChatModelCost(responseModel, pricingInfo, promptTokens, completionTokens);
884
+ // Fallback token estimation
885
+ if (!outputTokens && completionContent)
886
+ outputTokens = generalTokens(completionContent);
887
+ if (!inputTokens && holder.promptContent)
888
+ inputTokens = generalTokens(holder.promptContent);
889
+ // Cost
890
+ const pricingInfo = config_1.default.pricingInfo || {};
891
+ const cost = helpers_1.default.getChatModelCost(modelName, pricingInfo, inputTokens, outputTokens);
892
+ // Provider for span attributes
893
+ const provider = holder.provider || semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN;
894
+ const serverAddress = holder.serverAddress || '';
895
+ const serverPort = holder.serverPort || 0;
896
+ // Set span attributes
186
897
  span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
187
- span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, promptTokens);
188
- span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, completionTokens);
189
- span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, totalTokens);
190
- span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, totalTokens);
898
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
899
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
900
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, inputTokens + outputTokens);
191
901
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COST, cost);
192
- span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [finishReason]);
193
- span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT);
194
- span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration);
195
- if (isStreaming && firstTokenTime) {
196
- const ttft = (firstTokenTime - startTime) / 1000;
902
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [holder.finishReason]);
903
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, typeof completionContent === 'string' ? 'text' : 'json');
904
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, isStreaming);
905
+ if (responseId)
906
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, responseId);
907
+ if (serverAddress) {
908
+ span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, serverAddress);
909
+ span.setAttribute(semantic_convention_1.default.SERVER_PORT, serverPort);
910
+ }
911
+ if (isStreaming && ttft > 0) {
197
912
  span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
198
- if (tokenTimestamps.length > 1) {
199
- const diffs = tokenTimestamps.slice(1).map((t, i) => t - tokenTimestamps[i]);
200
- const tbt = diffs.reduce((a, b) => a + b, 0) / diffs.length / 1000;
913
+ if (tbt > 0)
201
914
  span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
915
+ }
916
+ // Cache tokens
917
+ if (holder.cacheReadInputTokens) {
918
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, holder.cacheReadInputTokens);
919
+ }
920
+ if (holder.cacheCreationInputTokens) {
921
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, holder.cacheCreationInputTokens);
922
+ }
923
+ // Tool calls on span
924
+ if (holder.toolCalls && holder.toolCalls.length > 0) {
925
+ const names = holder.toolCalls.map((t) => t.name || t.function?.name || '').filter(Boolean);
926
+ const ids = holder.toolCalls.map((t) => t.id || '').filter(Boolean);
927
+ const args = holder.toolCalls.map((t) => stringifyToolCallArgument(t.arguments ?? t.function?.arguments));
928
+ if (names.length)
929
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, names.join(', '));
930
+ if (ids.length)
931
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, ids.join(', '));
932
+ if (args.length)
933
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, args);
934
+ }
935
+ let outputMessagesJson = null;
936
+ // Content attributes (gated by captureMessageContent)
937
+ if (shouldCaptureMessageContent()) {
938
+ const inputRaw = holder.inputMessagesRaw || holder.prompts || [];
939
+ const inputMessagesStructured = holder.inputMessagesStructured || [];
940
+ const inputMsgs = inputMessagesStructured.length > 0
941
+ ? inputMessagesStructured
942
+ : buildInputMessages(inputRaw);
943
+ const outputToolCalls = holder.toolCalls && holder.toolCalls.length > 0 ? holder.toolCalls : undefined;
944
+ outputMessagesJson = helpers_1.default.buildOutputMessages(completionContent, holder.finishReason, outputToolCalls);
945
+ if (inputMsgs.length > 0)
946
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, JSON.stringify(inputMsgs));
947
+ if (outputMessagesJson && (outputMessagesJson !== '[]' || completionContent || outputToolCalls)) {
948
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, outputMessagesJson);
202
949
  }
203
950
  }
204
- if (config_1.default.traceContent && completionContent) {
205
- span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, helpers_1.default.buildOutputMessages(completionContent, finishReason));
951
+ // Emit inference event (always emitted; content within is gated)
952
+ const eventAttrs = {
953
+ [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT,
954
+ [semantic_convention_1.default.GEN_AI_REQUEST_MODEL]: modelName,
955
+ [semantic_convention_1.default.GEN_AI_RESPONSE_MODEL]: responseModel,
956
+ [semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS]: inputTokens,
957
+ [semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS]: outputTokens,
958
+ [semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON]: [holder.finishReason],
959
+ [semantic_convention_1.default.GEN_AI_OUTPUT_TYPE]: typeof completionContent === 'string' ? 'text' : 'json',
960
+ };
961
+ if (serverAddress)
962
+ eventAttrs[semantic_convention_1.default.SERVER_ADDRESS] = serverAddress;
963
+ if (serverPort)
964
+ eventAttrs[semantic_convention_1.default.SERVER_PORT] = serverPort;
965
+ if (responseId)
966
+ eventAttrs[semantic_convention_1.default.GEN_AI_RESPONSE_ID] = responseId;
967
+ if (shouldCaptureMessageContent()) {
968
+ const inputRaw = holder.inputMessagesRaw || holder.prompts || [];
969
+ const inputMessagesStructured = holder.inputMessagesStructured || [];
970
+ const inputMsgs = inputMessagesStructured.length > 0
971
+ ? inputMessagesStructured
972
+ : buildInputMessages(inputRaw);
973
+ if (inputMsgs.length > 0)
974
+ eventAttrs[semantic_convention_1.default.GEN_AI_INPUT_MESSAGES] = JSON.stringify(inputMsgs);
975
+ if (outputMessagesJson && outputMessagesJson !== '[]')
976
+ eventAttrs[semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES] = outputMessagesJson;
977
+ if (holder.systemInstructions) {
978
+ eventAttrs[semantic_convention_1.default.GEN_AI_SYSTEM_INSTRUCTIONS] = JSON.stringify(holder.systemInstructions);
979
+ }
980
+ if (holder.toolDefinitions) {
981
+ eventAttrs[semantic_convention_1.default.GEN_AI_TOOL_DEFINITIONS] = JSON.stringify(holder.toolDefinitions);
982
+ }
983
+ // Request params for event
984
+ for (const [k, attr] of [
985
+ ['temperature', semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE],
986
+ ['top_p', semantic_convention_1.default.GEN_AI_REQUEST_TOP_P],
987
+ ['frequency_penalty', semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY],
988
+ ['presence_penalty', semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY],
989
+ ]) {
990
+ if (modelParameters[k] != null)
991
+ eventAttrs[attr] = modelParameters[k];
992
+ }
993
+ const maxT = modelParameters.max_tokens || modelParameters.max_completion_tokens;
994
+ if (maxT != null)
995
+ eventAttrs[semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS] = maxT;
206
996
  }
207
- // base attributes (system, endpoint, etc.)
208
- base_wrapper_1.default.setBaseSpanAttributes(span, {
209
- genAIEndpoint: `langchain.chat_model`,
210
- model: responseModel,
211
- cost,
212
- aiSystem: semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN,
213
- serverAddress: 'localhost',
214
- serverPort: 80,
215
- });
997
+ if (typeof helpers_1.default.emitInferenceEvent === 'function') {
998
+ helpers_1.default.emitInferenceEvent(span, eventAttrs);
999
+ }
1000
+ // Record metrics
216
1001
  const metricParams = {
217
1002
  genAIEndpoint: 'langchain.chat_model',
218
1003
  model: responseModel,
219
1004
  cost,
220
- aiSystem: semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN,
221
- serverAddress: 'localhost',
222
- serverPort: 80,
1005
+ aiSystem: provider,
1006
+ serverAddress,
1007
+ serverPort,
223
1008
  };
224
- span.setStatus({ code: 1 });
225
- span.end();
226
1009
  base_wrapper_1.default.recordMetrics(span, metricParams);
227
- this.spans.delete(runId);
1010
+ if (holder.suppressionActive) {
1011
+ (0, helpers_1.resetFrameworkLlmActive)();
1012
+ (0, helpers_1.clearFrameworkParentContext)();
1013
+ }
1014
+ this._endSpan(runId);
1015
+ }
1016
+ catch { /* non-blocking */ }
1017
+ }
1018
+ handleLLMError(error, runId) {
1019
+ try {
1020
+ const holder = this.spans.get(runId);
1021
+ if (!holder)
1022
+ return;
1023
+ if (holder.suppressionActive) {
1024
+ (0, helpers_1.resetFrameworkLlmActive)();
1025
+ (0, helpers_1.clearFrameworkParentContext)();
1026
+ }
1027
+ const errorType = error?.constructor?.name || '_OTHER';
1028
+ holder.span.setAttribute(semantic_convention_1.default.ERROR_TYPE, errorType);
1029
+ helpers_1.default.handleException(holder.span, error instanceof Error ? error : new Error(String(error)));
1030
+ this._endSpan(runId, String(error));
228
1031
  }
229
1032
  catch { /* non-blocking */ }
230
1033
  }
231
- // ---- Chain callbacks -------------------------------------------------------
232
- handleChainStart(chain, inputs, runId, parentRunId) {
1034
+ // ---- Tool Callbacks ----------------------------------------------------
1035
+ handleToolStart(tool, input, runId, parentRunId, _tags, metadata, kwargs) {
233
1036
  try {
234
- const id = chain?.id || [];
235
- const name = id[id.length - 1] || 'chain';
236
- const spanName = `workflow ${name}`;
237
- const parentCtx = this._getParentContext(parentRunId);
238
- const span = this.tracer.startSpan(spanName, { kind: api_1.SpanKind.INTERNAL }, parentCtx);
239
- span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME, semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN);
1037
+ const name = this._getNameFromCallback(tool, kwargs || {});
1038
+ const spanName = `execute_tool ${name}`;
1039
+ const span = this._createSpan(runId, parentRunId, spanName);
1040
+ const holder = this._newHolder(span, parentRunId);
1041
+ this.spans.set(runId, holder);
1042
+ if (parentRunId) {
1043
+ const parentResolved = this._resolveParentRunId(parentRunId);
1044
+ if (parentResolved && this.spans.has(parentResolved)) {
1045
+ this.spans.get(parentResolved).children.push(runId);
1046
+ }
1047
+ }
240
1048
  span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN);
241
- span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_FRAMEWORK);
242
- span.setAttribute(semantic_convention_1.default.GEN_AI_ENVIRONMENT, config_1.default.environment || '');
243
- span.setAttribute(semantic_convention_1.default.GEN_AI_APPLICATION_NAME, config_1.default.applicationName || '');
244
- if (config_1.default.traceContent && inputs) {
245
- span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, JSON.stringify(inputs).slice(0, 2000));
246
- }
247
- this.spans.set(runId, {
248
- span,
249
- startTime: Date.now(),
250
- modelName: name,
251
- parentRunId,
252
- streamingContent: [],
253
- tokenTimestamps: [],
254
- promptTokens: 0,
255
- completionTokens: 0,
256
- });
1049
+ this._setCommonAttributes(span, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_TOOLS);
1050
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, name);
1051
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE_OTEL, 'function');
1052
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, runId);
1053
+ const description = tool?.description;
1054
+ if (description)
1055
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_DESCRIPTION, String(description));
1056
+ const convId = resolveConversationId(metadata);
1057
+ if (convId)
1058
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, convId);
1059
+ if (config_1.default.captureMessageContent && input) {
1060
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, String(input).slice(0, 2000));
1061
+ }
1062
+ (0, helpers_1.applyCustomSpanAttributes)(span);
257
1063
  }
258
1064
  catch { /* non-blocking */ }
259
1065
  }
260
- handleChainEnd(outputs, runId) {
1066
+ handleToolEnd(output, runId) {
261
1067
  try {
262
1068
  const holder = this.spans.get(runId);
263
1069
  if (!holder)
264
1070
  return;
265
1071
  const duration = (Date.now() - holder.startTime) / 1000;
266
1072
  holder.span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration);
267
- if (config_1.default.traceContent && outputs) {
268
- holder.span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, JSON.stringify(outputs).slice(0, 2000));
1073
+ // Extract tool_call_id from output if available
1074
+ if (output?.tool_call_id) {
1075
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, output.tool_call_id);
269
1076
  }
270
- holder.span.setStatus({ code: 1 });
271
- holder.span.end();
272
- this.spans.delete(runId);
1077
+ if (config_1.default.captureMessageContent && output) {
1078
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_RESULT, String(output).slice(0, 2000));
1079
+ }
1080
+ this._endSpan(runId);
273
1081
  }
274
1082
  catch { /* non-blocking */ }
275
1083
  }
276
- handleChainError(error, runId) {
1084
+ handleToolError(error, runId) {
1085
+ try {
1086
+ if (this.spans.has(runId)) {
1087
+ const span = this.spans.get(runId).span;
1088
+ const errorType = error?.constructor?.name || '_OTHER';
1089
+ span.setAttribute(semantic_convention_1.default.ERROR_TYPE, errorType);
1090
+ }
1091
+ this._endSpan(runId, String(error));
1092
+ }
1093
+ catch { /* non-blocking */ }
1094
+ }
1095
+ // ---- Retriever Callbacks -----------------------------------------------
1096
+ handleRetrieverStart(retriever, query, runId, parentRunId, _tags, metadata, kwargs) {
1097
+ try {
1098
+ const name = this._getNameFromCallback(retriever, kwargs || {});
1099
+ const spanName = `retrieval ${name}`;
1100
+ const span = this._createSpan(runId, parentRunId, spanName, api_1.SpanKind.CLIENT);
1101
+ const holder = this._newHolder(span, parentRunId);
1102
+ this.spans.set(runId, holder);
1103
+ if (parentRunId) {
1104
+ const parentResolved = this._resolveParentRunId(parentRunId);
1105
+ if (parentResolved && this.spans.has(parentResolved)) {
1106
+ this.spans.get(parentResolved).children.push(runId);
1107
+ }
1108
+ }
1109
+ span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, semantic_convention_1.default.GEN_AI_SYSTEM_LANGCHAIN);
1110
+ this._setCommonAttributes(span, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_RETRIEVE);
1111
+ span.setAttribute(semantic_convention_1.default.GEN_AI_DATA_SOURCE_ID, name);
1112
+ const convId = resolveConversationId(metadata);
1113
+ if (convId)
1114
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, convId);
1115
+ if (config_1.default.captureMessageContent && query) {
1116
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RETRIEVAL_QUERY_TEXT, String(query).slice(0, 2000));
1117
+ }
1118
+ (0, helpers_1.applyCustomSpanAttributes)(span);
1119
+ }
1120
+ catch { /* non-blocking */ }
1121
+ }
1122
+ handleRetrieverEnd(documents, runId) {
277
1123
  try {
278
1124
  const holder = this.spans.get(runId);
279
1125
  if (!holder)
280
1126
  return;
281
- helpers_1.default.handleException(holder.span, error);
282
- holder.span.end();
283
- this.spans.delete(runId);
1127
+ const duration = (Date.now() - holder.startTime) / 1000;
1128
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration);
1129
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_RETRIEVAL_DOCUMENT_COUNT, documents?.length || 0);
1130
+ if (config_1.default.captureMessageContent && documents?.length > 0) {
1131
+ const structured = documents.slice(0, 3).map((doc) => {
1132
+ const content = doc?.pageContent || doc?.page_content || String(doc);
1133
+ const entry = { content: String(content).slice(0, 2000) };
1134
+ const meta = doc?.metadata;
1135
+ if (meta) {
1136
+ const docId = meta.id || meta.source;
1137
+ if (docId)
1138
+ entry.id = String(docId);
1139
+ }
1140
+ return entry;
1141
+ });
1142
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_RETRIEVAL_DOCUMENTS, JSON.stringify(structured));
1143
+ }
1144
+ this._endSpan(runId);
1145
+ }
1146
+ catch { /* non-blocking */ }
1147
+ }
1148
+ handleRetrieverError(error, runId) {
1149
+ try {
1150
+ if (this.spans.has(runId)) {
1151
+ const span = this.spans.get(runId).span;
1152
+ const errorType = error?.constructor?.name || '_OTHER';
1153
+ span.setAttribute(semantic_convention_1.default.ERROR_TYPE, errorType);
1154
+ }
1155
+ this._endSpan(runId, String(error));
1156
+ }
1157
+ catch { /* non-blocking */ }
1158
+ }
1159
+ // ---- Agent Callbacks ---------------------------------------------------
1160
+ handleAgentAction(action, runId) {
1161
+ try {
1162
+ const holder = this.spans.get(runId);
1163
+ if (!holder)
1164
+ return;
1165
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT);
1166
+ if (config_1.default.captureMessageContent) {
1167
+ const tool = action?.tool ?? String(action);
1168
+ const toolInput = action?.tool_input ?? '';
1169
+ const log = action?.log ?? '';
1170
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_ACTION_TOOL, String(tool).slice(0, 2000));
1171
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_ACTION_TOOL_INPUT, String(toolInput).slice(0, 2000));
1172
+ if (log)
1173
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_ACTION_LOG, String(log).slice(0, 2000));
1174
+ }
1175
+ }
1176
+ catch { /* non-blocking */ }
1177
+ }
1178
+ handleAgentFinish(finish, runId) {
1179
+ try {
1180
+ const holder = this.spans.get(runId);
1181
+ if (!holder)
1182
+ return;
1183
+ if (config_1.default.captureMessageContent) {
1184
+ const output = finish?.return_values ?? String(finish);
1185
+ const log = finish?.log ?? '';
1186
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_FINISH_OUTPUT, String(output).slice(0, 2000));
1187
+ if (log)
1188
+ holder.span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_FINISH_LOG, String(log).slice(0, 2000));
1189
+ }
284
1190
  }
285
1191
  catch { /* non-blocking */ }
286
1192
  }
287
1193
  }
288
- // ----------------------------------------------------------------------------
289
- // Wrapper factory
290
- // ----------------------------------------------------------------------------
1194
+ exports.OpenLITCallbackHandler = OpenLITCallbackHandler;
1195
+ // ---------------------------------------------------------------------------
1196
+ // Wrapper factory that patches CallbackManager._configureSync
1197
+ // ---------------------------------------------------------------------------
291
1198
  class LangChainWrapper extends base_wrapper_1.default {
292
1199
  static _patchConfigure(tracer) {
293
- // Ensure singleton handler
294
1200
  if (!handlerInstance) {
295
1201
  handlerInstance = new OpenLITCallbackHandler(tracer);
296
1202
  }
297
1203
  const handler = handlerInstance;
1204
+ /**
1205
+ * The LLM call itself must run inside runWithFrameworkLlm() so that
1206
+ * provider wrappers (OpenAI, Anthropic, etc.) skip their own span
1207
+ * creation. We achieve this by wrapping _configureSync: when
1208
+ * LangChain configures callbacks for an LLM call, the callback
1209
+ * handler's handleChatModelStart/handleLLMStart already sets the
1210
+ * suppression flag via the span holder. But to actually suppress
1211
+ * the provider wrapper, we need the flag in the AsyncLocalStorage
1212
+ * context of the LLM call. Since LangChain doesn't give us a
1213
+ * hook around the actual LLM invocation, we rely on the provider
1214
+ * wrappers checking isFrameworkLlmActive() which is set during
1215
+ * the configure phase and active throughout the call chain.
1216
+ */
298
1217
  return (originalConfigure) => {
299
1218
  return function (inheritableHandlers, ...rest) {
300
- // inheritableHandlers can be:
301
- // - undefined : no handlers passed
302
- // - Array : list of handler objects (chat model calls)
303
- // - CallbackManager instance: already-built manager (Runnable/chain calls)
304
1219
  if (Array.isArray(inheritableHandlers) || !inheritableHandlers) {
305
1220
  const handlers = inheritableHandlers ? [...inheritableHandlers] : [];
306
1221
  if (!handlers.some((h) => h?.name === 'openlit_callback_handler')) {
@@ -309,7 +1224,6 @@ class LangChainWrapper extends base_wrapper_1.default {
309
1224
  return originalConfigure.call(this, handlers, ...rest);
310
1225
  }
311
1226
  else {
312
- // Existing CallbackManager — add our handler directly
313
1227
  const cbManager = inheritableHandlers;
314
1228
  if (cbManager?.handlers && !cbManager.handlers.some((h) => h?.name === 'openlit_callback_handler')) {
315
1229
  cbManager.addHandler(handler, true);