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.
- package/.editorconfig +16 -0
- package/LICENSE +7 -0
- package/README.md +268 -1
- package/copy-types.sh +19 -8
- package/examples/langchain/.env.example +2 -0
- package/examples/langchain/README.md +42 -0
- package/examples/langchain/package-lock.json +2930 -0
- package/examples/langchain/package.json +27 -0
- package/examples/langchain/src/cli-markdown.d.ts +137 -0
- package/examples/langchain/src/index.ts +109 -0
- package/examples/langchain/tsconfig.json +25 -0
- package/examples/langgraph/.env.example +2 -0
- package/examples/langgraph/README.md +42 -0
- package/examples/langgraph/package-lock.json +3031 -0
- package/examples/langgraph/package.json +28 -0
- package/examples/langgraph/src/cli-markdown.d.ts +137 -0
- package/examples/langgraph/src/index.ts +196 -0
- package/examples/langgraph/tsconfig.json +25 -0
- package/examples/mastra/.env.example +2 -0
- package/examples/mastra/README.md +57 -0
- package/examples/mastra/package-lock.json +5296 -0
- package/examples/mastra/package.json +32 -0
- package/examples/mastra/src/cli-markdown.d.ts +137 -0
- package/examples/mastra/src/index.ts +120 -0
- package/examples/mastra/src/mastra/agents/weather-agent.ts +30 -0
- package/examples/mastra/src/mastra/index.ts +21 -0
- package/examples/mastra/src/mastra/tools/weather-tool.ts +102 -0
- package/examples/mastra/tsconfig.json +25 -0
- package/examples/vercel-ai/.env.example +2 -0
- package/examples/vercel-ai/README.md +38 -0
- package/examples/vercel-ai/package-lock.json +2571 -0
- package/examples/vercel-ai/package.json +27 -0
- package/examples/vercel-ai/src/cli-markdown.d.ts +137 -0
- package/examples/vercel-ai/src/index.ts +110 -0
- package/examples/vercel-ai/src/instrumentation.ts +9 -0
- package/examples/vercel-ai/tsconfig.json +25 -0
- package/package.json +78 -34
- package/src/__tests__/client-browser.test.ts +92 -0
- package/src/__tests__/client-node.test.ts +76 -0
- package/src/__tests__/client.test.ts +71 -0
- package/src/__tests__/integration/client-browser.test.ts +46 -0
- package/src/__tests__/integration/client-node.test.ts +46 -0
- package/src/client-browser.ts +70 -0
- package/src/client-node.ts +82 -0
- package/src/client-shared.ts +72 -0
- package/src/client.ts +119 -0
- package/src/evaluation/__tests__/record-evaluation.test.ts +112 -0
- package/src/evaluation/__tests__/run-evaluation.test.ts +171 -0
- package/src/evaluation/index.ts +2 -0
- package/src/evaluation/record-evaluation.ts +101 -0
- package/src/evaluation/run-evaluation.ts +133 -0
- package/src/evaluation/tracer.ts +3 -0
- package/src/evaluation/types.ts +23 -0
- package/src/index.ts +10 -593
- package/src/internal/api/__tests__/errors.test.ts +98 -0
- package/src/internal/api/client.ts +30 -0
- package/src/internal/api/errors.ts +32 -0
- package/src/internal/generated/types/.gitkeep +0 -0
- package/src/observability/__tests__/integration/base.test.ts +74 -0
- package/src/observability/__tests__/integration/browser-setup-ordering.test.ts +60 -0
- package/src/observability/__tests__/integration/complex-nested-spans.test.ts +29 -0
- package/src/observability/__tests__/integration/error-handling.test.ts +24 -0
- package/src/observability/__tests__/integration/langwatch-disabled-otel.test.ts +24 -0
- package/src/observability/__tests__/integration/langwatch-first-then-vercel.test.ts +24 -0
- package/src/observability/__tests__/integration/multiple-setup-attempts.test.ts +27 -0
- package/src/observability/__tests__/integration/otel-ordering.test.ts +27 -0
- package/src/observability/__tests__/integration/vercel-configurations.test.ts +20 -0
- package/src/observability/__tests__/integration/vercel-first-then-langwatch.test.ts +27 -0
- package/src/observability/__tests__/span.test.ts +214 -0
- package/src/observability/__tests__/trace.test.ts +180 -0
- package/src/observability/exporters/index.ts +1 -0
- package/src/observability/exporters/langwatch-exporter.ts +53 -0
- package/src/observability/index.ts +4 -0
- package/src/observability/instrumentation/langchain/__tests__/integration/langchain-chatbot.test.ts +112 -0
- package/src/observability/instrumentation/langchain/__tests__/langchain.test.ts +284 -0
- package/src/observability/instrumentation/langchain/index.ts +624 -0
- package/src/observability/processors/__tests__/filterable-batch-span-exporter.test.ts +98 -0
- package/src/observability/processors/filterable-batch-span-processor.ts +99 -0
- package/src/observability/processors/index.ts +1 -0
- package/src/observability/semconv/attributes.ts +185 -0
- package/src/observability/semconv/events.ts +42 -0
- package/src/observability/semconv/index.ts +16 -0
- package/src/observability/semconv/values.ts +159 -0
- package/src/observability/span.ts +728 -0
- package/src/observability/trace.ts +301 -0
- package/src/prompt/__tests__/prompt.test.ts +139 -0
- package/src/prompt/get-prompt-version.ts +49 -0
- package/src/prompt/get-prompt.ts +44 -0
- package/src/prompt/index.ts +3 -0
- package/src/prompt/prompt.ts +133 -0
- package/src/prompt/service.ts +221 -0
- package/src/prompt/tracer.ts +3 -0
- package/src/prompt/types.ts +0 -0
- package/ts-to-zod.config.js +11 -0
- package/tsconfig.json +3 -9
- package/tsup.config.ts +11 -1
- package/vitest.config.ts +1 -0
- package/dist/chunk-LKD2K67J.mjs +0 -717
- package/dist/chunk-LKD2K67J.mjs.map +0 -1
- package/dist/index.d.mts +0 -1030
- package/dist/index.d.ts +0 -1030
- package/dist/index.js +0 -27310
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -963
- package/dist/index.mjs.map +0 -1
- package/dist/utils-Cv-rUjJ1.d.mts +0 -313
- package/dist/utils-Cv-rUjJ1.d.ts +0 -313
- package/dist/utils.d.mts +0 -2
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -709
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs +0 -11
- package/dist/utils.mjs.map +0 -1
- package/example/.env.example +0 -12
- package/example/.eslintrc.json +0 -26
- package/example/LICENSE +0 -13
- package/example/README.md +0 -12
- package/example/app/(chat)/chat/[id]/page.tsx +0 -60
- package/example/app/(chat)/layout.tsx +0 -14
- package/example/app/(chat)/page.tsx +0 -27
- package/example/app/actions.ts +0 -156
- package/example/app/globals.css +0 -76
- package/example/app/guardrails/page.tsx +0 -26
- package/example/app/langchain/page.tsx +0 -27
- package/example/app/langchain-rag/page.tsx +0 -28
- package/example/app/late-update/page.tsx +0 -27
- package/example/app/layout.tsx +0 -64
- package/example/app/login/actions.ts +0 -71
- package/example/app/login/page.tsx +0 -18
- package/example/app/manual/page.tsx +0 -27
- package/example/app/new/page.tsx +0 -5
- package/example/app/opengraph-image.png +0 -0
- package/example/app/share/[id]/page.tsx +0 -58
- package/example/app/signup/actions.ts +0 -111
- package/example/app/signup/page.tsx +0 -18
- package/example/app/twitter-image.png +0 -0
- package/example/auth.config.ts +0 -42
- package/example/auth.ts +0 -45
- package/example/components/button-scroll-to-bottom.tsx +0 -36
- package/example/components/chat-history.tsx +0 -49
- package/example/components/chat-list.tsx +0 -52
- package/example/components/chat-message-actions.tsx +0 -40
- package/example/components/chat-message.tsx +0 -80
- package/example/components/chat-panel.tsx +0 -139
- package/example/components/chat-share-dialog.tsx +0 -95
- package/example/components/chat.tsx +0 -84
- package/example/components/clear-history.tsx +0 -75
- package/example/components/empty-screen.tsx +0 -38
- package/example/components/external-link.tsx +0 -29
- package/example/components/footer.tsx +0 -19
- package/example/components/header.tsx +0 -114
- package/example/components/login-button.tsx +0 -42
- package/example/components/login-form.tsx +0 -97
- package/example/components/markdown.tsx +0 -9
- package/example/components/prompt-form.tsx +0 -115
- package/example/components/providers.tsx +0 -17
- package/example/components/sidebar-actions.tsx +0 -125
- package/example/components/sidebar-desktop.tsx +0 -19
- package/example/components/sidebar-footer.tsx +0 -16
- package/example/components/sidebar-item.tsx +0 -124
- package/example/components/sidebar-items.tsx +0 -42
- package/example/components/sidebar-list.tsx +0 -38
- package/example/components/sidebar-mobile.tsx +0 -31
- package/example/components/sidebar-toggle.tsx +0 -24
- package/example/components/sidebar.tsx +0 -21
- package/example/components/signup-form.tsx +0 -95
- package/example/components/stocks/events-skeleton.tsx +0 -31
- package/example/components/stocks/events.tsx +0 -30
- package/example/components/stocks/index.tsx +0 -36
- package/example/components/stocks/message.tsx +0 -134
- package/example/components/stocks/spinner.tsx +0 -16
- package/example/components/stocks/stock-purchase.tsx +0 -146
- package/example/components/stocks/stock-skeleton.tsx +0 -22
- package/example/components/stocks/stock.tsx +0 -210
- package/example/components/stocks/stocks-skeleton.tsx +0 -9
- package/example/components/stocks/stocks.tsx +0 -67
- package/example/components/tailwind-indicator.tsx +0 -14
- package/example/components/theme-toggle.tsx +0 -31
- package/example/components/ui/alert-dialog.tsx +0 -141
- package/example/components/ui/badge.tsx +0 -36
- package/example/components/ui/button.tsx +0 -57
- package/example/components/ui/codeblock.tsx +0 -148
- package/example/components/ui/dialog.tsx +0 -122
- package/example/components/ui/dropdown-menu.tsx +0 -205
- package/example/components/ui/icons.tsx +0 -507
- package/example/components/ui/input.tsx +0 -25
- package/example/components/ui/label.tsx +0 -26
- package/example/components/ui/select.tsx +0 -164
- package/example/components/ui/separator.tsx +0 -31
- package/example/components/ui/sheet.tsx +0 -140
- package/example/components/ui/sonner.tsx +0 -31
- package/example/components/ui/switch.tsx +0 -29
- package/example/components/ui/textarea.tsx +0 -24
- package/example/components/ui/tooltip.tsx +0 -30
- package/example/components/user-menu.tsx +0 -53
- package/example/components.json +0 -17
- package/example/instrumentation.ts +0 -11
- package/example/lib/chat/guardrails.tsx +0 -181
- package/example/lib/chat/langchain-rag.tsx +0 -191
- package/example/lib/chat/langchain.tsx +0 -112
- package/example/lib/chat/late-update.tsx +0 -208
- package/example/lib/chat/manual.tsx +0 -605
- package/example/lib/chat/vercel-ai.tsx +0 -576
- package/example/lib/hooks/use-copy-to-clipboard.tsx +0 -33
- package/example/lib/hooks/use-enter-submit.tsx +0 -23
- package/example/lib/hooks/use-local-storage.ts +0 -24
- package/example/lib/hooks/use-scroll-anchor.tsx +0 -86
- package/example/lib/hooks/use-sidebar.tsx +0 -60
- package/example/lib/hooks/use-streamable-text.ts +0 -25
- package/example/lib/types.ts +0 -41
- package/example/lib/utils.ts +0 -89
- package/example/middleware.ts +0 -8
- package/example/next-env.d.ts +0 -5
- package/example/next.config.js +0 -16
- package/example/package-lock.json +0 -10917
- package/example/package.json +0 -84
- package/example/pnpm-lock.yaml +0 -5712
- package/example/postcss.config.js +0 -6
- package/example/prettier.config.cjs +0 -34
- package/example/public/apple-touch-icon.png +0 -0
- package/example/public/favicon-16x16.png +0 -0
- package/example/public/favicon.ico +0 -0
- package/example/public/next.svg +0 -1
- package/example/public/thirteen.svg +0 -1
- package/example/public/vercel.svg +0 -1
- package/example/tailwind.config.ts +0 -81
- package/example/tsconfig.json +0 -35
- package/src/LangWatchExporter.ts +0 -96
- package/src/evaluations.ts +0 -219
- package/src/index.test.ts +0 -402
- package/src/langchain.ts +0 -557
- package/src/typeUtils.ts +0 -89
- package/src/types.ts +0 -82
- package/src/utils.ts +0 -205
- /package/src/{server/types → internal/generated/openapi}/.gitkeep +0 -0
package/src/index.ts
CHANGED
|
@@ -1,596 +1,13 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
});
|