langwatch 0.1.1 → 0.1.3
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/copy-types.sh +17 -0
- package/dist/{chunk-OVS4NSDE.mjs → chunk-2I4YLOQY.mjs} +184 -115
- package/dist/chunk-2I4YLOQY.mjs.map +1 -0
- package/dist/index.d.mts +310 -4
- package/dist/index.d.ts +310 -4
- package/dist/index.js +1429 -1121
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +264 -26
- package/dist/index.mjs.map +1 -1
- package/dist/{utils-K-jSEpnZ.d.mts → utils-CFtM8VVg.d.mts} +107 -31
- package/dist/{utils-K-jSEpnZ.d.ts → utils-CFtM8VVg.d.ts} +107 -31
- package/dist/utils.d.mts +1 -2
- package/dist/utils.d.ts +1 -2
- package/dist/utils.js +181 -114
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +1 -1
- package/example/app/guardrails/page.tsx +26 -0
- package/example/components/header.tsx +4 -0
- package/example/lib/chat/guardrails.tsx +181 -0
- package/example/lib/chat/langchain-rag.tsx +1 -1
- package/example/lib/chat/langchain.tsx +1 -1
- package/example/lib/chat/vercel-ai.tsx +1 -1
- package/example/package-lock.json +4 -3
- package/package.json +3 -4
- package/src/evaluations.ts +219 -0
- package/src/index.test.ts +5 -0
- package/src/index.ts +182 -8
- package/src/typeUtils.ts +20 -2
- package/src/types.ts +6 -2
- package/src/utils.ts +4 -8
- package/ts-to-zod.config.js +2 -0
- package/dist/chunk-OVS4NSDE.mjs.map +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { type LangWatchSpan, type LangWatchTrace } from "./index";
|
|
2
|
+
import { type Conversation } from "./server/types/evaluations";
|
|
3
|
+
import {
|
|
4
|
+
type Evaluators,
|
|
5
|
+
type EvaluatorTypes,
|
|
6
|
+
} from "./server/types/evaluators.generated";
|
|
7
|
+
import {
|
|
8
|
+
type RAGChunk,
|
|
9
|
+
type SpanTypes,
|
|
10
|
+
type TypedValueEvaluationResult,
|
|
11
|
+
type TypedValueGuardrailResult,
|
|
12
|
+
type TypedValueJson,
|
|
13
|
+
} from "./server/types/tracer";
|
|
14
|
+
|
|
15
|
+
type Money = {
|
|
16
|
+
currency: string;
|
|
17
|
+
amount: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type EvaluationResultModel = {
|
|
21
|
+
status: "processed" | "skipped" | "error";
|
|
22
|
+
passed?: boolean;
|
|
23
|
+
score?: number;
|
|
24
|
+
details?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
cost?: Money;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type CommonEvaluationParams = {
|
|
30
|
+
name?: string;
|
|
31
|
+
input?: string;
|
|
32
|
+
output?: string;
|
|
33
|
+
expectedOutput?: string;
|
|
34
|
+
contexts?: RAGChunk[] | string[];
|
|
35
|
+
conversation?: Conversation;
|
|
36
|
+
asGuardrail?: boolean;
|
|
37
|
+
trace?: LangWatchTrace;
|
|
38
|
+
span?: LangWatchSpan;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type SavedEvaluationParams = {
|
|
42
|
+
slug: string;
|
|
43
|
+
settings?: Record<string, unknown>;
|
|
44
|
+
} & CommonEvaluationParams;
|
|
45
|
+
|
|
46
|
+
export type LangEvalsEvaluationParams<T extends EvaluatorTypes> = {
|
|
47
|
+
evaluator: T;
|
|
48
|
+
settings?: Evaluators[T]["settings"];
|
|
49
|
+
} & CommonEvaluationParams;
|
|
50
|
+
|
|
51
|
+
export type EvaluationParams =
|
|
52
|
+
| SavedEvaluationParams
|
|
53
|
+
| LangEvalsEvaluationParams<EvaluatorTypes>;
|
|
54
|
+
|
|
55
|
+
export const evaluate = async (
|
|
56
|
+
params: EvaluationParams
|
|
57
|
+
): Promise<EvaluationResultModel> => {
|
|
58
|
+
const slug = "slug" in params ? params.slug : params.evaluator;
|
|
59
|
+
const span = optionalCreateSpan({
|
|
60
|
+
trace: params.trace,
|
|
61
|
+
span: params.span,
|
|
62
|
+
name: params.name ? params.name : slug,
|
|
63
|
+
type: params.asGuardrail ? "guardrail" : "evaluation",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const requestParams = prepareData({
|
|
68
|
+
...params,
|
|
69
|
+
slug,
|
|
70
|
+
traceId: span?.trace.traceId,
|
|
71
|
+
spanId: span?.spanId,
|
|
72
|
+
span,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const response = await fetch(requestParams.url, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: requestParams.headers,
|
|
78
|
+
body: JSON.stringify(requestParams.json),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await response.json();
|
|
86
|
+
return handleResponse(result, span, params.asGuardrail);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return handleException(e as Error, span, params.asGuardrail);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const optionalCreateSpan = ({
|
|
93
|
+
trace,
|
|
94
|
+
span,
|
|
95
|
+
name,
|
|
96
|
+
type,
|
|
97
|
+
}: {
|
|
98
|
+
trace?: LangWatchTrace;
|
|
99
|
+
span?: LangWatchSpan;
|
|
100
|
+
name: string;
|
|
101
|
+
type: SpanTypes;
|
|
102
|
+
}): LangWatchSpan | undefined => {
|
|
103
|
+
if (span) {
|
|
104
|
+
return span.startSpan({ name, type });
|
|
105
|
+
} else if (trace) {
|
|
106
|
+
return trace.startSpan({ name, type });
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const prepareData = (params: {
|
|
112
|
+
slug: string;
|
|
113
|
+
name?: string;
|
|
114
|
+
input?: string;
|
|
115
|
+
output?: string;
|
|
116
|
+
expectedOutput?: string;
|
|
117
|
+
contexts?: RAGChunk[] | string[];
|
|
118
|
+
conversation?: Conversation;
|
|
119
|
+
settings?: Record<string, unknown>;
|
|
120
|
+
traceId?: string;
|
|
121
|
+
spanId?: string;
|
|
122
|
+
span?: LangWatchSpan;
|
|
123
|
+
asGuardrail?: boolean;
|
|
124
|
+
}) => {
|
|
125
|
+
const data: Record<string, unknown> = {};
|
|
126
|
+
if (params.input) data.input = params.input;
|
|
127
|
+
if (params.output) data.output = params.output;
|
|
128
|
+
if (params.expectedOutput) data.expected_output = params.expectedOutput;
|
|
129
|
+
if (params.contexts && params.contexts.length > 0)
|
|
130
|
+
data.contexts = params.contexts;
|
|
131
|
+
if (params.conversation && params.conversation.length > 0)
|
|
132
|
+
data.conversation = params.conversation;
|
|
133
|
+
|
|
134
|
+
if (params.span) {
|
|
135
|
+
params.span.update({
|
|
136
|
+
input: { type: "json", value: data } as TypedValueJson,
|
|
137
|
+
params: params.settings,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
url: `${process.env.LANGWATCH_ENDPOINT}/api/evaluations/${params.slug}/evaluate`,
|
|
143
|
+
json: {
|
|
144
|
+
trace_id: params.traceId,
|
|
145
|
+
span_id: params.spanId,
|
|
146
|
+
name: params.name,
|
|
147
|
+
data,
|
|
148
|
+
settings: params.settings,
|
|
149
|
+
as_guardrail: params.asGuardrail,
|
|
150
|
+
},
|
|
151
|
+
headers: {
|
|
152
|
+
"X-Auth-Token": process.env.LANGWATCH_API_KEY ?? "",
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const handleResponse = (
|
|
159
|
+
response: EvaluationResultModel,
|
|
160
|
+
span?: LangWatchSpan,
|
|
161
|
+
asGuardrail = false
|
|
162
|
+
): EvaluationResultModel => {
|
|
163
|
+
if (response.status === "error") {
|
|
164
|
+
response.details = response.details ?? "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const key of Object.keys(response)) {
|
|
168
|
+
if (
|
|
169
|
+
response[key as keyof EvaluationResultModel] === null ||
|
|
170
|
+
response[key as keyof EvaluationResultModel] === undefined
|
|
171
|
+
) {
|
|
172
|
+
delete response[key as keyof EvaluationResultModel];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (span) {
|
|
177
|
+
const output: TypedValueGuardrailResult | TypedValueEvaluationResult =
|
|
178
|
+
asGuardrail
|
|
179
|
+
? {
|
|
180
|
+
type: "guardrail_result",
|
|
181
|
+
value: response,
|
|
182
|
+
}
|
|
183
|
+
: {
|
|
184
|
+
type: "evaluation_result",
|
|
185
|
+
value: response,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
span.update({ output });
|
|
189
|
+
|
|
190
|
+
if (response.cost) {
|
|
191
|
+
span.update({
|
|
192
|
+
metrics: {
|
|
193
|
+
cost: response.cost.amount,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
span.end();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return response;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const handleException = (
|
|
205
|
+
e: Error,
|
|
206
|
+
span?: LangWatchSpan,
|
|
207
|
+
asGuardrail = false
|
|
208
|
+
): EvaluationResultModel => {
|
|
209
|
+
const response: EvaluationResultModel = {
|
|
210
|
+
status: "error",
|
|
211
|
+
details: e.toString(),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (asGuardrail) {
|
|
215
|
+
response.passed = true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return handleResponse(response, span, asGuardrail);
|
|
219
|
+
};
|
package/src/index.test.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { openai } from "@ai-sdk/openai";
|
|
12
12
|
import { generateText, type CoreMessage } from "ai";
|
|
13
13
|
import "dotenv/config";
|
|
14
|
+
import { version } from "../package.json";
|
|
14
15
|
|
|
15
16
|
describe("LangWatch tracer", () => {
|
|
16
17
|
let mockFetch: SpyInstanceFn;
|
|
@@ -65,6 +66,8 @@ describe("LangWatch tracer", () => {
|
|
|
65
66
|
threadId: "123",
|
|
66
67
|
userId: "456",
|
|
67
68
|
labels: ["foo", "bar"],
|
|
69
|
+
sdkLanguage: "typescript",
|
|
70
|
+
sdkVersion: version,
|
|
68
71
|
});
|
|
69
72
|
expect(span.timestamps.startedAt).toBeDefined();
|
|
70
73
|
expect(span.timestamps.finishedAt).toBeDefined();
|
|
@@ -133,6 +136,8 @@ describe("LangWatch tracer", () => {
|
|
|
133
136
|
thread_id: "123",
|
|
134
137
|
user_id: "456",
|
|
135
138
|
labels: ["foo", "bar"],
|
|
139
|
+
sdk_language: "typescript",
|
|
140
|
+
sdk_version: version,
|
|
136
141
|
});
|
|
137
142
|
expect(requestBody.spans.length).toBe(3);
|
|
138
143
|
});
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,19 @@ import EventEmitter from "events";
|
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import { ZodError } from "zod";
|
|
4
4
|
import { fromZodError } from "zod-validation-error";
|
|
5
|
-
import {
|
|
5
|
+
import { version } from "../package.json";
|
|
6
|
+
import {
|
|
7
|
+
evaluate,
|
|
8
|
+
type EvaluationParams,
|
|
9
|
+
type EvaluationResultModel,
|
|
10
|
+
} from "./evaluations";
|
|
11
|
+
import { LangWatchCallbackHandler } from "./langchain";
|
|
6
12
|
import {
|
|
7
13
|
type CollectorRESTParams,
|
|
14
|
+
type EvaluationResult,
|
|
8
15
|
type Span as ServerSpan,
|
|
9
16
|
type SpanTypes,
|
|
17
|
+
type TypedValueEvaluationResult,
|
|
10
18
|
} from "./server/types/tracer";
|
|
11
19
|
import {
|
|
12
20
|
collectorRESTParamsSchema,
|
|
@@ -22,10 +30,15 @@ import {
|
|
|
22
30
|
type PendingLLMSpan,
|
|
23
31
|
type PendingRAGSpan,
|
|
24
32
|
type RAGSpan,
|
|
33
|
+
type RESTEvaluation,
|
|
25
34
|
type SpanInputOutput,
|
|
26
35
|
} from "./types";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
36
|
+
import { camelToSnakeCaseNested, type Strict } from "./typeUtils";
|
|
37
|
+
import {
|
|
38
|
+
autoconvertTypedValues,
|
|
39
|
+
captureError,
|
|
40
|
+
convertFromVercelAIMessages,
|
|
41
|
+
} from "./utils";
|
|
29
42
|
|
|
30
43
|
export type {
|
|
31
44
|
BaseSpan,
|
|
@@ -40,7 +53,7 @@ export type {
|
|
|
40
53
|
SpanInputOutput,
|
|
41
54
|
};
|
|
42
55
|
|
|
43
|
-
export {
|
|
56
|
+
export { autoconvertTypedValues, captureError, convertFromVercelAIMessages };
|
|
44
57
|
|
|
45
58
|
export class LangWatch extends EventEmitter {
|
|
46
59
|
apiKey: string | undefined;
|
|
@@ -133,13 +146,35 @@ export class LangWatch extends EventEmitter {
|
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
148
|
|
|
149
|
+
type CurrentSpan = {
|
|
150
|
+
current: LangWatchSpan;
|
|
151
|
+
previous?: CurrentSpan;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type AddEvaluationParams = {
|
|
155
|
+
evaluationId?: string;
|
|
156
|
+
span?: LangWatchSpan;
|
|
157
|
+
name: string;
|
|
158
|
+
type?: string;
|
|
159
|
+
isGuardrail?: boolean;
|
|
160
|
+
status?: "processed" | "skipped" | "error";
|
|
161
|
+
passed?: boolean;
|
|
162
|
+
score?: number;
|
|
163
|
+
label?: string;
|
|
164
|
+
details?: string;
|
|
165
|
+
error?: Error;
|
|
166
|
+
timestamps?: RESTEvaluation["timestamps"];
|
|
167
|
+
};
|
|
168
|
+
|
|
136
169
|
export class LangWatchTrace {
|
|
137
170
|
client: LangWatch;
|
|
138
171
|
traceId: string;
|
|
139
172
|
metadata?: Metadata;
|
|
140
173
|
finishedSpans: Record<string, ServerSpan> = {};
|
|
141
|
-
timeoutRef?: NodeJS.Timeout;
|
|
142
174
|
langchainCallback?: LangWatchCallbackHandler;
|
|
175
|
+
evaluations: RESTEvaluation[] = [];
|
|
176
|
+
private currentSpan?: CurrentSpan;
|
|
177
|
+
private timeoutRef?: NodeJS.Timeout;
|
|
143
178
|
|
|
144
179
|
constructor({
|
|
145
180
|
client,
|
|
@@ -152,7 +187,11 @@ export class LangWatchTrace {
|
|
|
152
187
|
}) {
|
|
153
188
|
this.client = client;
|
|
154
189
|
this.traceId = traceId;
|
|
155
|
-
this.metadata =
|
|
190
|
+
this.metadata = {
|
|
191
|
+
...metadata,
|
|
192
|
+
sdkVersion: version,
|
|
193
|
+
sdkLanguage: "typescript",
|
|
194
|
+
};
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
update({ metadata }: { metadata: Metadata }) {
|
|
@@ -160,16 +199,37 @@ export class LangWatchTrace {
|
|
|
160
199
|
...this.metadata,
|
|
161
200
|
...metadata,
|
|
162
201
|
...(typeof metadata.labels !== "undefined"
|
|
163
|
-
? {
|
|
202
|
+
? {
|
|
203
|
+
labels: [
|
|
204
|
+
...(this.metadata?.labels ?? []),
|
|
205
|
+
...(metadata.labels ?? []),
|
|
206
|
+
],
|
|
207
|
+
}
|
|
164
208
|
: {}),
|
|
165
209
|
};
|
|
166
210
|
}
|
|
167
211
|
|
|
212
|
+
setCurrentSpan(span: LangWatchSpan) {
|
|
213
|
+
this.currentSpan = {
|
|
214
|
+
current: span,
|
|
215
|
+
previous: this.currentSpan,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getCurrentSpan() {
|
|
220
|
+
return this.currentSpan?.current;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
resetCurrentSpan() {
|
|
224
|
+
this.currentSpan = this.currentSpan?.previous;
|
|
225
|
+
}
|
|
226
|
+
|
|
168
227
|
startSpan(params: Omit<Partial<PendingBaseSpan>, "parentId">) {
|
|
169
228
|
const span = new LangWatchSpan({
|
|
170
229
|
trace: this,
|
|
171
230
|
...params,
|
|
172
231
|
});
|
|
232
|
+
this.setCurrentSpan(span);
|
|
173
233
|
return span;
|
|
174
234
|
}
|
|
175
235
|
|
|
@@ -178,6 +238,7 @@ export class LangWatchTrace {
|
|
|
178
238
|
trace: this,
|
|
179
239
|
...params,
|
|
180
240
|
});
|
|
241
|
+
this.setCurrentSpan(span);
|
|
181
242
|
return span;
|
|
182
243
|
}
|
|
183
244
|
|
|
@@ -186,9 +247,103 @@ export class LangWatchTrace {
|
|
|
186
247
|
trace: this,
|
|
187
248
|
...params,
|
|
188
249
|
});
|
|
250
|
+
this.setCurrentSpan(span);
|
|
189
251
|
return span;
|
|
190
252
|
}
|
|
191
253
|
|
|
254
|
+
addEvaluation = ({
|
|
255
|
+
evaluationId,
|
|
256
|
+
span,
|
|
257
|
+
name,
|
|
258
|
+
type,
|
|
259
|
+
isGuardrail,
|
|
260
|
+
status = "processed",
|
|
261
|
+
passed,
|
|
262
|
+
score,
|
|
263
|
+
label,
|
|
264
|
+
details,
|
|
265
|
+
error,
|
|
266
|
+
timestamps,
|
|
267
|
+
}: AddEvaluationParams): void => {
|
|
268
|
+
const currentEvaluationIndex = this.evaluations.findIndex(
|
|
269
|
+
(e) =>
|
|
270
|
+
evaluationId && "evaluationId" in e && e.evaluationId === evaluationId
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const currentEvaluation =
|
|
274
|
+
currentEvaluationIndex !== -1
|
|
275
|
+
? this.evaluations[currentEvaluationIndex]
|
|
276
|
+
: undefined;
|
|
277
|
+
|
|
278
|
+
const evaluationResult: EvaluationResult = {
|
|
279
|
+
status,
|
|
280
|
+
...(passed !== undefined && { passed }),
|
|
281
|
+
...(score !== undefined && { score }),
|
|
282
|
+
...(label !== undefined && { label }),
|
|
283
|
+
...(details !== undefined && { details }),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
let span_ = span;
|
|
287
|
+
if (!span_) {
|
|
288
|
+
span_ = this.startSpan({
|
|
289
|
+
type: "evaluation",
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (span_.type !== "evaluation") {
|
|
293
|
+
span_ = span_.startSpan({ type: "evaluation" });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
span_.update({
|
|
297
|
+
name,
|
|
298
|
+
output: {
|
|
299
|
+
type: "evaluation_result",
|
|
300
|
+
value: evaluationResult,
|
|
301
|
+
} as TypedValueEvaluationResult,
|
|
302
|
+
error,
|
|
303
|
+
timestamps: timestamps
|
|
304
|
+
? {
|
|
305
|
+
startedAt: timestamps.startedAt ?? span_.timestamps.startedAt,
|
|
306
|
+
finishedAt: timestamps.finishedAt ?? undefined,
|
|
307
|
+
}
|
|
308
|
+
: undefined,
|
|
309
|
+
});
|
|
310
|
+
span_.end();
|
|
311
|
+
|
|
312
|
+
const evaluation: RESTEvaluation = {
|
|
313
|
+
evaluationId: evaluationId ?? `eval_${nanoid()}`,
|
|
314
|
+
spanId: span_.spanId,
|
|
315
|
+
name,
|
|
316
|
+
type,
|
|
317
|
+
isGuardrail,
|
|
318
|
+
status,
|
|
319
|
+
passed,
|
|
320
|
+
score,
|
|
321
|
+
label,
|
|
322
|
+
details,
|
|
323
|
+
error: error ? captureError(error) : undefined,
|
|
324
|
+
timestamps: timestamps ?? {
|
|
325
|
+
startedAt: span_.timestamps.startedAt,
|
|
326
|
+
finishedAt: span_.timestamps.finishedAt,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (currentEvaluation && currentEvaluationIndex !== -1) {
|
|
331
|
+
this.evaluations[currentEvaluationIndex] = {
|
|
332
|
+
...currentEvaluation,
|
|
333
|
+
...evaluation,
|
|
334
|
+
};
|
|
335
|
+
} else {
|
|
336
|
+
this.evaluations.push(evaluation);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
async evaluate(params: EvaluationParams): Promise<EvaluationResultModel> {
|
|
341
|
+
return evaluate({
|
|
342
|
+
trace: this,
|
|
343
|
+
...params,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
192
347
|
getLangChainCallback() {
|
|
193
348
|
if (!this.langchainCallback) {
|
|
194
349
|
this.langchainCallback = new LangWatchCallbackHandler({ trace: this });
|
|
@@ -198,6 +353,7 @@ export class LangWatchTrace {
|
|
|
198
353
|
|
|
199
354
|
onEnd(span: ServerSpan) {
|
|
200
355
|
this.finishedSpans[span.span_id] = span;
|
|
356
|
+
this.resetCurrentSpan();
|
|
201
357
|
this.delayedSendSpans();
|
|
202
358
|
}
|
|
203
359
|
|
|
@@ -215,8 +371,9 @@ export class LangWatchTrace {
|
|
|
215
371
|
try {
|
|
216
372
|
trace = collectorRESTParamsSchema.parse({
|
|
217
373
|
trace_id: this.traceId,
|
|
218
|
-
metadata: camelToSnakeCaseNested(this.metadata),
|
|
374
|
+
metadata: camelToSnakeCaseNested(this.metadata, "metadata"),
|
|
219
375
|
spans: Object.values(this.finishedSpans),
|
|
376
|
+
evaluations: camelToSnakeCaseNested(this.evaluations),
|
|
220
377
|
} as Strict<CollectorRESTParams>);
|
|
221
378
|
} catch (error) {
|
|
222
379
|
if (error instanceof ZodError) {
|
|
@@ -309,6 +466,7 @@ export class LangWatchSpan implements PendingBaseSpan {
|
|
|
309
466
|
parentId: this.spanId,
|
|
310
467
|
...params,
|
|
311
468
|
});
|
|
469
|
+
this.trace.setCurrentSpan(span);
|
|
312
470
|
return span;
|
|
313
471
|
}
|
|
314
472
|
|
|
@@ -318,6 +476,7 @@ export class LangWatchSpan implements PendingBaseSpan {
|
|
|
318
476
|
parentId: this.spanId,
|
|
319
477
|
...params,
|
|
320
478
|
});
|
|
479
|
+
this.trace.setCurrentSpan(span);
|
|
321
480
|
return span;
|
|
322
481
|
}
|
|
323
482
|
|
|
@@ -327,9 +486,24 @@ export class LangWatchSpan implements PendingBaseSpan {
|
|
|
327
486
|
parentId: this.spanId,
|
|
328
487
|
...params,
|
|
329
488
|
});
|
|
489
|
+
this.trace.setCurrentSpan(span);
|
|
330
490
|
return span;
|
|
331
491
|
}
|
|
332
492
|
|
|
493
|
+
addEvaluation(params: AddEvaluationParams) {
|
|
494
|
+
this.trace.addEvaluation({
|
|
495
|
+
...params,
|
|
496
|
+
span: this,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async evaluate(params: EvaluationParams): Promise<EvaluationResultModel> {
|
|
501
|
+
return evaluate({
|
|
502
|
+
span: this,
|
|
503
|
+
...params,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
333
507
|
end(params?: Partial<Omit<PendingBaseSpan, "spanId" | "parentId">>) {
|
|
334
508
|
this.timestamps.finishedAt = Date.now();
|
|
335
509
|
if (params) {
|
package/src/typeUtils.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
reservedSpanParamsSchema,
|
|
3
|
+
reservedTraceMetadataSchema
|
|
4
|
+
} from "./server/types/tracer.generated";
|
|
5
|
+
|
|
1
6
|
export type Strict<T> = T & { [K in Exclude<keyof any, keyof T>]: never };
|
|
2
7
|
|
|
3
8
|
type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
|
|
@@ -44,7 +49,10 @@ function camelToSnakeCase(str: string): string {
|
|
|
44
49
|
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
export function camelToSnakeCaseNested<T>(
|
|
52
|
+
export function camelToSnakeCaseNested<T>(
|
|
53
|
+
obj: T,
|
|
54
|
+
parentKey?: string
|
|
55
|
+
): CamelToSnakeCaseNested<T> {
|
|
48
56
|
if (Array.isArray(obj)) {
|
|
49
57
|
return obj.map((item) =>
|
|
50
58
|
camelToSnakeCaseNested(item)
|
|
@@ -54,7 +62,17 @@ export function camelToSnakeCaseNested<T>(obj: T): CamelToSnakeCaseNested<T> {
|
|
|
54
62
|
for (const key in obj) {
|
|
55
63
|
if (obj.hasOwnProperty(key)) {
|
|
56
64
|
const newKey = camelToSnakeCase(key);
|
|
57
|
-
|
|
65
|
+
// Keep arbitrary keys the same
|
|
66
|
+
if (
|
|
67
|
+
(parentKey === "metadata" &&
|
|
68
|
+
!Object.keys(reservedTraceMetadataSchema.shape).includes(newKey)) ||
|
|
69
|
+
(parentKey === "params" &&
|
|
70
|
+
!Object.keys(reservedSpanParamsSchema.shape).includes(newKey))
|
|
71
|
+
) {
|
|
72
|
+
newObj[key] = (obj as any)[key];
|
|
73
|
+
} else {
|
|
74
|
+
newObj[newKey] = camelToSnakeCaseNested((obj as any)[key], newKey);
|
|
75
|
+
}
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
return newObj as CamelToSnakeCaseNested<T>;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type modelPrices from "llm-cost/model_prices_and_context_window.json";
|
|
2
1
|
import type { OpenAI } from "openai";
|
|
3
2
|
import { type SnakeToCamelCaseNested } from "./typeUtils";
|
|
4
3
|
import {
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
type SpanInputOutput as ServerSpanInputOutput,
|
|
11
10
|
type TypedValueChatMessages,
|
|
12
11
|
type Trace,
|
|
12
|
+
type RESTEvaluation as ServerRESTEvaluation,
|
|
13
13
|
} from "./server/types/tracer";
|
|
14
14
|
|
|
15
15
|
export type Metadata = SnakeToCamelCaseNested<Trace["metadata"]>;
|
|
@@ -63,8 +63,12 @@ export type PendingBaseSpan = PendingSpan<BaseSpan>;
|
|
|
63
63
|
// vendor is deprecated, and we try to force the available models here
|
|
64
64
|
export type LLMSpan = ConvertServerSpan<
|
|
65
65
|
Omit<ServerLLMSpan, "vendor" | "model">
|
|
66
|
-
> & { model:
|
|
66
|
+
> & { model: string };
|
|
67
67
|
export type PendingLLMSpan = PendingSpan<LLMSpan>;
|
|
68
68
|
|
|
69
69
|
export type RAGSpan = ConvertServerSpan<ServerRAGSpan>;
|
|
70
70
|
export type PendingRAGSpan = PendingSpan<RAGSpan>;
|
|
71
|
+
|
|
72
|
+
export type RESTEvaluation = SnakeToCamelCaseNested<
|
|
73
|
+
Omit<ServerRESTEvaluation, "error">
|
|
74
|
+
> & { error?: ServerRESTEvaluation["error"] };
|
package/src/utils.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
|
|
2
|
-
import { type
|
|
3
|
-
import { type ChatMessage, type SpanInputOutput } from "./types";
|
|
4
|
-
import { type ErrorCapture } from "./server/types/tracer";
|
|
5
|
-
import {
|
|
6
|
-
chatMessageSchema,
|
|
7
|
-
spanInputOutputSchema,
|
|
8
|
-
typedValueChatMessagesSchema,
|
|
9
|
-
} from "./server/types/tracer.generated";
|
|
2
|
+
import { type CoreMessage, type ImagePart } from "ai";
|
|
10
3
|
import { z } from "zod";
|
|
4
|
+
import { type ErrorCapture } from "./server/types/tracer";
|
|
5
|
+
import { chatMessageSchema } from "./server/types/tracer.generated";
|
|
6
|
+
import { type ChatMessage, type SpanInputOutput } from "./types";
|
|
11
7
|
|
|
12
8
|
const convertImageToUrl = (
|
|
13
9
|
image: ImagePart["image"],
|