langwatch 0.3.0-prerelease.1 → 0.3.0-prerelease.2

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 (184) hide show
  1. package/dist/chunk-4BZATFKJ.mjs +181 -0
  2. package/dist/chunk-4BZATFKJ.mjs.map +1 -0
  3. package/dist/chunk-CSC3CMIT.mjs +118 -0
  4. package/dist/chunk-CSC3CMIT.mjs.map +1 -0
  5. package/dist/chunk-F63YKTXA.mjs +47 -0
  6. package/dist/chunk-F63YKTXA.mjs.map +1 -0
  7. package/dist/chunk-G3AUABT7.js +4 -0
  8. package/dist/chunk-G3AUABT7.js.map +1 -0
  9. package/dist/chunk-HPC6Z7J4.js +118 -0
  10. package/dist/chunk-HPC6Z7J4.js.map +1 -0
  11. package/dist/chunk-KGDAENGD.js +50 -0
  12. package/dist/chunk-KGDAENGD.js.map +1 -0
  13. package/dist/chunk-LD74LVRU.js +47 -0
  14. package/dist/chunk-LD74LVRU.js.map +1 -0
  15. package/dist/chunk-OM7VY3XT.mjs +4 -0
  16. package/dist/chunk-OM7VY3XT.mjs.map +1 -0
  17. package/dist/chunk-PCQVQ7SB.js +45 -0
  18. package/dist/chunk-PCQVQ7SB.js.map +1 -0
  19. package/dist/chunk-PMBEK6YE.mjs +424 -0
  20. package/dist/chunk-PMBEK6YE.mjs.map +1 -0
  21. package/dist/chunk-PR3JDWC3.mjs +50 -0
  22. package/dist/chunk-PR3JDWC3.mjs.map +1 -0
  23. package/dist/chunk-PTJ6AAI7.js +360 -0
  24. package/dist/chunk-PTJ6AAI7.js.map +1 -0
  25. package/dist/chunk-QEWDG5QE.mjs +45 -0
  26. package/dist/chunk-QEWDG5QE.mjs.map +1 -0
  27. package/dist/chunk-REUCVT7A.mjs +39 -0
  28. package/dist/chunk-REUCVT7A.mjs.map +1 -0
  29. package/dist/chunk-SVJ7SCGB.js +424 -0
  30. package/dist/chunk-SVJ7SCGB.js.map +1 -0
  31. package/dist/chunk-VJSOCNPA.js +181 -0
  32. package/dist/chunk-VJSOCNPA.js.map +1 -0
  33. package/dist/chunk-WM2GRSRW.js +39 -0
  34. package/dist/chunk-WM2GRSRW.js.map +1 -0
  35. package/dist/chunk-Z5J5UI5E.mjs +360 -0
  36. package/dist/chunk-Z5J5UI5E.mjs.map +1 -0
  37. package/dist/client-B2HqIKg6.d.ts +51 -0
  38. package/dist/client-XyCqclCi.d.mts +51 -0
  39. package/dist/client-browser.d.mts +8 -0
  40. package/dist/client-browser.d.ts +8 -0
  41. package/dist/client-browser.js +83 -0
  42. package/dist/client-browser.js.map +1 -0
  43. package/dist/client-browser.mjs +83 -0
  44. package/dist/client-browser.mjs.map +1 -0
  45. package/dist/client-node.d.mts +8 -0
  46. package/dist/client-node.d.ts +8 -0
  47. package/dist/client-node.js +90 -0
  48. package/dist/client-node.js.map +1 -0
  49. package/dist/client-node.mjs +90 -0
  50. package/dist/client-node.mjs.map +1 -0
  51. package/dist/evaluation/index.d.mts +897 -0
  52. package/dist/evaluation/index.d.ts +897 -0
  53. package/dist/evaluation/index.js +13 -0
  54. package/dist/evaluation/index.js.map +1 -0
  55. package/dist/evaluation/index.mjs +13 -0
  56. package/dist/evaluation/index.mjs.map +1 -0
  57. package/dist/filterable-batch-span-processor-zO5kcjBY.d.mts +64 -0
  58. package/dist/filterable-batch-span-processor-zO5kcjBY.d.ts +64 -0
  59. package/dist/index.d.mts +48 -0
  60. package/{src/observability/exporters/langwatch-exporter.ts → dist/index.d.ts} +13 -18
  61. package/dist/index.js +30 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/index.mjs +30 -0
  64. package/dist/index.mjs.map +1 -0
  65. package/dist/observability/index.d.mts +260 -0
  66. package/dist/observability/index.d.ts +260 -0
  67. package/dist/observability/index.js +20 -0
  68. package/dist/observability/index.js.map +1 -0
  69. package/dist/observability/index.mjs +20 -0
  70. package/dist/observability/index.mjs.map +1 -0
  71. package/dist/observability/instrumentation/langchain/index.d.mts +40 -0
  72. package/dist/observability/instrumentation/langchain/index.d.ts +40 -0
  73. package/dist/observability/instrumentation/langchain/index.js +666 -0
  74. package/dist/observability/instrumentation/langchain/index.js.map +1 -0
  75. package/dist/observability/instrumentation/langchain/index.mjs +666 -0
  76. package/dist/observability/instrumentation/langchain/index.mjs.map +1 -0
  77. package/dist/prompt/index.d.mts +10 -0
  78. package/dist/prompt/index.d.ts +10 -0
  79. package/dist/prompt/index.js +18 -0
  80. package/dist/prompt/index.js.map +1 -0
  81. package/dist/prompt/index.mjs +18 -0
  82. package/dist/prompt/index.mjs.map +1 -0
  83. package/dist/prompt-BXJWdbQp.d.mts +1967 -0
  84. package/dist/prompt-BXJWdbQp.d.ts +1967 -0
  85. package/dist/record-evaluation-CmxMXa-3.d.mts +25 -0
  86. package/dist/record-evaluation-CmxMXa-3.d.ts +25 -0
  87. package/dist/trace-D-bZOuqb.d.mts +622 -0
  88. package/dist/trace-G2312klE.d.ts +622 -0
  89. package/package.json +9 -4
  90. package/.editorconfig +0 -16
  91. package/.eslintrc.cjs +0 -37
  92. package/copy-types.sh +0 -28
  93. package/examples/langchain/.env.example +0 -2
  94. package/examples/langchain/README.md +0 -42
  95. package/examples/langchain/package-lock.json +0 -2930
  96. package/examples/langchain/package.json +0 -27
  97. package/examples/langchain/src/cli-markdown.d.ts +0 -137
  98. package/examples/langchain/src/index.ts +0 -109
  99. package/examples/langchain/tsconfig.json +0 -25
  100. package/examples/langgraph/.env.example +0 -2
  101. package/examples/langgraph/README.md +0 -42
  102. package/examples/langgraph/package-lock.json +0 -3031
  103. package/examples/langgraph/package.json +0 -28
  104. package/examples/langgraph/src/cli-markdown.d.ts +0 -137
  105. package/examples/langgraph/src/index.ts +0 -196
  106. package/examples/langgraph/tsconfig.json +0 -25
  107. package/examples/mastra/.env.example +0 -2
  108. package/examples/mastra/README.md +0 -57
  109. package/examples/mastra/package-lock.json +0 -5296
  110. package/examples/mastra/package.json +0 -32
  111. package/examples/mastra/src/cli-markdown.d.ts +0 -137
  112. package/examples/mastra/src/index.ts +0 -120
  113. package/examples/mastra/src/mastra/agents/weather-agent.ts +0 -30
  114. package/examples/mastra/src/mastra/index.ts +0 -21
  115. package/examples/mastra/src/mastra/tools/weather-tool.ts +0 -102
  116. package/examples/mastra/tsconfig.json +0 -25
  117. package/examples/vercel-ai/.env.example +0 -2
  118. package/examples/vercel-ai/README.md +0 -38
  119. package/examples/vercel-ai/package-lock.json +0 -2571
  120. package/examples/vercel-ai/package.json +0 -27
  121. package/examples/vercel-ai/src/cli-markdown.d.ts +0 -137
  122. package/examples/vercel-ai/src/index.ts +0 -110
  123. package/examples/vercel-ai/src/instrumentation.ts +0 -9
  124. package/examples/vercel-ai/tsconfig.json +0 -25
  125. package/src/__tests__/client-browser.test.ts +0 -92
  126. package/src/__tests__/client-node.test.ts +0 -76
  127. package/src/__tests__/client.test.ts +0 -71
  128. package/src/__tests__/integration/client-browser.test.ts +0 -46
  129. package/src/__tests__/integration/client-node.test.ts +0 -46
  130. package/src/client-browser.ts +0 -70
  131. package/src/client-node.ts +0 -82
  132. package/src/client-shared.ts +0 -72
  133. package/src/client.ts +0 -119
  134. package/src/evaluation/__tests__/record-evaluation.test.ts +0 -112
  135. package/src/evaluation/__tests__/run-evaluation.test.ts +0 -171
  136. package/src/evaluation/index.ts +0 -2
  137. package/src/evaluation/record-evaluation.ts +0 -101
  138. package/src/evaluation/run-evaluation.ts +0 -133
  139. package/src/evaluation/tracer.ts +0 -3
  140. package/src/evaluation/types.ts +0 -23
  141. package/src/index.ts +0 -13
  142. package/src/internal/api/__tests__/errors.test.ts +0 -98
  143. package/src/internal/api/client.ts +0 -30
  144. package/src/internal/api/errors.ts +0 -32
  145. package/src/internal/generated/openapi/.gitkeep +0 -0
  146. package/src/internal/generated/types/.gitkeep +0 -0
  147. package/src/observability/__tests__/integration/base.test.ts +0 -74
  148. package/src/observability/__tests__/integration/browser-setup-ordering.test.ts +0 -60
  149. package/src/observability/__tests__/integration/complex-nested-spans.test.ts +0 -29
  150. package/src/observability/__tests__/integration/error-handling.test.ts +0 -24
  151. package/src/observability/__tests__/integration/langwatch-disabled-otel.test.ts +0 -24
  152. package/src/observability/__tests__/integration/langwatch-first-then-vercel.test.ts +0 -24
  153. package/src/observability/__tests__/integration/multiple-setup-attempts.test.ts +0 -27
  154. package/src/observability/__tests__/integration/otel-ordering.test.ts +0 -27
  155. package/src/observability/__tests__/integration/vercel-configurations.test.ts +0 -20
  156. package/src/observability/__tests__/integration/vercel-first-then-langwatch.test.ts +0 -27
  157. package/src/observability/__tests__/span.test.ts +0 -214
  158. package/src/observability/__tests__/trace.test.ts +0 -180
  159. package/src/observability/exporters/index.ts +0 -1
  160. package/src/observability/index.ts +0 -4
  161. package/src/observability/instrumentation/langchain/__tests__/integration/langchain-chatbot.test.ts +0 -112
  162. package/src/observability/instrumentation/langchain/__tests__/langchain.test.ts +0 -284
  163. package/src/observability/instrumentation/langchain/index.ts +0 -624
  164. package/src/observability/processors/__tests__/filterable-batch-span-exporter.test.ts +0 -98
  165. package/src/observability/processors/filterable-batch-span-processor.ts +0 -99
  166. package/src/observability/processors/index.ts +0 -1
  167. package/src/observability/semconv/attributes.ts +0 -185
  168. package/src/observability/semconv/events.ts +0 -42
  169. package/src/observability/semconv/index.ts +0 -16
  170. package/src/observability/semconv/values.ts +0 -159
  171. package/src/observability/span.ts +0 -728
  172. package/src/observability/trace.ts +0 -301
  173. package/src/prompt/__tests__/prompt.test.ts +0 -139
  174. package/src/prompt/get-prompt-version.ts +0 -49
  175. package/src/prompt/get-prompt.ts +0 -44
  176. package/src/prompt/index.ts +0 -3
  177. package/src/prompt/prompt.ts +0 -133
  178. package/src/prompt/service.ts +0 -221
  179. package/src/prompt/tracer.ts +0 -3
  180. package/src/prompt/types.ts +0 -0
  181. package/ts-to-zod.config.js +0 -35
  182. package/tsconfig.json +0 -26
  183. package/tsup.config.ts +0 -20
  184. package/vitest.config.ts +0 -9
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { setupLangWatch } from "../../../client-node";
3
- import { getLangWatchTracer } from "../../trace";
4
-
5
- describe("Multiple setup attempts and error handling", () => {
6
- it("should throw error when multiple LangWatch setups are called", async () => {
7
- // First setup
8
- await setupLangWatch({
9
- apiKey: "test-key-1",
10
- endpoint: "http://localhost:9999",
11
- skipOpenTelemetrySetup: false,
12
- });
13
-
14
- // Second setup should throw an error
15
- await expect(setupLangWatch({
16
- apiKey: "test-key-2",
17
- endpoint: "http://localhost:9999",
18
- skipOpenTelemetrySetup: false,
19
- })).rejects.toThrow("LangWatch setup has already been called");
20
-
21
- // Test that LangWatch still works
22
- const tracer = getLangWatchTracer("multiple-setup-test");
23
- await tracer.withActiveSpan("test span", async () => {
24
- expect(true).toBe(true);
25
- });
26
- });
27
- });
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { registerOTel } from '@vercel/otel'
3
- import { getLangWatchTracer } from "../../trace";
4
- import { setupLangWatch } from "../../../client-node";
5
- import { isOtelInitialized } from "../../../client-shared";
6
-
7
- describe("SDK compatibility with Vercel AI OpenTelemetry", () => {
8
- it("should work when Vercel AI is set up first, then LangWatch", async () => {
9
- // Setup Vercel AI first
10
- registerOTel({ serviceName: 'vercel-first' });
11
-
12
- expect(isOtelInitialized()).toBe(true);
13
-
14
- // Then setup LangWatch
15
- await setupLangWatch({
16
- apiKey: "test-key",
17
- endpoint: "http://localhost:9999",
18
- skipOpenTelemetrySetup: false,
19
- });
20
-
21
- // Test that LangWatch still works
22
- const tracer = getLangWatchTracer("vercel-first-test");
23
- await tracer.withActiveSpan("test span", async () => {
24
- expect(true).toBe(true);
25
- });
26
- });
27
- });
@@ -1,20 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { registerOTel } from '@vercel/otel';
3
- import { setupLangWatch } from "../../../client-node";
4
- import { getLangWatchTracer } from "../../trace";
5
-
6
- describe("Different Vercel AI configurations", () => {
7
- it("should work with a specific Vercel AI configuration", async () => {
8
- registerOTel({ serviceName: 'config-1', version: '1.0.0' });
9
- await setupLangWatch({
10
- apiKey: "test-key",
11
- endpoint: "http://localhost:9999",
12
- skipOpenTelemetrySetup: false,
13
- });
14
-
15
- const tracer = getLangWatchTracer("config-test");
16
- await tracer.withActiveSpan("test span", async () => {
17
- expect(true).toBe(true);
18
- });
19
- });
20
- });
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { registerOTel } from '@vercel/otel';
3
- import { setupLangWatch } from "../../../client-node";
4
- import { getLangWatchTracer } from "../../trace";
5
- import { isOtelInitialized } from "../../../client-shared";
6
-
7
- describe("Vercel AI setup first, then LangWatch", () => {
8
- it("should work when Vercel AI is set up first, then LangWatch", async () => {
9
- // Setup Vercel AI first
10
- registerOTel({ serviceName: 'vercel-first' });
11
-
12
- expect(isOtelInitialized()).toBe(true);
13
-
14
- // Then setup LangWatch
15
- await setupLangWatch({
16
- apiKey: "test-key",
17
- endpoint: "http://localhost:9999",
18
- skipOpenTelemetrySetup: false,
19
- });
20
-
21
- // Test that LangWatch still works
22
- const tracer = getLangWatchTracer("vercel-first-test");
23
- await tracer.withActiveSpan("test span", async () => {
24
- expect(true).toBe(true);
25
- });
26
- });
27
- });
@@ -1,214 +0,0 @@
1
- vi.mock('../../evaluation/record-evaluation', () => ({
2
- recordEvaluation: vi.fn(),
3
- }));
4
-
5
- import { createLangWatchSpan } from '../span';
6
- import { recordEvaluation } from '../../evaluation/record-evaluation';
7
-
8
- import { describe, it, expect, vi, beforeEach } from 'vitest';
9
-
10
- const makeMockSpan = () => {
11
- const calls: any = { addEvent: [], setAttribute: [], end: 0, setAttributes: [], recordException: [], setStatus: [], updateName: [] };
12
- return {
13
- addEvent: vi.fn((...args) => { calls.addEvent.push(args); }),
14
- setAttribute: vi.fn((...args) => { calls.setAttribute.push(args); }),
15
- setAttributes: vi.fn((...args) => { calls.setAttributes.push(args); }),
16
- end: vi.fn(() => { calls.end++; }),
17
- recordException: vi.fn((...args) => { calls.recordException.push(args); }),
18
- setStatus: vi.fn((...args) => { calls.setStatus.push(args); }),
19
- updateName: vi.fn((...args) => { calls.updateName.push(args); }),
20
- calls,
21
- };
22
- };
23
-
24
- describe('createLangWatchSpan', () => {
25
- let span: ReturnType<typeof makeMockSpan>;
26
- let lwSpan: ReturnType<typeof createLangWatchSpan>;
27
- let intSemconv: any;
28
- let semconv: any;
29
-
30
- beforeEach(async () => {
31
- span = makeMockSpan();
32
- lwSpan = createLangWatchSpan(span as any);
33
- intSemconv = await import('../semconv/index.js');
34
- semconv = await import('@opentelemetry/semantic-conventions/incubating');
35
- });
36
-
37
- it('setType sets the span type attribute', () => {
38
- lwSpan.setType('llm');
39
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_SPAN_TYPE, 'llm');
40
- });
41
-
42
- it('setRequestModel sets the request model attribute', () => {
43
- lwSpan.setRequestModel('gpt-4');
44
- expect(span.setAttribute).toHaveBeenCalledWith(semconv.ATTR_GEN_AI_REQUEST_MODEL, 'gpt-4');
45
- });
46
-
47
- it('setResponseModel sets the response model attribute', () => {
48
- lwSpan.setResponseModel('gpt-4');
49
- expect(span.setAttribute).toHaveBeenCalledWith(semconv.ATTR_GEN_AI_RESPONSE_MODEL, 'gpt-4');
50
- });
51
-
52
- it('setRAGContexts sets the rag contexts attribute as JSON', () => {
53
- const ctxs = [{ document_id: 'd', chunk_id: 'c', content: 'x' }];
54
- lwSpan.setRAGContexts(ctxs);
55
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_RAG_CONTEXTS, JSON.stringify({ type: 'json', value: ctxs }));
56
- });
57
-
58
- it('setRAGContext sets a single rag context as JSON array', () => {
59
- const ctx = { document_id: 'd', chunk_id: 'c', content: 'x' };
60
- lwSpan.setRAGContext(ctx);
61
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_RAG_CONTEXTS, JSON.stringify({ type: 'json', value: [ctx] }));
62
- });
63
-
64
- it('setMetrics sets the metrics attribute as JSON', () => {
65
- const metrics = { promptTokens: 1, completionTokens: 2, cost: 3 };
66
- lwSpan.setMetrics(metrics);
67
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_METRICS, JSON.stringify({ type: 'json', value: metrics }));
68
- });
69
-
70
- it('setInput sets the input attribute as JSON', () => {
71
- lwSpan.setInput({ foo: 'bar' });
72
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_INPUT, JSON.stringify({ type: 'json', value: { foo: 'bar' } }));
73
- });
74
-
75
- it('setInputString sets the input attribute as text', () => {
76
- lwSpan.setInputString('prompt');
77
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_INPUT, JSON.stringify({ type: 'text', value: 'prompt' }));
78
- });
79
-
80
- it('setOutput sets the output attribute as JSON', () => {
81
- lwSpan.setOutput({ foo: 'bar' });
82
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'json', value: { foo: 'bar' } }));
83
- });
84
-
85
- it('setOutputString sets the output attribute as text', () => {
86
- lwSpan.setOutputString('completion');
87
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'text', value: 'completion' }));
88
- });
89
-
90
- it('setOutputEvaluation sets the output attribute as guardrail/evaluation result', () => {
91
- lwSpan.setOutputEvaluation(true, { status: 'processed', passed: true });
92
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'guardrail_result', value: { status: 'processed', passed: true } }));
93
- lwSpan.setOutputEvaluation(false, { status: 'processed', passed: false });
94
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'evaluation_result', value: { status: 'processed', passed: false } }));
95
- });
96
-
97
- it('addGenAISystemMessageEvent sets default role and adds event', () => {
98
- lwSpan.addGenAISystemMessageEvent({ content: 'hi' });
99
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_SYSTEM_MESSAGE, expect.objectContaining({
100
- [semconv.ATTR_GEN_AI_SYSTEM]: undefined,
101
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_BODY]: JSON.stringify({ content: 'hi', role: 'system' }),
102
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_IMPOSTER]: true,
103
- }));
104
- });
105
-
106
- it('addGenAIUserMessageEvent sets default role and adds event', () => {
107
- lwSpan.addGenAIUserMessageEvent({ content: 'hi' });
108
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_USER_MESSAGE, expect.objectContaining({
109
- [semconv.ATTR_GEN_AI_SYSTEM]: undefined,
110
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_BODY]: JSON.stringify({ content: 'hi', role: 'user' }),
111
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_IMPOSTER]: true,
112
- }));
113
- });
114
-
115
- it('addGenAIAssistantMessageEvent sets default role and adds event', () => {
116
- lwSpan.addGenAIAssistantMessageEvent({ content: 'hi' });
117
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_ASSISTANT_MESSAGE, expect.objectContaining({
118
- [semconv.ATTR_GEN_AI_SYSTEM]: undefined,
119
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_BODY]: JSON.stringify({ content: 'hi', role: 'assistant' }),
120
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_IMPOSTER]: true,
121
- }));
122
- });
123
-
124
- it('addGenAIToolMessageEvent sets default role and adds event', () => {
125
- lwSpan.addGenAIToolMessageEvent({ content: 'hi', id: 't1' });
126
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_TOOL_MESSAGE, expect.objectContaining({
127
- [semconv.ATTR_GEN_AI_SYSTEM]: undefined,
128
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_BODY]: JSON.stringify({ content: 'hi', id: 't1', role: 'tool' }),
129
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_IMPOSTER]: true,
130
- }));
131
- });
132
-
133
- it('addGenAIChoiceEvent sets default message.role and adds event', () => {
134
- lwSpan.addGenAIChoiceEvent({ finish_reason: 'stop', index: 0, message: { content: 'x' } });
135
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_CHOICE, expect.objectContaining({
136
- [semconv.ATTR_GEN_AI_SYSTEM]: undefined,
137
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_BODY]: JSON.stringify({ finish_reason: 'stop', index: 0, message: { content: 'x', role: 'assistant' } }),
138
- [intSemconv.ATTR_LANGWATCH_GEN_AI_LOG_EVENT_IMPOSTER]: true,
139
- }));
140
- });
141
-
142
- it('recordEvaluation calls recordEvaluation util', () => {
143
- const details = { name: 'eval', status: 'processed' as const };
144
- const attributes = { foo: 'bar' };
145
- const span = makeMockSpan();
146
- const lwSpan2 = createLangWatchSpan(span as any);
147
- lwSpan2.recordEvaluation(details, attributes);
148
- expect(recordEvaluation).toHaveBeenCalledWith(details, attributes);
149
- });
150
-
151
- it('supports fluent API chaining', () => {
152
- // Test that methods can be chained and each returns the LangWatchSpan instance
153
- const result = lwSpan
154
- .setType('llm')
155
- .setRequestModel('gpt-4')
156
- .setInputString('Hello')
157
- .addGenAIUserMessageEvent({ content: 'Hello' })
158
- .addGenAIAssistantMessageEvent({ content: 'Hi!' })
159
- .setOutputString('Hi!')
160
- .recordEvaluation({ name: 'test', status: 'processed' });
161
-
162
- // Verify that the result is the same LangWatchSpan instance
163
- expect(result).toBe(lwSpan);
164
-
165
- // Verify that all the expected methods were called
166
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_SPAN_TYPE, 'llm');
167
- expect(span.setAttribute).toHaveBeenCalledWith(semconv.ATTR_GEN_AI_REQUEST_MODEL, 'gpt-4');
168
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_INPUT, JSON.stringify({ type: 'text', value: 'Hello' }));
169
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'text', value: 'Hi!' }));
170
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_USER_MESSAGE, expect.any(Object));
171
- expect(span.addEvent).toHaveBeenCalledWith(intSemconv.LOG_EVNT_GEN_AI_ASSISTANT_MESSAGE, expect.any(Object));
172
- expect(recordEvaluation).toHaveBeenCalledWith({ name: 'test', status: 'processed' }, undefined);
173
- });
174
-
175
- it('maintains fluent API when mixing LangWatch and original span methods', () => {
176
- // Test that we can chain LangWatch methods with original span methods
177
- const result = lwSpan
178
- .setType('llm')
179
- .setAttribute('custom.attr', 'value') // Original span method
180
- .setInputString('Hello')
181
- .setAttributes({ 'another.attr': 'value' }) // Original span method
182
- .setOutputString('Hi!');
183
-
184
- // Verify that the result is the same LangWatchSpan instance
185
- expect(result).toBe(lwSpan);
186
-
187
- // Verify that both LangWatch and original span methods were called
188
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_SPAN_TYPE, 'llm');
189
- expect(span.setAttribute).toHaveBeenCalledWith('custom.attr', 'value');
190
- expect(span.setAttributes).toHaveBeenCalledWith({ 'another.attr': 'value' });
191
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_INPUT, JSON.stringify({ type: 'text', value: 'Hello' }));
192
- expect(span.setAttribute).toHaveBeenCalledWith(intSemconv.ATTR_LANGWATCH_OUTPUT, JSON.stringify({ type: 'text', value: 'Hi!' }));
193
- });
194
-
195
- it('forwards original span methods correctly', () => {
196
- // Test that original span methods work and return the LangWatchSpan for chaining
197
- const result = lwSpan
198
- .setAttribute('test', 'value')
199
- .setAttributes({ 'test2': 'value2' })
200
- .addEvent('test-event', { 'event-attr': 'value' })
201
- .setStatus({ code: 0 })
202
- .updateName('new-name');
203
-
204
- // Verify that the result is the same LangWatchSpan instance
205
- expect(result).toBe(lwSpan);
206
-
207
- // Verify that all the original span methods were called
208
- expect(span.setAttribute).toHaveBeenCalledWith('test', 'value');
209
- expect(span.setAttributes).toHaveBeenCalledWith({ 'test2': 'value2' });
210
- expect(span.addEvent).toHaveBeenCalledWith('test-event', { 'event-attr': 'value' });
211
- expect(span.setStatus).toHaveBeenCalledWith({ code: 0 });
212
- expect(span.updateName).toHaveBeenCalledWith('new-name');
213
- });
214
- });
@@ -1,180 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { getLangWatchTracer } from '../trace';
3
- import type { Tracer, Span, SpanOptions, Context } from '@opentelemetry/api';
4
-
5
- // Mock createLangWatchSpan to just tag the span for test visibility
6
- vi.mock('../span', () => ({
7
- createLangWatchSpan: (span: Span) => ({
8
- __isLangWatch: true,
9
- ...span,
10
- end: vi.fn(),
11
- setStatus: vi.fn(),
12
- recordException: vi.fn(),
13
- }),
14
- }));
15
-
16
- // Helper to create a mock Tracer
17
- function makeMockTracer() {
18
- return {
19
- startSpan: vi.fn((name, options, context) => ({ name, options, context })),
20
- startActiveSpan: vi.fn((...args: any[]) => {
21
- // OpenTelemetry's startActiveSpan calls the callback with the span
22
- const fn = args[args.length - 1];
23
- const span = { name: args[0], options: args[1], context: args[2] };
24
- return fn(span);
25
- }),
26
- someOtherMethod: vi.fn(() => 'other'),
27
- } as unknown as Tracer;
28
- }
29
-
30
- describe('getTracer', () => {
31
- const origGetTracer = vi.hoisted(() => vi.fn());
32
- let otelTrace: { getTracer: typeof origGetTracer };
33
-
34
- beforeEach(() => {
35
- otelTrace = require('@opentelemetry/api').trace;
36
- otelTrace.getTracer = vi.fn(() => makeMockTracer());
37
- });
38
-
39
- it('returns a proxy with startSpan wrapping the span', () => {
40
- const tracer = getLangWatchTracer('test');
41
- const span = tracer.startSpan('my-span', { foo: 'bar' } as SpanOptions, {} as Context);
42
- expect(span).toMatchObject({ __isLangWatch: true, name: 'my-span', options: { foo: 'bar' } });
43
- });
44
-
45
- it('returns a proxy with startActiveSpan wrapping the span in the callback', () => {
46
- const tracer = getLangWatchTracer('test');
47
- const result = tracer.startActiveSpan('active-span', (span: any) => {
48
- expect(span).toMatchObject({ __isLangWatch: true, name: 'active-span' });
49
- return 'done';
50
- });
51
- expect(result).toBe('done');
52
- });
53
-
54
- it('supports startActiveSpan with options and context overloads', () => {
55
- const tracer = getLangWatchTracer('test');
56
- let called = 0;
57
- tracer.startActiveSpan('span1', { foo: 1 } as SpanOptions, (span: any) => {
58
- expect(span).toMatchObject({ __isLangWatch: true, name: 'span1', options: { foo: 1 } });
59
- called++;
60
- });
61
- tracer.startActiveSpan('span2', { foo: 2 } as SpanOptions, {} as Context, (span: any) => {
62
- expect(span).toMatchObject({ __isLangWatch: true, name: 'span2', options: { foo: 2 }, context: {} });
63
- called++;
64
- });
65
- expect(called).toBe(2);
66
- });
67
-
68
- it('supports startActiveSpan with a callback that returns a Promise', async () => {
69
- const tracer = getLangWatchTracer('test');
70
- const result = await tracer.startActiveSpan('promise-span', async (span: any) => {
71
- expect(span).toMatchObject({ __isLangWatch: true, name: 'promise-span' });
72
- await new Promise((resolve) => setTimeout(resolve, 10));
73
- return 'async-done';
74
- });
75
- expect(result).toBe('async-done');
76
- });
77
-
78
- it('supports startActiveSpan with a callback that returns a thenable (Promise-like)', async () => {
79
- const tracer = getLangWatchTracer('test');
80
- const thenable = {
81
- then: (resolve: (v: string) => void) => setTimeout(() => resolve('thenable-done'), 10),
82
- };
83
- const result = await tracer.startActiveSpan('thenable-span', (_span: any) => thenable);
84
- expect(result).toBe('thenable-done');
85
- });
86
-
87
- it('forwards unknown methods to the underlying tracer', () => {
88
- const tracer = getLangWatchTracer('test');
89
- // @ts-expect-error
90
- expect(tracer.someOtherMethod()).toBe('other');
91
- });
92
-
93
- it('throws if startActiveSpan is called without a function', () => {
94
- const tracer = getLangWatchTracer('test');
95
- // @ts-expect-error
96
- expect(() => tracer.startActiveSpan('no-fn')).toThrow(/function as the last argument/);
97
- });
98
- });
99
-
100
- describe('getTracer (withActiveSpan)', () => {
101
- let tracer: ReturnType<typeof getLangWatchTracer>;
102
- beforeEach(() => {
103
- tracer = getLangWatchTracer('test');
104
- });
105
-
106
- it('returns a proxy with withActiveSpan wrapping the span', async () => {
107
- const result = await tracer.withActiveSpan('my-span', (span: any) => {
108
- expect(span).toMatchObject({ __isLangWatch: true, name: 'my-span' });
109
- return 'done';
110
- });
111
- expect(result).toBe('done');
112
- });
113
-
114
- it('supports withActiveSpan with options and context overloads', async () => {
115
- let called = 0;
116
- await tracer.withActiveSpan('span1', { foo: 1 } as SpanOptions, (span: any) => {
117
- expect(span).toMatchObject({ __isLangWatch: true, name: 'span1', options: { foo: 1 } });
118
- called++;
119
- });
120
- await tracer.withActiveSpan('span2', { foo: 2 } as SpanOptions, {} as Context, (span: any) => {
121
- expect(span).toMatchObject({ __isLangWatch: true, name: 'span2', options: { foo: 2 }, context: {} });
122
- called++;
123
- });
124
- expect(called).toBe(2);
125
- });
126
-
127
- it('supports withActiveSpan with a callback that returns a Promise', async () => {
128
- const result = await tracer.withActiveSpan('promise-span', async (span: any) => {
129
- expect(span).toMatchObject({ __isLangWatch: true, name: 'promise-span' });
130
- await new Promise((resolve) => setTimeout(resolve, 10));
131
- return 'async-done';
132
- });
133
- expect(result).toBe('async-done');
134
- });
135
-
136
- it('supports withActiveSpan with a callback that returns a thenable (Promise-like)', async () => {
137
- const thenable = {
138
- then: (resolve: (v: string) => void) => setTimeout(() => resolve('thenable-done'), 10),
139
- };
140
- const result = await tracer.withActiveSpan('thenable-span', (_span: any) => thenable as any);
141
- expect(result).toBe('thenable-done');
142
- });
143
-
144
- it('calls setStatus and recordException on error', async () => {
145
- const error = new Error('fail!');
146
- let spanRef: any = null;
147
- const resultPromise = tracer.withActiveSpan('err-span', (span: any) => {
148
- span.setStatus = vi.fn();
149
- span.recordException = vi.fn();
150
- spanRef = span;
151
- throw error;
152
- });
153
- await expect(resultPromise).rejects.toThrow('fail!');
154
- expect(spanRef.setStatus).toHaveBeenCalledWith({ code: expect.any(Number), message: 'fail!' });
155
- expect(spanRef.recordException).toHaveBeenCalledWith(error);
156
- });
157
-
158
- it('throws if withActiveSpan is called without a function', async () => {
159
- // @ts-expect-error
160
- await expect(tracer.withActiveSpan('no-fn')).rejects.toThrow(/function as the last argument/);
161
- });
162
-
163
- it('ensures nested withActiveSpan calls propagate context (parent-child)', async () => {
164
- const tracer = getLangWatchTracer('test');
165
- let parentSpanRef: any = null;
166
- let childSpanRef: any = null;
167
- await tracer.withActiveSpan('parent-span', (parentSpan: any) => {
168
- parentSpanRef = parentSpan;
169
- return tracer.withActiveSpan('child-span', (childSpan: any) => {
170
- childSpanRef = childSpan;
171
- return 'nested';
172
- });
173
- });
174
- // In the mock, context is just passed through, so we can check the parent/child linkage
175
- expect(childSpanRef.context).toBe(parentSpanRef.context);
176
- // Removed assertion on childSpanRef.options, as the mock does not reflect real OTel behavior
177
- expect(childSpanRef.name).toBe('child-span');
178
- expect(parentSpanRef.name).toBe('parent-span');
179
- });
180
- });
@@ -1 +0,0 @@
1
- export * from "./langwatch-exporter";
@@ -1,4 +0,0 @@
1
- export * from "./span";
2
- export * from "./trace";
3
- export * from "./processors";
4
- export * as semconv from "./semconv";
@@ -1,112 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { ChatOpenAI } from "@langchain/openai";
3
- import { LangWatchCallbackHandler } from "../../../langchain";
4
- import { setupLangWatch } from "../../../../../client-node";
5
- import { DynamicTool } from "@langchain/core/tools";
6
- import { AgentExecutor, createToolCallingAgent, initializeAgentExecutorWithOptions } from "langchain/agents";
7
- import { getLangWatchTracer } from "../../../../trace";
8
- import { ChatPromptTemplate } from "@langchain/core/prompts";
9
- import { beforeEach } from "node:test";
10
-
11
- beforeEach(async () => {
12
- await setupLangWatch();
13
- });
14
-
15
- describe("langchain chatbots", () => {
16
- it("it should be able to do a simple question/response", async () => {
17
- const tracer = getLangWatchTracer("langchain-chatbot.test");
18
- await tracer.withActiveSpan("simple question/response", { root: true }, async () => {
19
- const llm = new ChatOpenAI({
20
- model: "gpt-4o-mini",
21
- temperature: 0,
22
- });
23
-
24
- const result = await llm.invoke([
25
- { role: "user", content: "Hi im Bob" },
26
- ], { callbacks: [new LangWatchCallbackHandler()] });
27
- expect(result.content).toContain("Bob");
28
- });
29
- });
30
-
31
- it("it should be able to handle tool calls", async () => {
32
- const tools = [
33
- new DynamicTool({
34
- name: "get_current_time",
35
- description: "Returns the current time in ISO-8601 format.",
36
- func: async () => new Date().toISOString(),
37
- }),
38
-
39
- new DynamicTool({
40
- name: "multiply",
41
- description: 'Multiply two numbers, provide input like "a,b".',
42
- func: async (input: string) => {
43
- const [a, b] = input.split(",").map(Number);
44
-
45
- if (a === void 0 || b === void 0) {
46
- throw new Error("Invalid input");
47
- }
48
-
49
- return String(a * b);
50
- },
51
- }),
52
- ];
53
-
54
- const tracer = getLangWatchTracer("langchain-chatbot.test");
55
- await tracer.withActiveSpan(
56
- "langchain tool call",
57
- { root: true },
58
- async () => {
59
- const llm = new ChatOpenAI({
60
- model: "gpt-4.1-mini",
61
- temperature: 0,
62
- });
63
-
64
- const prompt = ChatPromptTemplate.fromMessages([
65
- ["system", "You are a helpful assistant"],
66
- ["placeholder", "{chat_history}"],
67
- ["human", "{input}"],
68
- ["placeholder", "{agent_scratchpad}"],
69
- ]);
70
-
71
- const agent = createToolCallingAgent({
72
- llm,
73
- tools,
74
- prompt,
75
- });
76
- const agentExecutor = new AgentExecutor({
77
- agent,
78
- tools,
79
- });
80
-
81
- const tracingCallback = new LangWatchCallbackHandler();
82
- const result = await agentExecutor.invoke({ input: "What time is it and what is 12 times 8?" }, { callbacks: [tracingCallback] });
83
- expect(result.output).toContain("96");
84
- },
85
- );
86
- });
87
-
88
- it("should understand context grouping", async () => {
89
- const tracer = getLangWatchTracer("langchain-chatbot.test");
90
- await tracer.withActiveSpan(
91
- "context grouping",
92
- { root: true },
93
- async () => {
94
- const llm = new ChatOpenAI({
95
- model: "gpt-4o-mini",
96
- temperature: 0,
97
- });
98
-
99
- const tracingCallback = new LangWatchCallbackHandler();
100
- const result1 = await llm.invoke([
101
- { role: "user", content: "Hi im Alice" },
102
- ], { callbacks: [tracingCallback] });
103
- const result2 = await llm.invoke([
104
- { role: "user", content: "Hi im Bob" },
105
- ], { callbacks: [tracingCallback] });
106
-
107
- expect(result1.content).toContain("Alice");
108
- expect(result2.content).toContain("Bob");
109
- },
110
- );
111
- });
112
- });