@langwatch/mcp-server 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +97 -25
- package/dist/chunk-AAQNA53E.js +28 -0
- package/dist/chunk-AAQNA53E.js.map +1 -0
- package/dist/chunk-HOPTUDCZ.js +90 -0
- package/dist/chunk-HOPTUDCZ.js.map +1 -0
- package/dist/chunk-ZXKLPC2E.js +27 -0
- package/dist/chunk-ZXKLPC2E.js.map +1 -0
- package/dist/config-FIQWQRUB.js +11 -0
- package/dist/config-FIQWQRUB.js.map +1 -0
- package/dist/create-prompt-UBC537BJ.js +22 -0
- package/dist/create-prompt-UBC537BJ.js.map +1 -0
- package/dist/discover-schema-3T52ORPB.js +446 -0
- package/dist/discover-schema-3T52ORPB.js.map +1 -0
- package/dist/get-analytics-3IFTN6MY.js +55 -0
- package/dist/get-analytics-3IFTN6MY.js.map +1 -0
- package/dist/get-prompt-2ZB5B3QC.js +48 -0
- package/dist/get-prompt-2ZB5B3QC.js.map +1 -0
- package/dist/get-trace-7IXKKCJJ.js +50 -0
- package/dist/get-trace-7IXKKCJJ.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20003 -0
- package/dist/index.js.map +1 -0
- package/dist/list-prompts-J72LTP7Z.js +33 -0
- package/dist/list-prompts-J72LTP7Z.js.map +1 -0
- package/dist/search-traces-RW2NDHN5.js +72 -0
- package/dist/search-traces-RW2NDHN5.js.map +1 -0
- package/dist/update-prompt-G6HHZSUM.js +31 -0
- package/dist/update-prompt-G6HHZSUM.js.map +1 -0
- package/package.json +8 -8
- package/src/__tests__/config.unit.test.ts +89 -0
- package/src/__tests__/date-parsing.unit.test.ts +78 -0
- package/src/__tests__/discover-schema.unit.test.ts +118 -0
- package/src/__tests__/integration.integration.test.ts +313 -0
- package/src/__tests__/langwatch-api.unit.test.ts +309 -0
- package/src/__tests__/schemas.unit.test.ts +85 -0
- package/src/__tests__/tools.unit.test.ts +729 -0
- package/src/config.ts +31 -0
- package/src/index.ts +254 -0
- package/src/langwatch-api.ts +265 -0
- package/src/schemas/analytics-groups.ts +78 -0
- package/src/schemas/analytics-metrics.ts +179 -0
- package/src/schemas/filter-fields.ts +119 -0
- package/src/schemas/index.ts +3 -0
- package/src/tools/create-prompt.ts +29 -0
- package/src/tools/discover-schema.ts +106 -0
- package/src/tools/get-analytics.ts +71 -0
- package/src/tools/get-prompt.ts +56 -0
- package/src/tools/get-trace.ts +61 -0
- package/src/tools/list-prompts.ts +35 -0
- package/src/tools/search-traces.ts +91 -0
- package/src/tools/update-prompt.ts +44 -0
- package/src/utils/date-parsing.ts +31 -0
- package/tests/evaluations.ipynb +634 -634
- package/tests/scenario-openai.test.ts +3 -1
package/src/config.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface McpConfig {
|
|
2
|
+
apiKey: string | undefined;
|
|
3
|
+
endpoint: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let config: McpConfig | undefined;
|
|
7
|
+
|
|
8
|
+
export function initConfig(args: { apiKey?: string; endpoint?: string }): void {
|
|
9
|
+
config = {
|
|
10
|
+
apiKey: args.apiKey || process.env.LANGWATCH_API_KEY,
|
|
11
|
+
endpoint:
|
|
12
|
+
args.endpoint ||
|
|
13
|
+
process.env.LANGWATCH_ENDPOINT ||
|
|
14
|
+
"https://app.langwatch.ai",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getConfig(): McpConfig {
|
|
19
|
+
if (!config) throw new Error("Config not initialized");
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function requireApiKey(): string {
|
|
24
|
+
const { apiKey } = getConfig();
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"LANGWATCH_API_KEY is required. Set it via --apiKey flag or LANGWATCH_API_KEY environment variable."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return apiKey;
|
|
31
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
import { hideBin } from "yargs/helpers";
|
|
3
5
|
import { z } from "zod";
|
|
4
6
|
|
|
5
7
|
import packageJson from "../package.json" assert { type: "json" };
|
|
8
|
+
import { initConfig } from "./config.js";
|
|
9
|
+
|
|
10
|
+
const argv = await yargs(hideBin(process.argv))
|
|
11
|
+
.option("apiKey", {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "LangWatch API key",
|
|
14
|
+
})
|
|
15
|
+
.option("endpoint", {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "LangWatch API endpoint",
|
|
18
|
+
})
|
|
19
|
+
.help()
|
|
20
|
+
.parseAsync();
|
|
21
|
+
|
|
22
|
+
initConfig({
|
|
23
|
+
apiKey: argv.apiKey,
|
|
24
|
+
endpoint: argv.endpoint,
|
|
25
|
+
});
|
|
6
26
|
|
|
7
27
|
const transport = new StdioServerTransport();
|
|
8
28
|
const server = new McpServer({
|
|
@@ -70,4 +90,238 @@ server.tool(
|
|
|
70
90
|
}
|
|
71
91
|
);
|
|
72
92
|
|
|
93
|
+
// --- Observability Tools (require API key) ---
|
|
94
|
+
|
|
95
|
+
server.tool(
|
|
96
|
+
"discover_schema",
|
|
97
|
+
"Discover available filter fields, metrics, aggregation types, and group-by options for LangWatch queries. Call this before using search_traces or get_analytics to understand available options.",
|
|
98
|
+
{
|
|
99
|
+
category: z
|
|
100
|
+
.enum(["filters", "metrics", "aggregations", "groups", "all"])
|
|
101
|
+
.describe("Which schema category to discover"),
|
|
102
|
+
},
|
|
103
|
+
async ({ category }) => {
|
|
104
|
+
const { formatSchema } = await import("./tools/discover-schema.js");
|
|
105
|
+
return { content: [{ type: "text", text: formatSchema(category) }] };
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
server.tool(
|
|
110
|
+
"search_traces",
|
|
111
|
+
"Search LangWatch traces with filters, text query, and date range. Returns AI-readable trace digests by default. Use format: 'json' for full raw data.",
|
|
112
|
+
{
|
|
113
|
+
query: z.string().optional().describe("Text search query"),
|
|
114
|
+
filters: z
|
|
115
|
+
.record(z.string(), z.array(z.string()))
|
|
116
|
+
.optional()
|
|
117
|
+
.describe(
|
|
118
|
+
'Filter traces. Format: {"field": ["value"]}. Use discover_schema for field names.'
|
|
119
|
+
),
|
|
120
|
+
startDate: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe(
|
|
124
|
+
'Start date: ISO string or relative like "24h", "7d", "30d". Default: 24h ago'
|
|
125
|
+
),
|
|
126
|
+
endDate: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("End date: ISO string or relative. Default: now"),
|
|
130
|
+
pageSize: z
|
|
131
|
+
.number()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Results per page (default: 25, max: 1000)"),
|
|
134
|
+
scrollId: z
|
|
135
|
+
.string()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Pagination token from previous search"),
|
|
138
|
+
format: z
|
|
139
|
+
.enum(["digest", "json"])
|
|
140
|
+
.optional()
|
|
141
|
+
.describe(
|
|
142
|
+
"Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
async (params) => {
|
|
146
|
+
const { requireApiKey } = await import("./config.js");
|
|
147
|
+
requireApiKey();
|
|
148
|
+
const { handleSearchTraces } = await import("./tools/search-traces.js");
|
|
149
|
+
return {
|
|
150
|
+
content: [{ type: "text", text: await handleSearchTraces(params) }],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
server.tool(
|
|
156
|
+
"get_trace",
|
|
157
|
+
"Get full details of a single trace by ID. Returns AI-readable trace digest by default. Use format: 'json' for full raw data including all spans.",
|
|
158
|
+
{
|
|
159
|
+
traceId: z.string().describe("The trace ID to retrieve"),
|
|
160
|
+
format: z
|
|
161
|
+
.enum(["digest", "json"])
|
|
162
|
+
.optional()
|
|
163
|
+
.describe(
|
|
164
|
+
"Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
|
|
165
|
+
),
|
|
166
|
+
},
|
|
167
|
+
async (params) => {
|
|
168
|
+
const { requireApiKey } = await import("./config.js");
|
|
169
|
+
requireApiKey();
|
|
170
|
+
const { handleGetTrace } = await import("./tools/get-trace.js");
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: "text", text: await handleGetTrace(params) }],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
server.tool(
|
|
178
|
+
"get_analytics",
|
|
179
|
+
'Query analytics timeseries from LangWatch. Metrics use "category.name" format (e.g., "performance.completion_time"). Use discover_schema to see available metrics.',
|
|
180
|
+
{
|
|
181
|
+
metric: z
|
|
182
|
+
.string()
|
|
183
|
+
.describe(
|
|
184
|
+
'Metric in "category.name" format, e.g., "metadata.trace_id", "performance.total_cost"'
|
|
185
|
+
),
|
|
186
|
+
aggregation: z
|
|
187
|
+
.string()
|
|
188
|
+
.optional()
|
|
189
|
+
.describe(
|
|
190
|
+
"Aggregation type: avg, sum, min, max, median, p90, p95, p99, cardinality, terms. Default: avg"
|
|
191
|
+
),
|
|
192
|
+
startDate: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe('Start date: ISO or relative ("7d", "30d"). Default: 7 days ago'),
|
|
196
|
+
endDate: z.string().optional().describe("End date. Default: now"),
|
|
197
|
+
timeZone: z.string().optional().describe("Timezone. Default: UTC"),
|
|
198
|
+
groupBy: z
|
|
199
|
+
.string()
|
|
200
|
+
.optional()
|
|
201
|
+
.describe(
|
|
202
|
+
"Group results by field. Use discover_schema for options."
|
|
203
|
+
),
|
|
204
|
+
filters: z
|
|
205
|
+
.record(z.string(), z.array(z.string()))
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Filters to apply"),
|
|
208
|
+
},
|
|
209
|
+
async (params) => {
|
|
210
|
+
const { requireApiKey } = await import("./config.js");
|
|
211
|
+
requireApiKey();
|
|
212
|
+
const { handleGetAnalytics } = await import("./tools/get-analytics.js");
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: await handleGetAnalytics(params) }],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
server.tool(
|
|
220
|
+
"list_prompts",
|
|
221
|
+
"List all prompts configured in the LangWatch project.",
|
|
222
|
+
{},
|
|
223
|
+
async () => {
|
|
224
|
+
const { requireApiKey } = await import("./config.js");
|
|
225
|
+
requireApiKey();
|
|
226
|
+
const { handleListPrompts } = await import("./tools/list-prompts.js");
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: await handleListPrompts() }],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
server.tool(
|
|
234
|
+
"get_prompt",
|
|
235
|
+
"Get a specific prompt by ID or handle, including messages, model config, and version history.",
|
|
236
|
+
{
|
|
237
|
+
idOrHandle: z.string().describe("Prompt ID or handle"),
|
|
238
|
+
version: z
|
|
239
|
+
.number()
|
|
240
|
+
.optional()
|
|
241
|
+
.describe("Specific version number (default: latest)"),
|
|
242
|
+
},
|
|
243
|
+
async (params) => {
|
|
244
|
+
const { requireApiKey } = await import("./config.js");
|
|
245
|
+
requireApiKey();
|
|
246
|
+
const { handleGetPrompt } = await import("./tools/get-prompt.js");
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: "text", text: await handleGetPrompt(params) }],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
server.tool(
|
|
254
|
+
"create_prompt",
|
|
255
|
+
"Create a new prompt in the LangWatch project.",
|
|
256
|
+
{
|
|
257
|
+
name: z.string().describe("Prompt name"),
|
|
258
|
+
handle: z
|
|
259
|
+
.string()
|
|
260
|
+
.optional()
|
|
261
|
+
.describe("URL-friendly handle (auto-generated if omitted)"),
|
|
262
|
+
messages: z
|
|
263
|
+
.array(
|
|
264
|
+
z.object({
|
|
265
|
+
role: z
|
|
266
|
+
.enum(["system", "user", "assistant"])
|
|
267
|
+
.describe("Message role"),
|
|
268
|
+
content: z.string().describe("Message content"),
|
|
269
|
+
})
|
|
270
|
+
)
|
|
271
|
+
.describe("Prompt messages"),
|
|
272
|
+
model: z
|
|
273
|
+
.string()
|
|
274
|
+
.describe('Model name, e.g., "gpt-4o", "claude-sonnet-4-5-20250929"'),
|
|
275
|
+
modelProvider: z
|
|
276
|
+
.string()
|
|
277
|
+
.describe('Provider name, e.g., "openai", "anthropic"'),
|
|
278
|
+
description: z.string().optional().describe("Prompt description"),
|
|
279
|
+
},
|
|
280
|
+
async (params) => {
|
|
281
|
+
const { requireApiKey } = await import("./config.js");
|
|
282
|
+
requireApiKey();
|
|
283
|
+
const { handleCreatePrompt } = await import("./tools/create-prompt.js");
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: await handleCreatePrompt(params) }],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
server.tool(
|
|
291
|
+
"update_prompt",
|
|
292
|
+
"Update an existing prompt or create a new version.",
|
|
293
|
+
{
|
|
294
|
+
idOrHandle: z.string().describe("Prompt ID or handle to update"),
|
|
295
|
+
messages: z
|
|
296
|
+
.array(
|
|
297
|
+
z.object({
|
|
298
|
+
role: z.enum(["system", "user", "assistant"]),
|
|
299
|
+
content: z.string(),
|
|
300
|
+
})
|
|
301
|
+
)
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Updated messages"),
|
|
304
|
+
model: z.string().optional().describe("Updated model name"),
|
|
305
|
+
modelProvider: z.string().optional().describe("Updated provider"),
|
|
306
|
+
commitMessage: z
|
|
307
|
+
.string()
|
|
308
|
+
.optional()
|
|
309
|
+
.describe("Commit message for the change"),
|
|
310
|
+
createVersion: z
|
|
311
|
+
.boolean()
|
|
312
|
+
.optional()
|
|
313
|
+
.describe(
|
|
314
|
+
"If true, creates a new version instead of updating in place"
|
|
315
|
+
),
|
|
316
|
+
},
|
|
317
|
+
async (params) => {
|
|
318
|
+
const { requireApiKey } = await import("./config.js");
|
|
319
|
+
requireApiKey();
|
|
320
|
+
const { handleUpdatePrompt } = await import("./tools/update-prompt.js");
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: await handleUpdatePrompt(params) }],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
73
327
|
await server.connect(transport);
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { getConfig, requireApiKey } from "./config.js";
|
|
2
|
+
|
|
3
|
+
// --- Response types ---
|
|
4
|
+
|
|
5
|
+
export interface TraceSearchResult {
|
|
6
|
+
trace_id: string;
|
|
7
|
+
formatted_trace?: string;
|
|
8
|
+
input?: { value: string };
|
|
9
|
+
output?: { value: string };
|
|
10
|
+
timestamps?: { started_at?: string | number };
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
error?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SearchTracesResponse {
|
|
16
|
+
traces: TraceSearchResult[];
|
|
17
|
+
pagination?: {
|
|
18
|
+
totalHits?: number;
|
|
19
|
+
scrollId?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TraceDetailResponse {
|
|
24
|
+
trace_id: string;
|
|
25
|
+
formatted_trace?: string;
|
|
26
|
+
input?: { value: string };
|
|
27
|
+
output?: { value: string };
|
|
28
|
+
timestamps?: {
|
|
29
|
+
started_at?: string | number;
|
|
30
|
+
updated_at?: string | number;
|
|
31
|
+
inserted_at?: string | number;
|
|
32
|
+
};
|
|
33
|
+
metadata?: {
|
|
34
|
+
user_id?: string;
|
|
35
|
+
thread_id?: string;
|
|
36
|
+
customer_id?: string;
|
|
37
|
+
labels?: string[];
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
error?: Record<string, unknown>;
|
|
41
|
+
ascii_tree?: string;
|
|
42
|
+
evaluations?: Array<{
|
|
43
|
+
evaluator_id?: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
score?: number;
|
|
46
|
+
passed?: boolean;
|
|
47
|
+
label?: string;
|
|
48
|
+
}>;
|
|
49
|
+
spans?: Array<{
|
|
50
|
+
span_id: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
type?: string;
|
|
53
|
+
model?: string;
|
|
54
|
+
input?: { value: string };
|
|
55
|
+
output?: { value: string };
|
|
56
|
+
timestamps?: { started_at?: number; finished_at?: number };
|
|
57
|
+
metrics?: {
|
|
58
|
+
completion_time_ms?: number;
|
|
59
|
+
prompt_tokens?: number;
|
|
60
|
+
completion_tokens?: number;
|
|
61
|
+
tokens_estimated?: boolean;
|
|
62
|
+
cost?: number;
|
|
63
|
+
};
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface AnalyticsBucket {
|
|
68
|
+
date: string;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AnalyticsTimeseriesResponse {
|
|
73
|
+
currentPeriod: AnalyticsBucket[];
|
|
74
|
+
previousPeriod: AnalyticsBucket[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PromptSummary {
|
|
78
|
+
id?: string;
|
|
79
|
+
handle?: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
description?: string | null;
|
|
82
|
+
latestVersionNumber?: number;
|
|
83
|
+
version?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PromptVersion {
|
|
87
|
+
version?: number;
|
|
88
|
+
commitMessage?: string;
|
|
89
|
+
model?: string;
|
|
90
|
+
modelProvider?: string;
|
|
91
|
+
messages?: Array<{ role: string; content: string }>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PromptDetailResponse extends PromptSummary {
|
|
95
|
+
versions?: PromptVersion[];
|
|
96
|
+
model?: string;
|
|
97
|
+
modelProvider?: string;
|
|
98
|
+
messages?: Array<{ role: string; content: string }>;
|
|
99
|
+
prompt?: Array<{ role: string; content: string }>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface PromptMutationResponse {
|
|
103
|
+
id?: string;
|
|
104
|
+
handle?: string;
|
|
105
|
+
name?: string;
|
|
106
|
+
latestVersionNumber?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- HTTP client ---
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sends an HTTP request to the LangWatch API.
|
|
113
|
+
*
|
|
114
|
+
* Builds the full URL from the configured endpoint, adds authentication,
|
|
115
|
+
* and handles JSON serialization/deserialization.
|
|
116
|
+
*
|
|
117
|
+
* @throws Error with status code and response body when the response is not OK
|
|
118
|
+
*/
|
|
119
|
+
async function makeRequest(
|
|
120
|
+
method: "GET" | "POST",
|
|
121
|
+
path: string,
|
|
122
|
+
body?: unknown
|
|
123
|
+
): Promise<unknown> {
|
|
124
|
+
const url = getConfig().endpoint + path;
|
|
125
|
+
const headers: Record<string, string> = {
|
|
126
|
+
"X-Auth-Token": requireApiKey(),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (method === "POST") {
|
|
130
|
+
headers["Content-Type"] = "application/json";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const response = await fetch(url, {
|
|
134
|
+
method,
|
|
135
|
+
headers,
|
|
136
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const responseBody = await response.text();
|
|
141
|
+
throw new Error(
|
|
142
|
+
`LangWatch API error ${response.status}: ${responseBody}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return response.json();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Searches traces with optional filters and pagination. */
|
|
150
|
+
export async function searchTraces(params: {
|
|
151
|
+
query?: string;
|
|
152
|
+
filters?: Record<string, string[]>;
|
|
153
|
+
startDate: number;
|
|
154
|
+
endDate: number;
|
|
155
|
+
pageSize?: number;
|
|
156
|
+
pageOffset?: number;
|
|
157
|
+
scrollId?: string;
|
|
158
|
+
format?: "digest" | "json";
|
|
159
|
+
}): Promise<SearchTracesResponse> {
|
|
160
|
+
const { format = "digest", ...rest } = params;
|
|
161
|
+
return makeRequest("POST", "/api/traces/search", {
|
|
162
|
+
...rest,
|
|
163
|
+
format,
|
|
164
|
+
}) as Promise<SearchTracesResponse>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Retrieves a single trace by its ID. */
|
|
168
|
+
export async function getTraceById(
|
|
169
|
+
traceId: string,
|
|
170
|
+
format: "digest" | "json" = "digest"
|
|
171
|
+
): Promise<TraceDetailResponse> {
|
|
172
|
+
return makeRequest(
|
|
173
|
+
"GET",
|
|
174
|
+
`/api/traces/${encodeURIComponent(traceId)}?format=${format}`
|
|
175
|
+
) as Promise<TraceDetailResponse>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Fetches analytics timeseries data for the given metrics and date range. */
|
|
179
|
+
export async function getAnalyticsTimeseries(params: {
|
|
180
|
+
series: Array<{
|
|
181
|
+
metric: string;
|
|
182
|
+
aggregation: string;
|
|
183
|
+
key?: string;
|
|
184
|
+
subkey?: string;
|
|
185
|
+
}>;
|
|
186
|
+
startDate: number;
|
|
187
|
+
endDate: number;
|
|
188
|
+
timeZone?: string;
|
|
189
|
+
groupBy?: string;
|
|
190
|
+
groupByKey?: string;
|
|
191
|
+
filters?: Record<string, string[]>;
|
|
192
|
+
}): Promise<AnalyticsTimeseriesResponse> {
|
|
193
|
+
return makeRequest(
|
|
194
|
+
"POST",
|
|
195
|
+
"/api/analytics/timeseries",
|
|
196
|
+
params
|
|
197
|
+
) as Promise<AnalyticsTimeseriesResponse>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Lists all prompts in the project. */
|
|
201
|
+
export async function listPrompts(): Promise<PromptSummary[]> {
|
|
202
|
+
return makeRequest("GET", "/api/prompts") as Promise<PromptSummary[]>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Retrieves a single prompt by ID or handle. */
|
|
206
|
+
export async function getPrompt(
|
|
207
|
+
idOrHandle: string,
|
|
208
|
+
version?: number
|
|
209
|
+
): Promise<PromptDetailResponse> {
|
|
210
|
+
const query = version != null ? `?version=${version}` : "";
|
|
211
|
+
return makeRequest(
|
|
212
|
+
"GET",
|
|
213
|
+
`/api/prompts/${encodeURIComponent(idOrHandle)}${query}`
|
|
214
|
+
) as Promise<PromptDetailResponse>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Creates a new prompt. */
|
|
218
|
+
export async function createPrompt(data: {
|
|
219
|
+
name: string;
|
|
220
|
+
handle?: string;
|
|
221
|
+
messages: Array<{ role: string; content: string }>;
|
|
222
|
+
model: string;
|
|
223
|
+
modelProvider: string;
|
|
224
|
+
description?: string;
|
|
225
|
+
}): Promise<PromptMutationResponse> {
|
|
226
|
+
return makeRequest(
|
|
227
|
+
"POST",
|
|
228
|
+
"/api/prompts",
|
|
229
|
+
data
|
|
230
|
+
) as Promise<PromptMutationResponse>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Updates an existing prompt by ID or handle. */
|
|
234
|
+
export async function updatePrompt(
|
|
235
|
+
idOrHandle: string,
|
|
236
|
+
data: {
|
|
237
|
+
messages?: Array<{ role: string; content: string }>;
|
|
238
|
+
model?: string;
|
|
239
|
+
modelProvider?: string;
|
|
240
|
+
commitMessage?: string;
|
|
241
|
+
}
|
|
242
|
+
): Promise<PromptMutationResponse> {
|
|
243
|
+
return makeRequest(
|
|
244
|
+
"POST",
|
|
245
|
+
`/api/prompts/${encodeURIComponent(idOrHandle)}`,
|
|
246
|
+
data
|
|
247
|
+
) as Promise<PromptMutationResponse>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Creates a new version of an existing prompt. */
|
|
251
|
+
export async function createPromptVersion(
|
|
252
|
+
idOrHandle: string,
|
|
253
|
+
data: {
|
|
254
|
+
messages?: Array<{ role: string; content: string }>;
|
|
255
|
+
model?: string;
|
|
256
|
+
modelProvider?: string;
|
|
257
|
+
commitMessage?: string;
|
|
258
|
+
}
|
|
259
|
+
): Promise<PromptMutationResponse> {
|
|
260
|
+
return makeRequest(
|
|
261
|
+
"POST",
|
|
262
|
+
`/api/prompts/${encodeURIComponent(idOrHandle)}/versions`,
|
|
263
|
+
data
|
|
264
|
+
) as Promise<PromptMutationResponse>;
|
|
265
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface GroupByInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const analyticsGroups: GroupByInfo[] = [
|
|
8
|
+
{
|
|
9
|
+
name: "topics.topics",
|
|
10
|
+
label: "Topic",
|
|
11
|
+
description: "Group by topic classification",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "metadata.user_id",
|
|
15
|
+
label: "User",
|
|
16
|
+
description: "Group by user ID",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "metadata.thread_id",
|
|
20
|
+
label: "Thread",
|
|
21
|
+
description: "Group by conversation thread",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "metadata.customer_id",
|
|
25
|
+
label: "Customer ID",
|
|
26
|
+
description: "Group by customer/organization",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "metadata.labels",
|
|
30
|
+
label: "Label",
|
|
31
|
+
description: "Group by custom labels",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "metadata.model",
|
|
35
|
+
label: "Model",
|
|
36
|
+
description: "Group by LLM model name",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "metadata.span_type",
|
|
40
|
+
label: "Span Type",
|
|
41
|
+
description: "Group by span type (llm, tool, agent, etc.)",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "sentiment.input_sentiment",
|
|
45
|
+
label: "Input Sentiment",
|
|
46
|
+
description: "Group by detected input sentiment (positive, negative, neutral)",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "sentiment.thumbs_up_down",
|
|
50
|
+
label: "Thumbs Up/Down",
|
|
51
|
+
description: "Group by user feedback (positive, negative, neutral)",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "events.event_type",
|
|
55
|
+
label: "Event Type",
|
|
56
|
+
description: "Group by event type",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "evaluations.evaluation_passed",
|
|
60
|
+
label: "Evaluation Passed",
|
|
61
|
+
description: "Group by evaluation pass/fail status",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "evaluations.evaluation_label",
|
|
65
|
+
label: "Evaluation Label",
|
|
66
|
+
description: "Group by evaluation label result",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "evaluations.evaluation_processing_state",
|
|
70
|
+
label: "Evaluation Processing State",
|
|
71
|
+
description: "Group by evaluation processing state",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "error.has_error",
|
|
75
|
+
label: "Contains Error",
|
|
76
|
+
description: "Group by whether the trace contains an error",
|
|
77
|
+
},
|
|
78
|
+
];
|