langwatch 0.2.0 → 0.3.0-prerelease.1

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 (235) hide show
  1. package/.editorconfig +16 -0
  2. package/LICENSE +7 -0
  3. package/README.md +268 -1
  4. package/copy-types.sh +19 -8
  5. package/examples/langchain/.env.example +2 -0
  6. package/examples/langchain/README.md +42 -0
  7. package/examples/langchain/package-lock.json +2930 -0
  8. package/examples/langchain/package.json +27 -0
  9. package/examples/langchain/src/cli-markdown.d.ts +137 -0
  10. package/examples/langchain/src/index.ts +109 -0
  11. package/examples/langchain/tsconfig.json +25 -0
  12. package/examples/langgraph/.env.example +2 -0
  13. package/examples/langgraph/README.md +42 -0
  14. package/examples/langgraph/package-lock.json +3031 -0
  15. package/examples/langgraph/package.json +28 -0
  16. package/examples/langgraph/src/cli-markdown.d.ts +137 -0
  17. package/examples/langgraph/src/index.ts +196 -0
  18. package/examples/langgraph/tsconfig.json +25 -0
  19. package/examples/mastra/.env.example +2 -0
  20. package/examples/mastra/README.md +57 -0
  21. package/examples/mastra/package-lock.json +5296 -0
  22. package/examples/mastra/package.json +32 -0
  23. package/examples/mastra/src/cli-markdown.d.ts +137 -0
  24. package/examples/mastra/src/index.ts +120 -0
  25. package/examples/mastra/src/mastra/agents/weather-agent.ts +30 -0
  26. package/examples/mastra/src/mastra/index.ts +21 -0
  27. package/examples/mastra/src/mastra/tools/weather-tool.ts +102 -0
  28. package/examples/mastra/tsconfig.json +25 -0
  29. package/examples/vercel-ai/.env.example +2 -0
  30. package/examples/vercel-ai/README.md +38 -0
  31. package/examples/vercel-ai/package-lock.json +2571 -0
  32. package/examples/vercel-ai/package.json +27 -0
  33. package/examples/vercel-ai/src/cli-markdown.d.ts +137 -0
  34. package/examples/vercel-ai/src/index.ts +110 -0
  35. package/examples/vercel-ai/src/instrumentation.ts +9 -0
  36. package/examples/vercel-ai/tsconfig.json +25 -0
  37. package/package.json +78 -34
  38. package/src/__tests__/client-browser.test.ts +92 -0
  39. package/src/__tests__/client-node.test.ts +76 -0
  40. package/src/__tests__/client.test.ts +71 -0
  41. package/src/__tests__/integration/client-browser.test.ts +46 -0
  42. package/src/__tests__/integration/client-node.test.ts +46 -0
  43. package/src/client-browser.ts +70 -0
  44. package/src/client-node.ts +82 -0
  45. package/src/client-shared.ts +72 -0
  46. package/src/client.ts +119 -0
  47. package/src/evaluation/__tests__/record-evaluation.test.ts +112 -0
  48. package/src/evaluation/__tests__/run-evaluation.test.ts +171 -0
  49. package/src/evaluation/index.ts +2 -0
  50. package/src/evaluation/record-evaluation.ts +101 -0
  51. package/src/evaluation/run-evaluation.ts +133 -0
  52. package/src/evaluation/tracer.ts +3 -0
  53. package/src/evaluation/types.ts +23 -0
  54. package/src/index.ts +10 -593
  55. package/src/internal/api/__tests__/errors.test.ts +98 -0
  56. package/src/internal/api/client.ts +30 -0
  57. package/src/internal/api/errors.ts +32 -0
  58. package/src/internal/generated/types/.gitkeep +0 -0
  59. package/src/observability/__tests__/integration/base.test.ts +74 -0
  60. package/src/observability/__tests__/integration/browser-setup-ordering.test.ts +60 -0
  61. package/src/observability/__tests__/integration/complex-nested-spans.test.ts +29 -0
  62. package/src/observability/__tests__/integration/error-handling.test.ts +24 -0
  63. package/src/observability/__tests__/integration/langwatch-disabled-otel.test.ts +24 -0
  64. package/src/observability/__tests__/integration/langwatch-first-then-vercel.test.ts +24 -0
  65. package/src/observability/__tests__/integration/multiple-setup-attempts.test.ts +27 -0
  66. package/src/observability/__tests__/integration/otel-ordering.test.ts +27 -0
  67. package/src/observability/__tests__/integration/vercel-configurations.test.ts +20 -0
  68. package/src/observability/__tests__/integration/vercel-first-then-langwatch.test.ts +27 -0
  69. package/src/observability/__tests__/span.test.ts +214 -0
  70. package/src/observability/__tests__/trace.test.ts +180 -0
  71. package/src/observability/exporters/index.ts +1 -0
  72. package/src/observability/exporters/langwatch-exporter.ts +53 -0
  73. package/src/observability/index.ts +4 -0
  74. package/src/observability/instrumentation/langchain/__tests__/integration/langchain-chatbot.test.ts +112 -0
  75. package/src/observability/instrumentation/langchain/__tests__/langchain.test.ts +284 -0
  76. package/src/observability/instrumentation/langchain/index.ts +624 -0
  77. package/src/observability/processors/__tests__/filterable-batch-span-exporter.test.ts +98 -0
  78. package/src/observability/processors/filterable-batch-span-processor.ts +99 -0
  79. package/src/observability/processors/index.ts +1 -0
  80. package/src/observability/semconv/attributes.ts +185 -0
  81. package/src/observability/semconv/events.ts +42 -0
  82. package/src/observability/semconv/index.ts +16 -0
  83. package/src/observability/semconv/values.ts +159 -0
  84. package/src/observability/span.ts +728 -0
  85. package/src/observability/trace.ts +301 -0
  86. package/src/prompt/__tests__/prompt.test.ts +139 -0
  87. package/src/prompt/get-prompt-version.ts +49 -0
  88. package/src/prompt/get-prompt.ts +44 -0
  89. package/src/prompt/index.ts +3 -0
  90. package/src/prompt/prompt.ts +133 -0
  91. package/src/prompt/service.ts +221 -0
  92. package/src/prompt/tracer.ts +3 -0
  93. package/src/prompt/types.ts +0 -0
  94. package/ts-to-zod.config.js +11 -0
  95. package/tsconfig.json +3 -9
  96. package/tsup.config.ts +11 -1
  97. package/vitest.config.ts +1 -0
  98. package/dist/chunk-LKD2K67J.mjs +0 -717
  99. package/dist/chunk-LKD2K67J.mjs.map +0 -1
  100. package/dist/index.d.mts +0 -1030
  101. package/dist/index.d.ts +0 -1030
  102. package/dist/index.js +0 -27310
  103. package/dist/index.js.map +0 -1
  104. package/dist/index.mjs +0 -963
  105. package/dist/index.mjs.map +0 -1
  106. package/dist/utils-Cv-rUjJ1.d.mts +0 -313
  107. package/dist/utils-Cv-rUjJ1.d.ts +0 -313
  108. package/dist/utils.d.mts +0 -2
  109. package/dist/utils.d.ts +0 -2
  110. package/dist/utils.js +0 -709
  111. package/dist/utils.js.map +0 -1
  112. package/dist/utils.mjs +0 -11
  113. package/dist/utils.mjs.map +0 -1
  114. package/example/.env.example +0 -12
  115. package/example/.eslintrc.json +0 -26
  116. package/example/LICENSE +0 -13
  117. package/example/README.md +0 -12
  118. package/example/app/(chat)/chat/[id]/page.tsx +0 -60
  119. package/example/app/(chat)/layout.tsx +0 -14
  120. package/example/app/(chat)/page.tsx +0 -27
  121. package/example/app/actions.ts +0 -156
  122. package/example/app/globals.css +0 -76
  123. package/example/app/guardrails/page.tsx +0 -26
  124. package/example/app/langchain/page.tsx +0 -27
  125. package/example/app/langchain-rag/page.tsx +0 -28
  126. package/example/app/late-update/page.tsx +0 -27
  127. package/example/app/layout.tsx +0 -64
  128. package/example/app/login/actions.ts +0 -71
  129. package/example/app/login/page.tsx +0 -18
  130. package/example/app/manual/page.tsx +0 -27
  131. package/example/app/new/page.tsx +0 -5
  132. package/example/app/opengraph-image.png +0 -0
  133. package/example/app/share/[id]/page.tsx +0 -58
  134. package/example/app/signup/actions.ts +0 -111
  135. package/example/app/signup/page.tsx +0 -18
  136. package/example/app/twitter-image.png +0 -0
  137. package/example/auth.config.ts +0 -42
  138. package/example/auth.ts +0 -45
  139. package/example/components/button-scroll-to-bottom.tsx +0 -36
  140. package/example/components/chat-history.tsx +0 -49
  141. package/example/components/chat-list.tsx +0 -52
  142. package/example/components/chat-message-actions.tsx +0 -40
  143. package/example/components/chat-message.tsx +0 -80
  144. package/example/components/chat-panel.tsx +0 -139
  145. package/example/components/chat-share-dialog.tsx +0 -95
  146. package/example/components/chat.tsx +0 -84
  147. package/example/components/clear-history.tsx +0 -75
  148. package/example/components/empty-screen.tsx +0 -38
  149. package/example/components/external-link.tsx +0 -29
  150. package/example/components/footer.tsx +0 -19
  151. package/example/components/header.tsx +0 -114
  152. package/example/components/login-button.tsx +0 -42
  153. package/example/components/login-form.tsx +0 -97
  154. package/example/components/markdown.tsx +0 -9
  155. package/example/components/prompt-form.tsx +0 -115
  156. package/example/components/providers.tsx +0 -17
  157. package/example/components/sidebar-actions.tsx +0 -125
  158. package/example/components/sidebar-desktop.tsx +0 -19
  159. package/example/components/sidebar-footer.tsx +0 -16
  160. package/example/components/sidebar-item.tsx +0 -124
  161. package/example/components/sidebar-items.tsx +0 -42
  162. package/example/components/sidebar-list.tsx +0 -38
  163. package/example/components/sidebar-mobile.tsx +0 -31
  164. package/example/components/sidebar-toggle.tsx +0 -24
  165. package/example/components/sidebar.tsx +0 -21
  166. package/example/components/signup-form.tsx +0 -95
  167. package/example/components/stocks/events-skeleton.tsx +0 -31
  168. package/example/components/stocks/events.tsx +0 -30
  169. package/example/components/stocks/index.tsx +0 -36
  170. package/example/components/stocks/message.tsx +0 -134
  171. package/example/components/stocks/spinner.tsx +0 -16
  172. package/example/components/stocks/stock-purchase.tsx +0 -146
  173. package/example/components/stocks/stock-skeleton.tsx +0 -22
  174. package/example/components/stocks/stock.tsx +0 -210
  175. package/example/components/stocks/stocks-skeleton.tsx +0 -9
  176. package/example/components/stocks/stocks.tsx +0 -67
  177. package/example/components/tailwind-indicator.tsx +0 -14
  178. package/example/components/theme-toggle.tsx +0 -31
  179. package/example/components/ui/alert-dialog.tsx +0 -141
  180. package/example/components/ui/badge.tsx +0 -36
  181. package/example/components/ui/button.tsx +0 -57
  182. package/example/components/ui/codeblock.tsx +0 -148
  183. package/example/components/ui/dialog.tsx +0 -122
  184. package/example/components/ui/dropdown-menu.tsx +0 -205
  185. package/example/components/ui/icons.tsx +0 -507
  186. package/example/components/ui/input.tsx +0 -25
  187. package/example/components/ui/label.tsx +0 -26
  188. package/example/components/ui/select.tsx +0 -164
  189. package/example/components/ui/separator.tsx +0 -31
  190. package/example/components/ui/sheet.tsx +0 -140
  191. package/example/components/ui/sonner.tsx +0 -31
  192. package/example/components/ui/switch.tsx +0 -29
  193. package/example/components/ui/textarea.tsx +0 -24
  194. package/example/components/ui/tooltip.tsx +0 -30
  195. package/example/components/user-menu.tsx +0 -53
  196. package/example/components.json +0 -17
  197. package/example/instrumentation.ts +0 -11
  198. package/example/lib/chat/guardrails.tsx +0 -181
  199. package/example/lib/chat/langchain-rag.tsx +0 -191
  200. package/example/lib/chat/langchain.tsx +0 -112
  201. package/example/lib/chat/late-update.tsx +0 -208
  202. package/example/lib/chat/manual.tsx +0 -605
  203. package/example/lib/chat/vercel-ai.tsx +0 -576
  204. package/example/lib/hooks/use-copy-to-clipboard.tsx +0 -33
  205. package/example/lib/hooks/use-enter-submit.tsx +0 -23
  206. package/example/lib/hooks/use-local-storage.ts +0 -24
  207. package/example/lib/hooks/use-scroll-anchor.tsx +0 -86
  208. package/example/lib/hooks/use-sidebar.tsx +0 -60
  209. package/example/lib/hooks/use-streamable-text.ts +0 -25
  210. package/example/lib/types.ts +0 -41
  211. package/example/lib/utils.ts +0 -89
  212. package/example/middleware.ts +0 -8
  213. package/example/next-env.d.ts +0 -5
  214. package/example/next.config.js +0 -16
  215. package/example/package-lock.json +0 -10917
  216. package/example/package.json +0 -84
  217. package/example/pnpm-lock.yaml +0 -5712
  218. package/example/postcss.config.js +0 -6
  219. package/example/prettier.config.cjs +0 -34
  220. package/example/public/apple-touch-icon.png +0 -0
  221. package/example/public/favicon-16x16.png +0 -0
  222. package/example/public/favicon.ico +0 -0
  223. package/example/public/next.svg +0 -1
  224. package/example/public/thirteen.svg +0 -1
  225. package/example/public/vercel.svg +0 -1
  226. package/example/tailwind.config.ts +0 -81
  227. package/example/tsconfig.json +0 -35
  228. package/src/LangWatchExporter.ts +0 -96
  229. package/src/evaluations.ts +0 -219
  230. package/src/index.test.ts +0 -402
  231. package/src/langchain.ts +0 -557
  232. package/src/typeUtils.ts +0 -89
  233. package/src/types.ts +0 -82
  234. package/src/utils.ts +0 -205
  235. /package/src/{server/types → internal/generated/openapi}/.gitkeep +0 -0
