@respan/cli 0.9.0 → 0.10.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/dist/commands/datasets/create-span.js +5 -3
- package/dist/commands/datasets/create.js +5 -4
- package/dist/commands/datasets/get-span.js +5 -1
- package/dist/commands/datasets/get.js +1 -1
- package/dist/commands/datasets/list.js +5 -1
- package/dist/commands/datasets/spans.js +1 -1
- package/dist/commands/datasets/update.js +3 -6
- package/dist/commands/{datasets/add-spans.d.ts → eval/index.d.ts} +2 -3
- package/dist/commands/eval/index.js +26 -0
- package/dist/commands/evaluators/create.js +10 -7
- package/dist/commands/evaluators/get.js +1 -1
- package/dist/commands/evaluators/list.js +1 -1
- package/dist/commands/evaluators/run.js +8 -7
- package/dist/commands/evaluators/update.js +6 -7
- package/dist/commands/experiments/create.js +9 -8
- package/dist/commands/experiments/get.js +1 -1
- package/dist/commands/experiments/list.js +1 -1
- package/dist/commands/logs/create.js +9 -7
- package/dist/commands/logs/get.js +1 -1
- package/dist/commands/logs/list.js +4 -3
- package/dist/commands/logs/summary.js +3 -2
- package/dist/commands/prompts/create-version.js +9 -9
- package/dist/commands/prompts/create.js +5 -4
- package/dist/commands/prompts/get.js +1 -1
- package/dist/commands/prompts/list.js +1 -1
- package/dist/commands/prompts/update.js +3 -6
- package/dist/commands/prompts/versions.js +1 -1
- package/dist/commands/setup/index.js +32 -177
- package/dist/commands/traces/get.js +1 -1
- package/dist/commands/traces/list.js +8 -10
- package/dist/commands/traces/summary.js +6 -2
- package/dist/commands/users/get.js +1 -1
- package/dist/commands/users/list.js +5 -7
- package/dist/lib/base-command.d.ts +2 -0
- package/dist/lib/base-command.js +11 -1
- package/dist/lib/eval/runner.d.ts +15 -0
- package/dist/lib/eval/runner.js +292 -0
- package/dist/lib/eval/schema.d.ts +67 -0
- package/dist/lib/eval/schema.js +22 -0
- package/dist/lib/skill-refs.generated.d.ts +5 -0
- package/dist/lib/skill-refs.generated.js +1168 -0
- package/oclif.manifest.json +181 -189
- package/package.json +7 -3
- package/dist/commands/datasets/add-spans.js +0 -24
|
@@ -0,0 +1,1168 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/embed-skill-refs.mjs
|
|
2
|
+
// Source: skills/references/{tracing,prompts,evals,monitors}.md
|
|
3
|
+
// Do not edit directly — edit the markdown sources and rebuild.
|
|
4
|
+
export const TRACING_MD = `# Tracing Reference
|
|
5
|
+
|
|
6
|
+
Advanced tracing configuration: decorators, context propagation, processors, and span attributes.
|
|
7
|
+
|
|
8
|
+
Docs: \`https://respan.ai/docs/documentation/features/tracing/traces/tracing-sdk.md\`
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Decorators
|
|
13
|
+
|
|
14
|
+
Wrap functions to create structured span hierarchies. All decorators share the same signature.
|
|
15
|
+
|
|
16
|
+
### Types
|
|
17
|
+
|
|
18
|
+
| Decorator | Purpose |
|
|
19
|
+
|-----------|---------|
|
|
20
|
+
| \`@workflow\` / \`withWorkflow\` | Root span — top-level pipeline |
|
|
21
|
+
| \`@task\` / \`withTask\` | Step within a workflow |
|
|
22
|
+
| \`@agent\` / \`withAgent\` | Agent loop span |
|
|
23
|
+
| \`@tool\` / \`withTool\` | Tool/function call span |
|
|
24
|
+
|
|
25
|
+
### Python
|
|
26
|
+
|
|
27
|
+
\`\`\`python
|
|
28
|
+
from respan import Respan, workflow, task, agent, tool
|
|
29
|
+
|
|
30
|
+
Respan()
|
|
31
|
+
|
|
32
|
+
@workflow(name="write_article")
|
|
33
|
+
def write_article(topic: str):
|
|
34
|
+
outline = generate_outline(topic)
|
|
35
|
+
return write_draft(outline)
|
|
36
|
+
|
|
37
|
+
@task(name="generate_outline")
|
|
38
|
+
def generate_outline(topic: str):
|
|
39
|
+
return client.chat.completions.create(...)
|
|
40
|
+
|
|
41
|
+
@task(name="write_draft")
|
|
42
|
+
def write_draft(outline: str):
|
|
43
|
+
return client.chat.completions.create(...)
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
### TypeScript
|
|
47
|
+
|
|
48
|
+
\`\`\`typescript
|
|
49
|
+
import { Respan, withWorkflow, withTask } from "@respan/respan";
|
|
50
|
+
|
|
51
|
+
const respan = new Respan();
|
|
52
|
+
await respan.initialize();
|
|
53
|
+
|
|
54
|
+
const result = await withWorkflow({ name: "write_article" }, async () => {
|
|
55
|
+
const outline = await withTask({ name: "generate_outline" }, async () => {
|
|
56
|
+
return await client.chat.completions.create({...});
|
|
57
|
+
});
|
|
58
|
+
return await withTask({ name: "write_draft" }, async () => {
|
|
59
|
+
return await client.chat.completions.create({...});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
### Decorator Parameters
|
|
65
|
+
|
|
66
|
+
\`\`\`python
|
|
67
|
+
@workflow(
|
|
68
|
+
name="my_workflow", # Display name (default: function name)
|
|
69
|
+
version=1, # Version number
|
|
70
|
+
processors="my_processor", # Route to specific processor(s)
|
|
71
|
+
sample_rate=0.5, # 0.0-1.0, fraction of spans exported
|
|
72
|
+
export_filter={ # Conditional export
|
|
73
|
+
"metadata.env": {"operator": "", "value": "production"}
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
All parameters are optional. Without any, the function name is used as the span name.
|
|
79
|
+
|
|
80
|
+
### Async Support
|
|
81
|
+
|
|
82
|
+
Decorators work with \`async def\`, generators, and async generators automatically.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Context Propagation
|
|
87
|
+
|
|
88
|
+
Attach attributes to **all spans** within a scope, including auto-instrumented LLM calls.
|
|
89
|
+
|
|
90
|
+
### propagate_attributes
|
|
91
|
+
|
|
92
|
+
\`\`\`python
|
|
93
|
+
from respan import Respan, propagate_attributes
|
|
94
|
+
|
|
95
|
+
Respan()
|
|
96
|
+
|
|
97
|
+
with propagate_attributes(
|
|
98
|
+
customer_identifier="user_123",
|
|
99
|
+
thread_identifier="conv_abc",
|
|
100
|
+
environment="production",
|
|
101
|
+
metadata={"plan": "pro", "team": "search"},
|
|
102
|
+
):
|
|
103
|
+
# All spans here (including auto-instrumented OpenAI calls) get these attributes
|
|
104
|
+
result = run_pipeline()
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
#### Available attributes
|
|
108
|
+
|
|
109
|
+
| Attribute | Description |
|
|
110
|
+
|-----------|-------------|
|
|
111
|
+
| \`customer_identifier\` | User/customer ID — enables per-user analytics |
|
|
112
|
+
| \`customer_email\` | Customer email |
|
|
113
|
+
| \`customer_name\` | Customer display name |
|
|
114
|
+
| \`thread_identifier\` | Conversation thread ID — groups related calls |
|
|
115
|
+
| \`custom_identifier\` | Indexed custom identifier for fast queries |
|
|
116
|
+
| \`group_identifier\` | Group related traces together |
|
|
117
|
+
| \`environment\` | Environment name (e.g. \`"production"\`, \`"staging"\`) |
|
|
118
|
+
| \`metadata\` | Dict of custom key-value pairs (merged in nested contexts) |
|
|
119
|
+
| \`prompt\` | Dict with \`prompt_id\` and \`variables\` for prompt logging |
|
|
120
|
+
|
|
121
|
+
#### TypeScript
|
|
122
|
+
|
|
123
|
+
\`\`\`typescript
|
|
124
|
+
const result = await respan.propagateAttributes(
|
|
125
|
+
{
|
|
126
|
+
customerIdentifier: "user_123",
|
|
127
|
+
threadIdentifier: "conv_abc",
|
|
128
|
+
metadata: { plan: "pro" },
|
|
129
|
+
},
|
|
130
|
+
async () => {
|
|
131
|
+
return await runPipeline();
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
#### Nesting behavior
|
|
137
|
+
|
|
138
|
+
- Nested \`propagate_attributes\` calls merge with the outer context
|
|
139
|
+
- \`metadata\` dicts are merged (not replaced)
|
|
140
|
+
- For duplicate keys, inner values override outer
|
|
141
|
+
- Async-safe via \`contextvars\`
|
|
142
|
+
|
|
143
|
+
### respan_span_attributes
|
|
144
|
+
|
|
145
|
+
Attaches attributes to the **current active span only** (not auto-instrumented child spans):
|
|
146
|
+
|
|
147
|
+
\`\`\`python
|
|
148
|
+
from respan import respan_span_attributes
|
|
149
|
+
|
|
150
|
+
with respan_span_attributes({
|
|
151
|
+
"customer_identifier": "user-123",
|
|
152
|
+
"metadata": {"priority": "high"},
|
|
153
|
+
}):
|
|
154
|
+
pass
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
Use \`propagate_attributes\` when you need attributes on all nested spans (most common). Use \`respan_span_attributes\` when you only want to tag the current span.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Imperative Span Creation
|
|
162
|
+
|
|
163
|
+
For runtime-determined span names, use the client API instead of decorators:
|
|
164
|
+
|
|
165
|
+
\`\`\`python
|
|
166
|
+
from respan import get_client
|
|
167
|
+
|
|
168
|
+
client = get_client()
|
|
169
|
+
|
|
170
|
+
with client.start_span(name="dynamic_step", kind="task") as span:
|
|
171
|
+
result = do_work()
|
|
172
|
+
# span is automatically closed and exported
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Updating the Current Span
|
|
178
|
+
|
|
179
|
+
Add attributes or change status on the active span at runtime:
|
|
180
|
+
|
|
181
|
+
\`\`\`python
|
|
182
|
+
from respan import get_client
|
|
183
|
+
|
|
184
|
+
client = get_client()
|
|
185
|
+
|
|
186
|
+
# Inside a decorated function or start_span context:
|
|
187
|
+
client.update_current_span(
|
|
188
|
+
respan_params={
|
|
189
|
+
"customer_identifier": "user_123",
|
|
190
|
+
"metadata": {"step": "final"},
|
|
191
|
+
},
|
|
192
|
+
name="renamed_span",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Add events
|
|
196
|
+
client.add_event("checkpoint_reached", attributes={"items": 42})
|
|
197
|
+
|
|
198
|
+
# Record exceptions (span continues)
|
|
199
|
+
try:
|
|
200
|
+
risky_operation()
|
|
201
|
+
except Exception as e:
|
|
202
|
+
client.record_exception(e)
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Custom Processors
|
|
208
|
+
|
|
209
|
+
Route spans to multiple destinations with filtering:
|
|
210
|
+
|
|
211
|
+
\`\`\`python
|
|
212
|
+
from respan_tracing import RespanTelemetry
|
|
213
|
+
|
|
214
|
+
telemetry = RespanTelemetry(api_key="...", base_url="...")
|
|
215
|
+
|
|
216
|
+
# Add a second exporter for debug logging
|
|
217
|
+
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
|
|
218
|
+
|
|
219
|
+
telemetry.add_processor(
|
|
220
|
+
exporter=ConsoleSpanExporter(),
|
|
221
|
+
name="debug",
|
|
222
|
+
filter_fn=lambda span: span.attributes.get("environment") == "debug",
|
|
223
|
+
)
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
Route specific spans to a processor using the \`processors\` parameter on decorators:
|
|
227
|
+
|
|
228
|
+
\`\`\`python
|
|
229
|
+
@task(name="sensitive_step", processors="debug")
|
|
230
|
+
def sensitive_step():
|
|
231
|
+
...
|
|
232
|
+
\`\`\`
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Flush
|
|
237
|
+
|
|
238
|
+
Always call \`flush()\` before process exit to ensure all spans are exported:
|
|
239
|
+
|
|
240
|
+
\`\`\`python
|
|
241
|
+
respan = Respan()
|
|
242
|
+
# ... your code ...
|
|
243
|
+
respan.flush()
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
In serverless/Lambda: call \`flush()\` at the end of every handler invocation.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Trace Hierarchy Example
|
|
251
|
+
|
|
252
|
+
\`\`\`
|
|
253
|
+
workflow: handle_request
|
|
254
|
+
+-- task: classify_intent
|
|
255
|
+
| +-- llm: openai.chat (auto-captured)
|
|
256
|
+
+-- agent: support_agent
|
|
257
|
+
| +-- tool: lookup_order
|
|
258
|
+
| +-- llm: openai.chat (auto-captured)
|
|
259
|
+
| +-- tool: process_refund
|
|
260
|
+
+-- task: generate_response
|
|
261
|
+
+-- llm: openai.chat (auto-captured)
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
LLM calls within decorated functions are automatically nested as child spans.
|
|
265
|
+
`;
|
|
266
|
+
export const GATEWAY_MD = `# Gateway Reference
|
|
267
|
+
|
|
268
|
+
Route LLM calls through the Respan proxy for logging, caching, key management, fallbacks, and model switching.
|
|
269
|
+
|
|
270
|
+
Docs: \`https://respan.ai/docs/documentation/features/gateway/gateway-quickstart.md\`
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Setup
|
|
275
|
+
|
|
276
|
+
Point the LLM client's \`base_url\` at the Respan gateway. The Respan API key authenticates both the proxy and tracing.
|
|
277
|
+
|
|
278
|
+
### Python (OpenAI SDK)
|
|
279
|
+
|
|
280
|
+
\`\`\`python
|
|
281
|
+
import os
|
|
282
|
+
from openai import OpenAI
|
|
283
|
+
|
|
284
|
+
client = OpenAI(
|
|
285
|
+
api_key=os.environ["RESPAN_API_KEY"],
|
|
286
|
+
base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
response = client.chat.completions.create(
|
|
290
|
+
model="gpt-4o-mini",
|
|
291
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
292
|
+
)
|
|
293
|
+
\`\`\`
|
|
294
|
+
|
|
295
|
+
### TypeScript (OpenAI SDK)
|
|
296
|
+
|
|
297
|
+
\`\`\`typescript
|
|
298
|
+
import OpenAI from "openai";
|
|
299
|
+
|
|
300
|
+
const client = new OpenAI({
|
|
301
|
+
apiKey: process.env.RESPAN_API_KEY,
|
|
302
|
+
baseURL: process.env.RESPAN_BASE_URL || "https://api.respan.ai/api",
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const response = await client.chat.completions.create({
|
|
306
|
+
model: "gpt-4o-mini",
|
|
307
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
308
|
+
});
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
### Python (Anthropic SDK)
|
|
312
|
+
|
|
313
|
+
\`\`\`python
|
|
314
|
+
import os
|
|
315
|
+
import anthropic
|
|
316
|
+
|
|
317
|
+
client = anthropic.Anthropic(
|
|
318
|
+
api_key=os.environ["RESPAN_API_KEY"],
|
|
319
|
+
base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
|
|
320
|
+
)
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
323
|
+
### Environment variables
|
|
324
|
+
|
|
325
|
+
Set once, works everywhere:
|
|
326
|
+
|
|
327
|
+
\`\`\`bash
|
|
328
|
+
export RESPAN_API_KEY="your-key"
|
|
329
|
+
export OPENAI_API_KEY="$RESPAN_API_KEY"
|
|
330
|
+
export OPENAI_BASE_URL="https://api.respan.ai/api"
|
|
331
|
+
\`\`\`
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Gateway + Tracing (Both)
|
|
336
|
+
|
|
337
|
+
Combine SDK tracing and gateway routing:
|
|
338
|
+
|
|
339
|
+
### Python
|
|
340
|
+
|
|
341
|
+
\`\`\`python
|
|
342
|
+
import os
|
|
343
|
+
from openai import OpenAI
|
|
344
|
+
from respan import Respan
|
|
345
|
+
|
|
346
|
+
respan_api_key = os.environ["RESPAN_API_KEY"]
|
|
347
|
+
respan_base_url = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
|
|
348
|
+
|
|
349
|
+
respan = Respan(api_key=respan_api_key, base_url=respan_base_url)
|
|
350
|
+
|
|
351
|
+
client = OpenAI(
|
|
352
|
+
api_key=respan_api_key,
|
|
353
|
+
base_url=respan_base_url,
|
|
354
|
+
)
|
|
355
|
+
\`\`\`
|
|
356
|
+
|
|
357
|
+
### TypeScript
|
|
358
|
+
|
|
359
|
+
\`\`\`typescript
|
|
360
|
+
import OpenAI from "openai";
|
|
361
|
+
import { Respan } from "@respan/respan";
|
|
362
|
+
|
|
363
|
+
const respanApiKey = process.env.RESPAN_API_KEY!;
|
|
364
|
+
const respanBaseUrl = process.env.RESPAN_BASE_URL || "https://api.respan.ai/api";
|
|
365
|
+
|
|
366
|
+
const respan = new Respan({ apiKey: respanApiKey, baseUrl: respanBaseUrl });
|
|
367
|
+
await respan.initialize();
|
|
368
|
+
|
|
369
|
+
const client = new OpenAI({
|
|
370
|
+
apiKey: respanApiKey,
|
|
371
|
+
baseURL: respanBaseUrl,
|
|
372
|
+
});
|
|
373
|
+
\`\`\`
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Framework-Specific Gateway Setup
|
|
378
|
+
|
|
379
|
+
### CrewAI
|
|
380
|
+
|
|
381
|
+
\`\`\`python
|
|
382
|
+
import os
|
|
383
|
+
os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
|
|
384
|
+
os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
|
|
385
|
+
|
|
386
|
+
from crewai import Agent, Task, Crew
|
|
387
|
+
# CrewAI uses the env vars automatically
|
|
388
|
+
\`\`\`
|
|
389
|
+
|
|
390
|
+
### Haystack
|
|
391
|
+
|
|
392
|
+
\`\`\`python
|
|
393
|
+
import os
|
|
394
|
+
os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
|
|
395
|
+
os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
|
|
396
|
+
|
|
397
|
+
from haystack.components.generators import OpenAIGenerator
|
|
398
|
+
# OpenAIGenerator picks up env vars automatically
|
|
399
|
+
\`\`\`
|
|
400
|
+
|
|
401
|
+
### OpenAI Agents SDK
|
|
402
|
+
|
|
403
|
+
\`\`\`python
|
|
404
|
+
import os
|
|
405
|
+
os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
|
|
406
|
+
os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
|
|
407
|
+
\`\`\`
|
|
408
|
+
|
|
409
|
+
### Pydantic AI
|
|
410
|
+
|
|
411
|
+
\`\`\`python
|
|
412
|
+
import os
|
|
413
|
+
os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
|
|
414
|
+
os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
|
|
415
|
+
|
|
416
|
+
from pydantic_ai import Agent
|
|
417
|
+
agent = Agent(model="openai:gpt-4o-mini")
|
|
418
|
+
\`\`\`
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Model Switching
|
|
423
|
+
|
|
424
|
+
The gateway supports 250+ models across providers. Use any model name:
|
|
425
|
+
|
|
426
|
+
\`\`\`python
|
|
427
|
+
# OpenAI models
|
|
428
|
+
response = client.chat.completions.create(model="gpt-4o", ...)
|
|
429
|
+
|
|
430
|
+
# Anthropic models (via OpenAI SDK!)
|
|
431
|
+
response = client.chat.completions.create(model="claude-sonnet-4-20250514", ...)
|
|
432
|
+
|
|
433
|
+
# Google models
|
|
434
|
+
response = client.chat.completions.create(model="gemini-2.0-flash", ...)
|
|
435
|
+
|
|
436
|
+
# Open-source models
|
|
437
|
+
response = client.chat.completions.create(model="llama-3.1-70b", ...)
|
|
438
|
+
\`\`\`
|
|
439
|
+
|
|
440
|
+
No code changes needed — just change the model string. View all models at \`https://platform.respan.ai/platform/models\`.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Advanced Features
|
|
445
|
+
|
|
446
|
+
### Fallback Models
|
|
447
|
+
|
|
448
|
+
Automatic failover if the primary model fails:
|
|
449
|
+
|
|
450
|
+
\`\`\`python
|
|
451
|
+
response = client.chat.completions.create(
|
|
452
|
+
model="gpt-4o",
|
|
453
|
+
messages=[...],
|
|
454
|
+
extra_body={
|
|
455
|
+
"fallback_models": ["claude-sonnet-4-20250514", "gemini-2.0-flash"],
|
|
456
|
+
},
|
|
457
|
+
)
|
|
458
|
+
\`\`\`
|
|
459
|
+
|
|
460
|
+
### Retries
|
|
461
|
+
|
|
462
|
+
\`\`\`python
|
|
463
|
+
response = client.chat.completions.create(
|
|
464
|
+
model="gpt-4o",
|
|
465
|
+
messages=[...],
|
|
466
|
+
extra_body={
|
|
467
|
+
"retries": 3,
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
### Caching
|
|
473
|
+
|
|
474
|
+
Enable response caching to save cost on repeated calls:
|
|
475
|
+
|
|
476
|
+
\`\`\`python
|
|
477
|
+
response = client.chat.completions.create(
|
|
478
|
+
model="gpt-4o",
|
|
479
|
+
messages=[...],
|
|
480
|
+
extra_body={
|
|
481
|
+
"cache_enabled": True,
|
|
482
|
+
"cache_ttl": 3600, # seconds
|
|
483
|
+
},
|
|
484
|
+
)
|
|
485
|
+
\`\`\`
|
|
486
|
+
|
|
487
|
+
### Streaming
|
|
488
|
+
|
|
489
|
+
Streaming works normally through the gateway:
|
|
490
|
+
|
|
491
|
+
\`\`\`python
|
|
492
|
+
stream = client.chat.completions.create(
|
|
493
|
+
model="gpt-4o-mini",
|
|
494
|
+
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
495
|
+
stream=True,
|
|
496
|
+
)
|
|
497
|
+
for chunk in stream:
|
|
498
|
+
print(chunk.choices[0].delta.content, end="")
|
|
499
|
+
\`\`\`
|
|
500
|
+
|
|
501
|
+
### Disable Logging
|
|
502
|
+
|
|
503
|
+
For sensitive data, disable logging on specific requests:
|
|
504
|
+
|
|
505
|
+
\`\`\`python
|
|
506
|
+
response = client.chat.completions.create(
|
|
507
|
+
model="gpt-4o",
|
|
508
|
+
messages=[...],
|
|
509
|
+
extra_body={
|
|
510
|
+
"disable_log": True,
|
|
511
|
+
},
|
|
512
|
+
)
|
|
513
|
+
\`\`\`
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Key Benefits
|
|
518
|
+
|
|
519
|
+
- **Unified key management** — one Respan API key for all providers
|
|
520
|
+
- **Automatic logging** — every request logged as a span
|
|
521
|
+
- **Cost tracking** — per-request cost tracking across providers
|
|
522
|
+
- **Caching** — cache repeated LLM calls to reduce cost and latency
|
|
523
|
+
- **Fallbacks** — automatic failover between providers
|
|
524
|
+
- **Model switching** — swap models without code changes
|
|
525
|
+
- **~50-150ms added latency** — minimal overhead
|
|
526
|
+
`;
|
|
527
|
+
export const PROMPTS_MD = `# Prompts Reference
|
|
528
|
+
|
|
529
|
+
Create, version, deploy, and use prompt templates from the Respan platform.
|
|
530
|
+
|
|
531
|
+
Docs: \`https://respan.ai/docs/documentation/features/prompt-management/prompt-management-quickstart.md\`
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## Overview
|
|
536
|
+
|
|
537
|
+
Prompts are version-controlled templates stored on the Respan platform. The workflow is:
|
|
538
|
+
|
|
539
|
+
1. **Create** a prompt with a name
|
|
540
|
+
2. **Add versions** with messages, model config, and \`{{variables}}\`
|
|
541
|
+
3. **Deploy** the version you want live
|
|
542
|
+
4. **Fetch** the prompt in code using the API or MCP tools
|
|
543
|
+
5. **Iterate** — commit new versions and deploy without code changes
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Create a Prompt
|
|
548
|
+
|
|
549
|
+
### Via MCP
|
|
550
|
+
|
|
551
|
+
\`\`\`
|
|
552
|
+
Use create_prompt to create a prompt:
|
|
553
|
+
name: "classify_intent"
|
|
554
|
+
description: "Classifies user messages into intent categories"
|
|
555
|
+
|
|
556
|
+
Then use create_prompt_version to add content:
|
|
557
|
+
prompt_id: "<id from above>"
|
|
558
|
+
model: "gpt-4o-mini"
|
|
559
|
+
messages:
|
|
560
|
+
- role: "system"
|
|
561
|
+
content: "You are an intent classifier. Classify the user message into one of: question, complaint, request, feedback."
|
|
562
|
+
- role: "user"
|
|
563
|
+
content: "{{user_message}}"
|
|
564
|
+
\`\`\`
|
|
565
|
+
|
|
566
|
+
### Via CLI
|
|
567
|
+
|
|
568
|
+
\`\`\`bash
|
|
569
|
+
respan prompts create --name "classify_intent"
|
|
570
|
+
respan prompts versions <prompt-id>
|
|
571
|
+
\`\`\`
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Variables
|
|
576
|
+
|
|
577
|
+
Use \`{{variable_name}}\` in message content. Variables are filled at runtime.
|
|
578
|
+
|
|
579
|
+
\`\`\`json
|
|
580
|
+
{
|
|
581
|
+
"messages": [
|
|
582
|
+
{"role": "system", "content": "You are a {{role}} assistant."},
|
|
583
|
+
{"role": "user", "content": "{{user_input}}"}
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
\`\`\`
|
|
587
|
+
|
|
588
|
+
### Jinja templates
|
|
589
|
+
|
|
590
|
+
Prompts support Jinja2 syntax:
|
|
591
|
+
|
|
592
|
+
- **Conditionals:** \`{% if context %}Use this context: {{context}}{% endif %}\`
|
|
593
|
+
- **JSON access:** \`{{ input.key }}\`
|
|
594
|
+
- **Filters:** \`{{ var | upper }}\`
|
|
595
|
+
- **Loops:** \`{% for item in items %}{{item}}{% endfor %}\`
|
|
596
|
+
- **Default values:** Set via the API or platform UI
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## Versioning and Deployment
|
|
601
|
+
|
|
602
|
+
Each prompt can have multiple versions. Only one version is **deployed** (live) at a time.
|
|
603
|
+
|
|
604
|
+
### Workflow
|
|
605
|
+
|
|
606
|
+
1. \`create_prompt_version\` — creates a new version (always starts as NOT deployed)
|
|
607
|
+
2. Test the version in the playground or via API
|
|
608
|
+
3. Deploy via platform UI when ready
|
|
609
|
+
4. Code using \`prompt_id\` automatically gets the deployed version
|
|
610
|
+
|
|
611
|
+
### Version pinning
|
|
612
|
+
|
|
613
|
+
\`\`\`python
|
|
614
|
+
# Always use the deployed version (default)
|
|
615
|
+
prompt = get_prompt(prompt_id="abc123")
|
|
616
|
+
|
|
617
|
+
# Pin to a specific version
|
|
618
|
+
prompt = get_prompt(prompt_id="abc123", version=3)
|
|
619
|
+
\`\`\`
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Using Prompts in Code
|
|
624
|
+
|
|
625
|
+
### Python (via Gateway)
|
|
626
|
+
|
|
627
|
+
Fetch the prompt config and pass to the LLM:
|
|
628
|
+
|
|
629
|
+
\`\`\`python
|
|
630
|
+
import os
|
|
631
|
+
from openai import OpenAI
|
|
632
|
+
|
|
633
|
+
client = OpenAI(
|
|
634
|
+
api_key=os.environ["RESPAN_API_KEY"],
|
|
635
|
+
base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# The gateway resolves the prompt template and fills variables
|
|
639
|
+
response = client.chat.completions.create(
|
|
640
|
+
model="gpt-4o-mini",
|
|
641
|
+
messages=[], # Messages come from the prompt template
|
|
642
|
+
extra_body={
|
|
643
|
+
"prompt_id": "your-prompt-id",
|
|
644
|
+
"prompt_variables": {
|
|
645
|
+
"user_message": "I want to return my order",
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
)
|
|
649
|
+
\`\`\`
|
|
650
|
+
|
|
651
|
+
### Linking traces to prompts
|
|
652
|
+
|
|
653
|
+
Use \`propagate_attributes\` to tag traces with the prompt used:
|
|
654
|
+
|
|
655
|
+
\`\`\`python
|
|
656
|
+
from respan import propagate_attributes
|
|
657
|
+
|
|
658
|
+
with propagate_attributes(
|
|
659
|
+
prompt={"prompt_id": "abc123", "variables": {"user_message": "Hello"}},
|
|
660
|
+
):
|
|
661
|
+
response = client.chat.completions.create(...)
|
|
662
|
+
\`\`\`
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Prompt Composition
|
|
667
|
+
|
|
668
|
+
Variables can reference other prompts (child prompts rendered first, injected as text).
|
|
669
|
+
|
|
670
|
+
Configure in the platform UI by changing a variable's type from **Text** to **Prompt**.
|
|
671
|
+
|
|
672
|
+
Max nesting depth: 2 (parent -> child).
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
## Structured Output (JSON Schema)
|
|
677
|
+
|
|
678
|
+
Attach a JSON schema to a prompt version for structured responses:
|
|
679
|
+
|
|
680
|
+
\`\`\`json
|
|
681
|
+
{
|
|
682
|
+
"response_format": {
|
|
683
|
+
"type": "json_schema",
|
|
684
|
+
"json_schema": {
|
|
685
|
+
"name": "extraction",
|
|
686
|
+
"strict": true,
|
|
687
|
+
"schema": {
|
|
688
|
+
"type": "object",
|
|
689
|
+
"properties": {
|
|
690
|
+
"intent": {"type": "string"},
|
|
691
|
+
"confidence": {"type": "number"}
|
|
692
|
+
},
|
|
693
|
+
"required": ["intent", "confidence"],
|
|
694
|
+
"additionalProperties": false
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
\`\`\`
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## MCP Tools Reference
|
|
704
|
+
|
|
705
|
+
| Action | Tool |
|
|
706
|
+
|--------|------|
|
|
707
|
+
| List all prompts | \`list_prompts\` |
|
|
708
|
+
| Get prompt details | \`get_prompt_detail(prompt_id)\` |
|
|
709
|
+
| Create a prompt | \`create_prompt(name, description)\` |
|
|
710
|
+
| Update name/description | \`update_prompt(prompt_id, name, description)\` |
|
|
711
|
+
| Create a version | \`create_prompt_version(prompt_id, messages, model, ...)\` |
|
|
712
|
+
| Update a version | \`update_prompt_version(prompt_id, version, ...)\` |
|
|
713
|
+
| List versions | \`list_prompt_versions(prompt_id)\` |
|
|
714
|
+
| Get version detail | \`get_prompt_version_detail(prompt_id, version)\` |
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## CLI Commands
|
|
719
|
+
|
|
720
|
+
\`\`\`bash
|
|
721
|
+
respan prompts list # List all prompts
|
|
722
|
+
respan prompts get <id> # Get prompt details
|
|
723
|
+
respan prompts create --name <name> # Create a prompt
|
|
724
|
+
respan prompts update <id> # Update a prompt
|
|
725
|
+
respan prompts versions <id> # List prompt versions
|
|
726
|
+
\`\`\`
|
|
727
|
+
`;
|
|
728
|
+
export const EVALS_MD = `# Evals Reference
|
|
729
|
+
|
|
730
|
+
Build evaluation pipelines: datasets, evaluators, and experiments.
|
|
731
|
+
|
|
732
|
+
Docs: \`https://respan.ai/docs/documentation/features/evals/concepts.md\`
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## Overview
|
|
737
|
+
|
|
738
|
+
Two evaluation modes:
|
|
739
|
+
|
|
740
|
+
- **Offline** — build datasets, run experiments across prompt/model variants, compare scores
|
|
741
|
+
- **Online** — automatically score live production traffic (see [monitors.md](monitors.md))
|
|
742
|
+
|
|
743
|
+
Offline eval workflow:
|
|
744
|
+
|
|
745
|
+
\`\`\`
|
|
746
|
+
Create dataset -> Create evaluator -> Create experiment -> Run & compare
|
|
747
|
+
\`\`\`
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Datasets
|
|
752
|
+
|
|
753
|
+
A dataset is a collection of test rows (input/output pairs) for experiments.
|
|
754
|
+
|
|
755
|
+
### Create from scratch
|
|
756
|
+
|
|
757
|
+
\`\`\`
|
|
758
|
+
Use create_dataset:
|
|
759
|
+
name: "intent_classification_testset"
|
|
760
|
+
description: "Test cases for intent classifier"
|
|
761
|
+
|
|
762
|
+
Then add rows with create_dataset_span:
|
|
763
|
+
dataset_id: "<id>"
|
|
764
|
+
input: '{"user_message": "I want a refund"}'
|
|
765
|
+
output: '{"intent": "refund_request"}'
|
|
766
|
+
\`\`\`
|
|
767
|
+
|
|
768
|
+
### Create from production traces
|
|
769
|
+
|
|
770
|
+
Sample real spans from traced data:
|
|
771
|
+
|
|
772
|
+
\`\`\`
|
|
773
|
+
1. Use list_traces or list_logs to find relevant spans
|
|
774
|
+
2. Use add_spans_to_dataset to add span IDs to a dataset:
|
|
775
|
+
dataset_id: "<id>"
|
|
776
|
+
span_ids: ["span_1", "span_2", "span_3"]
|
|
777
|
+
\`\`\`
|
|
778
|
+
|
|
779
|
+
Or use the platform UI: Datasets -> Insert by sampling (filter, set sampling %, preview, confirm).
|
|
780
|
+
|
|
781
|
+
### CSV import
|
|
782
|
+
|
|
783
|
+
Upload CSV via the platform UI. Columns map to prompt variables. Optional \`ideal_output\` column for expected outputs. Max 500 rows per import.
|
|
784
|
+
|
|
785
|
+
### MCP tools
|
|
786
|
+
|
|
787
|
+
| Action | Tool |
|
|
788
|
+
|--------|------|
|
|
789
|
+
| Create dataset | \`create_dataset(name, description)\` |
|
|
790
|
+
| List datasets | \`list_datasets\` |
|
|
791
|
+
| Get dataset | \`get_dataset(dataset_id)\` |
|
|
792
|
+
| Update dataset | \`update_dataset(dataset_id, name, description)\` |
|
|
793
|
+
| Add a row manually | \`create_dataset_span(dataset_id, input, output)\` |
|
|
794
|
+
| Add existing spans | \`add_spans_to_dataset(dataset_id, span_ids)\` |
|
|
795
|
+
| List rows | \`list_dataset_spans(dataset_id)\` |
|
|
796
|
+
| Get a row | \`retrieve_dataset_span(dataset_id, log_id)\` |
|
|
797
|
+
| Update a row | \`update_dataset_span(dataset_id, log_id, ...)\` |
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
## Evaluators
|
|
802
|
+
|
|
803
|
+
An evaluator scores LLM outputs. It is a workflow built from **graders** connected with conditions.
|
|
804
|
+
|
|
805
|
+
### Grader types
|
|
806
|
+
|
|
807
|
+
| Type | How it works |
|
|
808
|
+
|------|-------------|
|
|
809
|
+
| **LLM grader** | A language model judges the output. Uses variables: \`{{output}}\`, \`{{input}}\`, \`{{expected_output}}\`, \`{{metadata}}\`, \`{{metrics}}\` |
|
|
810
|
+
| **Code grader** | A Python function returns a score. Signature: \`main(eval_inputs)\` where \`eval_inputs["output"]\` is required |
|
|
811
|
+
| **Human grader** | Team members review and score outputs manually |
|
|
812
|
+
|
|
813
|
+
### Score value types
|
|
814
|
+
|
|
815
|
+
| Type | Use case |
|
|
816
|
+
|------|----------|
|
|
817
|
+
| \`numerical\` | Rating scales (e.g. 0-5) |
|
|
818
|
+
| \`boolean\` | Pass/fail checks |
|
|
819
|
+
| \`categorical\` | Multi-choice classifications |
|
|
820
|
+
| \`comment\` | Qualitative text feedback |
|
|
821
|
+
|
|
822
|
+
### Create an evaluator
|
|
823
|
+
|
|
824
|
+
\`\`\`
|
|
825
|
+
Use create_evaluator:
|
|
826
|
+
name: "response_quality"
|
|
827
|
+
score_value_type: "numerical"
|
|
828
|
+
type: "llm"
|
|
829
|
+
description: "Scores response quality 1-5"
|
|
830
|
+
evaluator_slug: "response-quality"
|
|
831
|
+
\`\`\`
|
|
832
|
+
|
|
833
|
+
For complex evaluators with grader workflows, conditions, and compute blocks — use the platform UI (Evaluators -> Create).
|
|
834
|
+
|
|
835
|
+
### Evaluator workflow blocks
|
|
836
|
+
|
|
837
|
+
- **Markers:** \`Original input\`, \`Final result\` (entry/exit)
|
|
838
|
+
- **Graders:** LLM, Code, or Human scoring
|
|
839
|
+
- **Conditions:** \`If...Then...Else\` branching based on scores
|
|
840
|
+
- **Compute:** Average/weighted average of multiple grader scores
|
|
841
|
+
- **Metrics:** Built-in: completion tokens, cost, latency, model, prompt tokens, total tokens
|
|
842
|
+
- **Constants:** Fixed threshold values
|
|
843
|
+
|
|
844
|
+
### MCP tools
|
|
845
|
+
|
|
846
|
+
| Action | Tool |
|
|
847
|
+
|--------|------|
|
|
848
|
+
| List evaluators | \`list_evaluators\` |
|
|
849
|
+
| Get evaluator | \`get_evaluator(evaluator_id)\` |
|
|
850
|
+
| Create evaluator | \`create_evaluator(name, score_value_type, ...)\` |
|
|
851
|
+
| Update evaluator | \`update_evaluator(evaluator_id, ...)\` |
|
|
852
|
+
| Run evaluator | \`run_evaluator(evaluator_id, dataset_id or log_ids)\` |
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## Experiments
|
|
857
|
+
|
|
858
|
+
An experiment runs evaluators across a dataset to compare prompt/model configurations.
|
|
859
|
+
|
|
860
|
+
### Workflow
|
|
861
|
+
|
|
862
|
+
1. Create a dataset with test rows
|
|
863
|
+
2. Create evaluator(s) to score outputs
|
|
864
|
+
3. Create an experiment linking dataset + evaluators
|
|
865
|
+
4. Run the experiment — it generates outputs for each row and scores them
|
|
866
|
+
5. Compare results across configurations
|
|
867
|
+
|
|
868
|
+
### Create an experiment
|
|
869
|
+
|
|
870
|
+
\`\`\`
|
|
871
|
+
Use create_experiment:
|
|
872
|
+
name: "v2_vs_v3_comparison"
|
|
873
|
+
dataset_id: "<dataset-id>"
|
|
874
|
+
evaluator_slugs: ["response-quality", "hallucination-check"]
|
|
875
|
+
workflows:
|
|
876
|
+
- type: "prompt"
|
|
877
|
+
config: { prompt_id: "<prompt-v2-id>" }
|
|
878
|
+
- type: "prompt"
|
|
879
|
+
config: { prompt_id: "<prompt-v3-id>" }
|
|
880
|
+
\`\`\`
|
|
881
|
+
|
|
882
|
+
### Workflow types
|
|
883
|
+
|
|
884
|
+
| Type | Description |
|
|
885
|
+
|------|-------------|
|
|
886
|
+
| \`prompt\` | Run a saved prompt template against dataset rows |
|
|
887
|
+
| \`completion\` | Run a model completion with config |
|
|
888
|
+
| \`custom\` | User-defined logic |
|
|
889
|
+
|
|
890
|
+
### MCP tools
|
|
891
|
+
|
|
892
|
+
| Action | Tool |
|
|
893
|
+
|--------|------|
|
|
894
|
+
| List experiments | \`list_experiments\` |
|
|
895
|
+
| Get experiment | \`get_experiment(experiment_id)\` |
|
|
896
|
+
| Create experiment | \`create_experiment(name, dataset_id, ...)\` |
|
|
897
|
+
| List experiment spans | \`list_experiment_spans(experiment_id)\` |
|
|
898
|
+
| Get experiment span | \`get_experiment_span(experiment_id, log_id)\` |
|
|
899
|
+
| Update experiment span | \`update_experiment_span(experiment_id, log_id, ...)\` |
|
|
900
|
+
| Get summary stats | \`get_experiment_spans_summary(experiment_id, start_time, end_time)\` |
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
## End-to-End Example
|
|
905
|
+
|
|
906
|
+
\`\`\`
|
|
907
|
+
Step 1: Create dataset
|
|
908
|
+
create_dataset(name="qa_testset")
|
|
909
|
+
create_dataset_span(dataset_id="...", input="What is Python?", output="A programming language")
|
|
910
|
+
create_dataset_span(dataset_id="...", input="Who made Rust?", output="Mozilla Research")
|
|
911
|
+
|
|
912
|
+
Step 2: Create evaluator
|
|
913
|
+
create_evaluator(
|
|
914
|
+
name="accuracy_check",
|
|
915
|
+
score_value_type="boolean",
|
|
916
|
+
type="llm",
|
|
917
|
+
evaluator_slug="accuracy-check"
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
Step 3: Create experiment
|
|
921
|
+
create_experiment(
|
|
922
|
+
name="gpt4o_vs_claude",
|
|
923
|
+
dataset_id="...",
|
|
924
|
+
evaluator_slugs=["accuracy-check"]
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
Step 4: Review results
|
|
928
|
+
list_experiment_spans(experiment_id="...")
|
|
929
|
+
get_experiment_spans_summary(experiment_id="...", start_time="...", end_time="...")
|
|
930
|
+
\`\`\`
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
934
|
+
## CLI Commands
|
|
935
|
+
|
|
936
|
+
\`\`\`bash
|
|
937
|
+
respan datasets list # List datasets
|
|
938
|
+
respan datasets create --name <name> # Create a dataset
|
|
939
|
+
respan datasets get <id> # Get dataset details
|
|
940
|
+
respan datasets spans <id> # List dataset spans
|
|
941
|
+
respan evaluators list # List evaluators
|
|
942
|
+
respan evaluators get <id> # Get evaluator details
|
|
943
|
+
respan evaluators create # Create an evaluator
|
|
944
|
+
respan evaluators run <id> # Run an evaluator
|
|
945
|
+
respan experiments list # List experiments
|
|
946
|
+
respan experiments get <id> # Get experiment details
|
|
947
|
+
respan experiments create # Create an experiment
|
|
948
|
+
respan eval <file> # Run a one-shot eval defined in a JSON file
|
|
949
|
+
\`\`\`
|
|
950
|
+
|
|
951
|
+
## One-Shot Evals via JSON
|
|
952
|
+
|
|
953
|
+
\`respan eval <file.json>\` runs a complete eval pipeline from a single JSON spec —
|
|
954
|
+
creates the prompt, dataset, evaluators, and experiment in one command.
|
|
955
|
+
IDs are written back into the file so reruns reuse existing resources.
|
|
956
|
+
|
|
957
|
+
### Minimal example
|
|
958
|
+
|
|
959
|
+
\`\`\`json
|
|
960
|
+
{
|
|
961
|
+
"name": "Movie matcher",
|
|
962
|
+
"prompt": {
|
|
963
|
+
"name": "movie-matcher",
|
|
964
|
+
"model": "gpt-5-mini",
|
|
965
|
+
"messages": [
|
|
966
|
+
{ "role": "system", "content": "Identify the movie. Reply with only the title." },
|
|
967
|
+
{ "role": "user", "content": "{{description}}" }
|
|
968
|
+
]
|
|
969
|
+
},
|
|
970
|
+
"dataset": {
|
|
971
|
+
"name": "movie-descriptions",
|
|
972
|
+
"rows": [
|
|
973
|
+
{ "description": "A detective investigates seven deadly sins.", "expected_output": "Se7en" },
|
|
974
|
+
{ "description": "A hacker learns reality is a simulation.", "expected_output": "The Matrix" }
|
|
975
|
+
]
|
|
976
|
+
},
|
|
977
|
+
"experiment": {
|
|
978
|
+
"evaluators": [
|
|
979
|
+
{
|
|
980
|
+
"name": "Title match",
|
|
981
|
+
"type": "llm",
|
|
982
|
+
"score_value_type": "boolean",
|
|
983
|
+
"llm_config": {
|
|
984
|
+
"model": "openai/gpt-5-mini",
|
|
985
|
+
"evaluator_definition": "Expected: {{expected_output}}. Got: {{output}}. Correct? Reply true or false."
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
]
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
\`\`\`
|
|
992
|
+
|
|
993
|
+
Run:
|
|
994
|
+
|
|
995
|
+
\`\`\`bash
|
|
996
|
+
respan eval movie-matcher.eval.json
|
|
997
|
+
\`\`\`
|
|
998
|
+
|
|
999
|
+
The runner creates the prompt (and deploys version 1), creates the dataset (and bulk-loads rows),
|
|
1000
|
+
creates each evaluator + its workflow (and deploys), then creates the experiment linking them.
|
|
1001
|
+
Each step writes the resulting IDs back to the JSON file so subsequent runs are idempotent —
|
|
1002
|
+
re-running with the same file reuses the existing resources.
|
|
1003
|
+
`;
|
|
1004
|
+
export const MONITORS_MD = `# Monitors & Automation Reference
|
|
1005
|
+
|
|
1006
|
+
Set up monitoring alerts, online evaluations, and automated workflows.
|
|
1007
|
+
|
|
1008
|
+
Docs: \`https://respan.ai/docs/documentation/features/monitoring/monitors.md\`
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
## Monitors
|
|
1013
|
+
|
|
1014
|
+
A monitor watches a metric over time and sends a notification when a threshold is crossed.
|
|
1015
|
+
|
|
1016
|
+
### Examples
|
|
1017
|
+
|
|
1018
|
+
- Error rate > 5% over 10 minutes
|
|
1019
|
+
- Total cost > $1.00 over 5 minutes
|
|
1020
|
+
- Average latency > 3 seconds over 15 minutes
|
|
1021
|
+
- P95 latency > 5 seconds over 1 hour
|
|
1022
|
+
|
|
1023
|
+
### Setup
|
|
1024
|
+
|
|
1025
|
+
Monitors are configured in the platform UI: **Monitoring -> Monitors -> Create**.
|
|
1026
|
+
|
|
1027
|
+
A monitor has two parts:
|
|
1028
|
+
|
|
1029
|
+
#### 1. Trigger
|
|
1030
|
+
|
|
1031
|
+
Template: \`When [metric] of [source] is [threshold] over [time window]\`
|
|
1032
|
+
|
|
1033
|
+
- **Metric:** cost, latency, error rate, token usage, request count, etc.
|
|
1034
|
+
- **Source:** all requests, specific model, specific customer, etc.
|
|
1035
|
+
- **Threshold:** condition (greater than, less than, equals)
|
|
1036
|
+
- **Time window:** evaluation period (1 min, 5 min, 10 min, 1 hour, etc.)
|
|
1037
|
+
|
|
1038
|
+
Add **Where** conditions to narrow scope:
|
|
1039
|
+
- Filter by model, project, environment, customer, or custom metadata
|
|
1040
|
+
|
|
1041
|
+
#### 2. Notifications
|
|
1042
|
+
|
|
1043
|
+
Destinations:
|
|
1044
|
+
|
|
1045
|
+
| Destination | Details |
|
|
1046
|
+
|-------------|---------|
|
|
1047
|
+
| **Email** | Customizable subject and body |
|
|
1048
|
+
| **Slack** | Customizable message |
|
|
1049
|
+
| **Webhook** | Raw URL — receives JSON payload |
|
|
1050
|
+
|
|
1051
|
+
A single monitor can send to multiple destinations.
|
|
1052
|
+
|
|
1053
|
+
### Manage
|
|
1054
|
+
|
|
1055
|
+
- **Deploy** to make a monitor live
|
|
1056
|
+
- **Pause/Resume** deployed monitors
|
|
1057
|
+
- **Send test alert** to validate before deploying
|
|
1058
|
+
- **View versions** and restore earlier versions as drafts
|
|
1059
|
+
- **Create from chart** — select a metric on the dashboard, click to create a monitor with metric/source/interval prefilled
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
## Online Evaluations (Automation)
|
|
1064
|
+
|
|
1065
|
+
Automatically score production traffic in real time using deployed evaluators.
|
|
1066
|
+
|
|
1067
|
+
### Setup
|
|
1068
|
+
|
|
1069
|
+
Configure in the platform UI: **Evaluators -> Automations -> Create**.
|
|
1070
|
+
|
|
1071
|
+
1. **Create a condition** — filter rules for which logs to evaluate:
|
|
1072
|
+
- **Single log** — evaluate individual spans matching criteria
|
|
1073
|
+
- **Aggregation** — evaluate aggregated metrics over a window
|
|
1074
|
+
|
|
1075
|
+
2. **Create an automation** — connect:
|
|
1076
|
+
- Condition (which logs)
|
|
1077
|
+
- Evaluator (which scoring workflow)
|
|
1078
|
+
- Sampling rate (0.0-1.0, fraction of matching logs to evaluate)
|
|
1079
|
+
|
|
1080
|
+
3. **Check results** — view eval scores in the automation table
|
|
1081
|
+
|
|
1082
|
+
### Use case
|
|
1083
|
+
|
|
1084
|
+
> "Score 10% of production chat responses for hallucination using my LLM evaluator"
|
|
1085
|
+
|
|
1086
|
+
\`\`\`
|
|
1087
|
+
Condition: log_type = "chat" AND model = "gpt-4o"
|
|
1088
|
+
Evaluator: hallucination-check
|
|
1089
|
+
Sampling: 0.1
|
|
1090
|
+
\`\`\`
|
|
1091
|
+
|
|
1092
|
+
---
|
|
1093
|
+
|
|
1094
|
+
## Webhooks
|
|
1095
|
+
|
|
1096
|
+
Receive event notifications via HTTP webhooks.
|
|
1097
|
+
|
|
1098
|
+
### Setup
|
|
1099
|
+
|
|
1100
|
+
1. Go to platform: **Settings -> Webhooks -> Create**
|
|
1101
|
+
2. Enter your endpoint URL
|
|
1102
|
+
3. Select event types to subscribe to
|
|
1103
|
+
4. Optionally set a webhook secret for HMAC-SHA256 verification
|
|
1104
|
+
|
|
1105
|
+
### Verification
|
|
1106
|
+
|
|
1107
|
+
Respan signs webhook payloads with HMAC-SHA256. Verify the signature:
|
|
1108
|
+
|
|
1109
|
+
\`\`\`python
|
|
1110
|
+
import hashlib
|
|
1111
|
+
import hmac
|
|
1112
|
+
|
|
1113
|
+
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
|
|
1114
|
+
expected = hmac.new(
|
|
1115
|
+
secret.encode(),
|
|
1116
|
+
payload,
|
|
1117
|
+
hashlib.sha256,
|
|
1118
|
+
).hexdigest()
|
|
1119
|
+
return hmac.compare_digest(expected, signature)
|
|
1120
|
+
\`\`\`
|
|
1121
|
+
|
|
1122
|
+
The signature is sent in the request headers.
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
## Metrics Dashboard
|
|
1127
|
+
|
|
1128
|
+
Track key indicators in the platform: **Dashboard -> Metrics**.
|
|
1129
|
+
|
|
1130
|
+
### Available metrics
|
|
1131
|
+
|
|
1132
|
+
- **Requests:** total count, requests per model/customer/API key
|
|
1133
|
+
- **Tokens:** prompt tokens, completion tokens, total tokens
|
|
1134
|
+
- **Cost:** per-request, per-model, per-customer
|
|
1135
|
+
- **Latency:** average, P50, P95, P99
|
|
1136
|
+
- **Errors:** error count, error rate
|
|
1137
|
+
- **Time to first token (TTFT):** streaming latency
|
|
1138
|
+
|
|
1139
|
+
### Breakdowns
|
|
1140
|
+
|
|
1141
|
+
Slice metrics by:
|
|
1142
|
+
- Model
|
|
1143
|
+
- Customer (\`customer_identifier\`)
|
|
1144
|
+
- API key
|
|
1145
|
+
- Prompt template
|
|
1146
|
+
- Environment
|
|
1147
|
+
- Custom metadata keys
|
|
1148
|
+
|
|
1149
|
+
### Per-user analytics
|
|
1150
|
+
|
|
1151
|
+
Use \`customer_identifier\` in spans to enable:
|
|
1152
|
+
- Per-user cost tracking and budgets
|
|
1153
|
+
- Per-user rate limiting
|
|
1154
|
+
- User-level quality metrics
|
|
1155
|
+
|
|
1156
|
+
---
|
|
1157
|
+
|
|
1158
|
+
## CLI Commands
|
|
1159
|
+
|
|
1160
|
+
\`\`\`bash
|
|
1161
|
+
respan traces list --limit 10 # List recent traces
|
|
1162
|
+
respan traces get <trace-id> # Get trace details
|
|
1163
|
+
respan traces summary # Trace summary stats
|
|
1164
|
+
respan logs list --limit 10 # List recent logs
|
|
1165
|
+
respan logs get <log-id> # Get log details
|
|
1166
|
+
respan logs summary # Log summary stats
|
|
1167
|
+
\`\`\`
|
|
1168
|
+
`;
|