package/src/index.ts CHANGED
@@ -1,596 +1,13 @@
1
- import EventEmitter from "eventemitter3";
2
- import { nanoid } from "nanoid";
3
- import { ZodError } from "zod";
4
- import { fromZodError } from "zod-validation-error";
5
- import { version } from "../package.json";
6
- import {
7
- evaluate,
8
- type EvaluationParams,
9
- type EvaluationResultModel,
10
- } from "./evaluations";
11
- import { LangWatchCallbackHandler } from "./langchain";
12
- import {
13
- type CollectorRESTParams,
14
- type EvaluationResult,
15
- type Span as ServerSpan,
16
- type SpanTypes,
17
- type TypedValueEvaluationResult,
18
- } from "./server/types/tracer";
19
- import {
20
- collectorRESTParamsSchema,
21
- spanSchema,
22
- } from "./server/types/tracer.generated";
23
- import {
24
- type Trace,
25
- type BaseSpan,
26
- type ChatMessage,
27
- type ChatRichContent,
28
- type LLMSpan,
29
- type Metadata,
30
- type PendingBaseSpan,
31
- type PendingLLMSpan,
32
- type PendingRAGSpan,
33
- type RAGSpan,
34
- type RESTEvaluation,
35
- type SpanInputOutput,
36
- type LLMModeTrace,
37
- type ErrorCapture,
38
- } from "./types";
39
- import { camelToSnakeCaseNested, type Strict } from "./typeUtils";
40
- import {
41
- autoconvertTypedValues,
42
- captureError,
43
- convertFromVercelAIMessages,
44
- } from "./utils";
45
- import { LangWatchExporter } from "./LangWatchExporter";
46
-
47
- export type {
48
- Trace,
49
- BaseSpan,
50
- ChatMessage as ChatMessage,
51
- ChatRichContent,
52
- LLMSpan,
53
- Metadata,
54
- PendingBaseSpan,
55
- PendingLLMSpan,
56
- PendingRAGSpan,
57
- RAGSpan,
58
- SpanInputOutput,
59
- LLMModeTrace,
60
- ErrorCapture,
61
- };
62
-
1
+ export { getLangWatchTracer, type LangWatchSpan } from "./observability";
63
2
  export {
64
- autoconvertTypedValues,
65
- captureError,
66
- convertFromVercelAIMessages,
67
- LangWatchExporter,
68
- };
69
-
70
- export class LangWatch extends EventEmitter {
71
- apiKey: string | undefined;
72
- endpoint: string;
73
-
74
- constructor({
75
- apiKey,
76
- endpoint = process.env.LANGWATCH_ENDPOINT ?? "https://app.langwatch.ai",
77
- }: {
78
- apiKey?: string;
79
- endpoint?: string;
80
- } = {}) {
81
- super();
82
- const apiKey_ = apiKey ?? process.env.LANGWATCH_API_KEY;
83
- if (!apiKey_) {
84
- const error = new Error(
85
- "LangWatch API key is not set, please set the LANGWATCH_API_KEY environment variable or pass it in the constructor. Traces will not be captured."
86
- );
87
- this.emit("error", error);
88
- }
89
- this.apiKey = apiKey_;
90
- this.endpoint = endpoint;
91
- }
92
-
93
- getTrace({
94
- traceId,
95
- metadata,
96
- }: { traceId?: string; metadata?: Metadata } = {}) {
97
- return new LangWatchTrace({
98
- client: this,
99
- traceId: traceId ?? `trace_${nanoid()}`,
100
- metadata,
101
- });
102
- }
103
-
104
- async sendTrace(params: CollectorRESTParams) {
105
- const backoff = [1000, 2000, 4000, 8000, 16000];
106
- for (const backoffTime of backoff) {
107
- try {
108
- await this._sendTrace(params);
109
- return;
110
- } catch (e) {
111
- console.warn(
112
- `[LangWatch] ⚠️ Failed to send trace, retrying in ${
113
- backoffTime / 1000
114
- }s`
115
- );
116
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
117
- }
118
- }
119
- console.warn("[LangWatch] ⚠️ Failed to send trace, giving up");
120
- }
121
-
122
- async _sendTrace(params: CollectorRESTParams) {
123
- if (params.spans.length === 0) {
124
- return;
125
- }
126
-
127
- if (!this.apiKey) {
128
- const error = new Error(
129
- "LangWatch API key is not set, LLMs traces will not be sent, go to https://langwatch.ai to set it up"
130
- );
131
- this.emit("error", error);
132
- return;
133
- }
134
-
135
- const response = await fetch(`${this.endpoint}/api/collector`, {
136
- method: "POST",
137
- headers: {
138
- "X-Auth-Token": this.apiKey,
139
- "Content-Type": "application/json",
140
- },
141
- body: JSON.stringify(params),
142
- });
143
-
144
- if (response.status === 429) {
145
- const error = new Error(
146
- "Rate limit exceeded, dropping message from being sent to LangWatch. Please check your dashboard to upgrade your plan."
147
- );
148
- this.emit("error", error);
149
- return;
150
- }
151
- if (!response.ok) {
152
- const error = new Error(
153
- `Failed to send trace, status: ${response.status}`
154
- );
155
- this.emit("error", error);
156
- throw error;
157
- }
158
- }
159
- }
160
-
161
- type CurrentSpan = {
162
- current: LangWatchSpan;
163
- previous?: CurrentSpan;
164
- };
165
-
166
- type AddEvaluationParams = {
167
- evaluationId?: string;
168
- span?: LangWatchSpan;
169
- name: string;
170
- type?: string;
171
- isGuardrail?: boolean;
172
- status?: "processed" | "skipped" | "error";
173
- passed?: boolean;
174
- score?: number;
175
- label?: string;
176
- details?: string;
177
- error?: Error;
178
- timestamps?: RESTEvaluation["timestamps"];
179
- };
180
-
181
- export class LangWatchTrace {
182
- client: LangWatch;
183
- traceId: string;
184
- metadata?: Metadata;
185
- finishedSpans: Record<string, ServerSpan> = {};
186
- langchainCallback?: LangWatchCallbackHandler;
187
- evaluations: RESTEvaluation[] = [];
188
- private currentSpan?: CurrentSpan;
189
- private timeoutRef?: NodeJS.Timeout;
190
-
191
- constructor({
192
- client,
193
- traceId,
194
- metadata,
195
- }: {
196
- client: LangWatch;
197
- traceId: string;
198
- metadata?: Metadata;
199
- }) {
200
- this.client = client;
201
- this.traceId = traceId;
202
- this.metadata = {
203
- ...metadata,
204
- sdkVersion: version,
205
- sdkLanguage: "typescript",
206
- };
207
- }
208
-
209
- update({ metadata }: { metadata: Metadata }) {
210
- this.metadata = {
211
- ...this.metadata,
212
- ...metadata,
213
- ...(typeof metadata.labels !== "undefined"
214
- ? {
215
- labels: [
216
- ...(this.metadata?.labels ?? []),
217
- ...(metadata.labels ?? []),
218
- ],
219
- }
220
- : {}),
221
- };
222
- }
223
-
224
- setCurrentSpan(span: LangWatchSpan) {
225
- this.currentSpan = {
226
- current: span,
227
- previous: this.currentSpan,
228
- };
229
- }
230
-
231
- getCurrentSpan() {
232
- return this.currentSpan?.current;
233
- }
234
-
235
- resetCurrentSpan() {
236
- this.currentSpan = this.currentSpan?.previous;
237
- }
238
-
239
- startSpan(params: Omit<Partial<PendingBaseSpan>, "parentId">) {
240
- const span = new LangWatchSpan({
241
- trace: this,
242
- ...params,
243
- });
244
- this.setCurrentSpan(span);
245
- return span;
246
- }
247
-
248
- startLLMSpan(params: Omit<Partial<PendingLLMSpan>, "parentId">) {
249
- const span = new LangWatchLLMSpan({
250
- trace: this,
251
- ...params,
252
- });
253
- this.setCurrentSpan(span);
254
- return span;
255
- }
256
-
257
- startRAGSpan(params: Omit<Partial<PendingRAGSpan>, "parentId">) {
258
- const span = new LangWatchRAGSpan({
259
- trace: this,
260
- ...params,
261
- });
262
- this.setCurrentSpan(span);
263
- return span;
264
- }
265
-
266
- addEvaluation = ({
267
- evaluationId,
268
- span,
269
- name,
270
- type,
271
- isGuardrail,
272
- status = "processed",
273
- passed,
274
- score,
275
- label,
276
- details,
277
- error,
278
- timestamps,
279
- }: AddEvaluationParams): void => {
280
- const currentEvaluationIndex = this.evaluations.findIndex(
281
- (e) =>
282
- evaluationId && "evaluationId" in e && e.evaluationId === evaluationId
283
- );
284
-
285
- const currentEvaluation =
286
- currentEvaluationIndex !== -1
287
- ? this.evaluations[currentEvaluationIndex]
288
- : undefined;
289
-
290
- const evaluationResult: EvaluationResult = {
291
- status,
292
- ...(passed !== undefined && { passed }),
293
- ...(score !== undefined && { score }),
294
- ...(label !== undefined && { label }),
295
- ...(details !== undefined && { details }),
296
- };
297
-
298
- let span_ = span;
299
- if (!span_) {
300
- span_ = this.startSpan({
301
- type: "evaluation",
302
- });
303
- }
304
- if (span_.type !== "evaluation") {
305
- span_ = span_.startSpan({ type: "evaluation" });
306
- }
307
-
308
- span_.update({
309
- name,
310
- output: {
311
- type: "evaluation_result",
312
- value: evaluationResult,
313
- } as TypedValueEvaluationResult,
314
- error,
315
- timestamps: timestamps
316
- ? {
317
- startedAt: timestamps.startedAt ?? span_.timestamps.startedAt,
318
- finishedAt: timestamps.finishedAt ?? undefined,
319
- }
320
- : undefined,
321
- });
322
- span_.end();
323
-
324
- const evaluation: RESTEvaluation = {
325
- evaluationId: evaluationId ?? `eval_${nanoid()}`,
326
- spanId: span_.spanId,
327
- name,
328
- type,
329
- isGuardrail,
330
- status,
331
- passed,
332
- score,
333
- label,
334
- details,
335
- error: error ? captureError(error) : undefined,
336
- timestamps: timestamps ?? {
337
- startedAt: span_.timestamps.startedAt,
338
- finishedAt: span_.timestamps.finishedAt,
339
- },
340
- };
3
+ FilterableBatchSpanProcessor,
4
+ type SpanProcessingExcludeRule,
5
+ } from "./observability/processors";
6
+ export { LangWatchExporter } from "./observability/exporters";
341
7
 
342
- if (currentEvaluation && currentEvaluationIndex !== -1) {
343
- this.evaluations[currentEvaluationIndex] = {
344
- ...currentEvaluation,
345
- ...evaluation,
346
- };
347
- } else {
348
- this.evaluations.push(evaluation);
349
- }
350
- };
8
+ export { recordEvaluation, runEvaluation } from "./evaluation";
351
9
 
352
- async evaluate(params: EvaluationParams): Promise<EvaluationResultModel> {
353
- return evaluate({
354
- trace: this,
355
- ...params,
356
- });
357
- }
358
-
359
- getLangChainCallback() {
360
- if (!this.langchainCallback) {
361
- this.langchainCallback = new LangWatchCallbackHandler({ trace: this });
362
- }
363
- return this.langchainCallback;
364
- }
365
-
366
- onEnd(span: ServerSpan) {
367
- this.finishedSpans[span.span_id] = span;
368
- this.resetCurrentSpan();
369
- this.delayedSendSpans();
370
- }
371
-
372
- delayedSendSpans() {
373
- clearTimeout(this.timeoutRef);
374
- this.timeoutRef = setTimeout(() => {
375
- void this.sendSpans();
376
- }, 1000);
377
- }
378
-
379
- async sendSpans() {
380
- clearTimeout(this.timeoutRef);
381
-
382
- let trace: CollectorRESTParams | undefined = undefined;
383
- try {
384
- trace = collectorRESTParamsSchema.parse({
385
- trace_id: this.traceId,
386
- metadata: camelToSnakeCaseNested(this.metadata, "metadata"),
387
- spans: Object.values(this.finishedSpans),
388
- evaluations: camelToSnakeCaseNested(this.evaluations),
389
- } as Strict<CollectorRESTParams>);
390
- } catch (error) {
391
- if (error instanceof ZodError) {
392
- console.warn("[LangWatch] ⚠️ Failed to parse trace");
393
- console.warn(fromZodError(error).message);
394
- }
395
- this.client.emit("error", error);
396
- }
397
-
398
- if (trace) {
399
- await this.client.sendTrace(trace);
400
- }
401
- }
402
- }
403
-
404
- export class LangWatchSpan implements PendingBaseSpan {
405
- trace: LangWatchTrace;
406
-
407
- spanId: string;
408
- parentId?: string | null;
409
- type: SpanTypes;
410
- name?: string | null;
411
- input?: PendingBaseSpan["input"];
412
- output?: PendingBaseSpan["output"];
413
- error?: PendingBaseSpan["error"];
414
- timestamps: PendingBaseSpan["timestamps"];
415
- metrics: PendingBaseSpan["metrics"];
416
-
417
- constructor({
418
- trace,
419
- spanId,
420
- parentId,
421
- type,
422
- name,
423
- input,
424
- output,
425
- error,
426
- timestamps,
427
- metrics,
428
- }: Partial<PendingBaseSpan> & { trace: LangWatchTrace }) {
429
- this.spanId = spanId ?? `span_${nanoid()}`;
430
- this.parentId = parentId;
431
- this.trace = trace;
432
- this.type = type ?? "span";
433
- this.name = name;
434
- this.input = input;
435
- this.output = output;
436
- this.error = error;
437
- this.timestamps = timestamps ?? {
438
- startedAt: Date.now(),
439
- };
440
- this.metrics = metrics;
441
- }
442
-
443
- update(params: Partial<Omit<PendingBaseSpan, "spanId" | "parentId">>) {
444
- if (Object.isFrozen(this)) {
445
- const error = new Error(
446
- `Tried to update span ${this.spanId}, but the span is already finished, discarding update`
447
- );
448
- this.trace.client.emit("error", error);
449
- return;
450
- }
451
-
452
- if (params.type) {
453
- this.type = params.type;
454
- }
455
- if ("name" in params) {
456
- this.name = params.name;
457
- }
458
- if ("input" in params) {
459
- this.input = params.input;
460
- }
461
- if ("output" in params) {
462
- this.output = params.output;
463
- }
464
- if ("error" in params) {
465
- this.error = params.error;
466
- }
467
- if (params.timestamps) {
468
- this.timestamps = params.timestamps;
469
- }
470
- if ("metrics" in params) {
471
- this.metrics = params.metrics;
472
- }
473
- }
474
-
475
- startSpan(params: Omit<Partial<PendingBaseSpan>, "parentId">) {
476
- const span = new LangWatchSpan({
477
- trace: this.trace,
478
- parentId: this.spanId,
479
- ...params,
480
- });
481
- this.trace.setCurrentSpan(span);
482
- return span;
483
- }
484
-
485
- startLLMSpan(params: Omit<Partial<PendingLLMSpan>, "parentId">) {
486
- const span = new LangWatchLLMSpan({
487
- trace: this.trace,
488
- parentId: this.spanId,
489
- ...params,
490
- });
491
- this.trace.setCurrentSpan(span);
492
- return span;
493
- }
494
-
495
- startRAGSpan(params: Omit<Partial<PendingRAGSpan>, "parentId">) {
496
- const span = new LangWatchRAGSpan({
497
- trace: this.trace,
498
- parentId: this.spanId,
499
- ...params,
500
- });
501
- this.trace.setCurrentSpan(span);
502
- return span;
503
- }
504
-
505
- addEvaluation(params: AddEvaluationParams) {
506
- this.trace.addEvaluation({
507
- ...params,
508
- span: this,
509
- });
510
- }
511
-
512
- async evaluate(params: EvaluationParams): Promise<EvaluationResultModel> {
513
- return evaluate({
514
- span: this,
515
- ...params,
516
- });
517
- }
518
-
519
- end(params?: Partial<Omit<PendingBaseSpan, "spanId" | "parentId">>) {
520
- this.timestamps.finishedAt = Date.now();
521
- if (params) {
522
- this.update(params);
523
- }
524
-
525
- try {
526
- const finalSpan = spanSchema.parse(
527
- camelToSnakeCaseNested({
528
- ...this,
529
- trace: undefined,
530
- traceId: this.trace.traceId,
531
- timestamps: {
532
- ...this.timestamps,
533
- finishedAt: this.timestamps.finishedAt,
534
- },
535
- ...(this.error && { error: captureError(this.error) }),
536
- }) as ServerSpan
537
- );
538
- this.trace.onEnd(finalSpan);
539
- } catch (error) {
540
- if (error instanceof ZodError) {
541
- console.warn("[LangWatch] ⚠️ Failed to parse span");
542
- console.warn(fromZodError(error).message);
543
- }
544
- this.trace.client.emit("error", error);
545
- }
546
- }
547
- }
548
-
549
- export class LangWatchLLMSpan extends LangWatchSpan implements PendingLLMSpan {
550
- type: "llm";
551
- model: PendingLLMSpan["model"];
552
- params: PendingLLMSpan["params"];
553
-
554
- constructor(params: Partial<PendingLLMSpan> & { trace: LangWatchTrace }) {
555
- super({ ...params });
556
- this.type = "llm";
557
- this.model = params.model ?? "unknown";
558
- this.params = params.params ?? {};
559
- }
560
-
561
- update(params: Partial<PendingLLMSpan>) {
562
- super.update(params);
563
- if (params.model) {
564
- this.model = params.model;
565
- }
566
- if (params.params) {
567
- this.params = params.params;
568
- }
569
- }
570
-
571
- end(params?: Partial<PendingLLMSpan>) {
572
- super.end(params);
573
- }
574
- }
575
-
576
- export class LangWatchRAGSpan extends LangWatchSpan implements PendingRAGSpan {
577
- type: "rag";
578
- contexts: PendingRAGSpan["contexts"];
579
-
580
- constructor(params: Partial<PendingRAGSpan> & { trace: LangWatchTrace }) {
581
- super({ ...params });
582
- this.type = "rag";
583
- this.contexts = params.contexts ?? [];
584
- }
585
-
586
- update(params: Partial<PendingRAGSpan>) {
587
- super.update(params);
588
- if (params.contexts) {
589
- this.contexts = params.contexts;
590
- }
591
- }
592
-
593
- end(params?: Partial<PendingRAGSpan>) {
594
- super.end(params);
595
- }
596
- }
10
+ export {
11
+ getPrompt,
12
+ getPromptVersion,
13
+ } from "./prompt";
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { LangWatchApiError } from '../errors';
3
+
4
+ describe('LangWatchApiError', () => {
5
+ it('creates error with message and response', () => {
6
+ const mockResponse = {
7
+ status: 400,
8
+ statusText: 'Bad Request'
9
+ } as Response;
10
+
11
+ const error = new LangWatchApiError('Test error message', mockResponse);
12
+ expect(error.message).toBe('Test error message');
13
+ expect(error.name).toBe('Error');
14
+ expect(error.httpStatus).toBe(400);
15
+ expect(error.httpStatusText).toBe('Bad Request');
16
+ });
17
+
18
+ it('inherits from Error', () => {
19
+ const mockResponse = { status: 500, statusText: 'Internal Server Error' } as Response;
20
+ const error = new LangWatchApiError('Test error', mockResponse);
21
+ expect(error).toBeInstanceOf(Error);
22
+ expect(error).toBeInstanceOf(LangWatchApiError);
23
+ });
24
+
25
+ it('has stack trace', () => {
26
+ const mockResponse = { status: 404, statusText: 'Not Found' } as Response;
27
+ const error = new LangWatchApiError('Test error', mockResponse);
28
+ expect(error.stack).toBeDefined();
29
+ expect(typeof error.stack).toBe('string');
30
+ });
31
+
32
+ it('parses JSON response body', async () => {
33
+ const mockResponse = {
34
+ status: 400,
35
+ statusText: 'Bad Request',
36
+ headers: {
37
+ get: vi.fn().mockReturnValue('application/json')
38
+ },
39
+ json: vi.fn().mockResolvedValue({ error: 'Invalid API key' })
40
+ } as unknown as Response;
41
+
42
+ const error = new LangWatchApiError('Bad request', mockResponse);
43
+ await error.safeParseBody(mockResponse);
44
+
45
+ expect(error.body).toEqual({ error: 'Invalid API key' });
46
+ expect(error.apiError).toBe('Invalid API key');
47
+ });
48
+
49
+ it('parses text response body for non-JSON content', async () => {
50
+ const mockResponse = {
51
+ status: 500,
52
+ statusText: 'Internal Server Error',
53
+ headers: {
54
+ get: vi.fn().mockReturnValue('text/plain')
55
+ },
56
+ text: vi.fn().mockResolvedValue('Server error occurred')
57
+ } as unknown as Response;
58
+
59
+ const error = new LangWatchApiError('Server error', mockResponse);
60
+ await error.safeParseBody(mockResponse);
61
+
62
+ expect(error.body).toBe('Server error occurred');
63
+ expect(error.apiError).toBeUndefined();
64
+ });
65
+
66
+ it('handles parsing errors gracefully', async () => {
67
+ const mockResponse = {
68
+ status: 500,
69
+ statusText: 'Internal Server Error',
70
+ headers: {
71
+ get: vi.fn().mockReturnValue('application/json')
72
+ },
73
+ json: vi.fn().mockRejectedValue(new Error('Parse error'))
74
+ } as unknown as Response;
75
+
76
+ const error = new LangWatchApiError('Server error', mockResponse);
77
+ await error.safeParseBody(mockResponse);
78
+
79
+ expect(error.body).toBe(null);
80
+ });
81
+
82
+ it('handles JSON without error field', async () => {
83
+ const mockResponse = {
84
+ status: 400,
85
+ statusText: 'Bad Request',
86
+ headers: {
87
+ get: vi.fn().mockReturnValue('application/json')
88
+ },
89
+ json: vi.fn().mockResolvedValue({ message: 'Something went wrong' })
90
+ } as unknown as Response;
91
+
92
+ const error = new LangWatchApiError('Bad request', mockResponse);
93
+ await error.safeParseBody(mockResponse);
94
+
95
+ expect(error.body).toEqual({ message: 'Something went wrong' });
96
+ expect(error.apiError).toBeUndefined();
97
+ });
98
+ });