@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.
Files changed (44) hide show
  1. package/dist/commands/datasets/create-span.js +5 -3
  2. package/dist/commands/datasets/create.js +5 -4
  3. package/dist/commands/datasets/get-span.js +5 -1
  4. package/dist/commands/datasets/get.js +1 -1
  5. package/dist/commands/datasets/list.js +5 -1
  6. package/dist/commands/datasets/spans.js +1 -1
  7. package/dist/commands/datasets/update.js +3 -6
  8. package/dist/commands/{datasets/add-spans.d.ts → eval/index.d.ts} +2 -3
  9. package/dist/commands/eval/index.js +26 -0
  10. package/dist/commands/evaluators/create.js +10 -7
  11. package/dist/commands/evaluators/get.js +1 -1
  12. package/dist/commands/evaluators/list.js +1 -1
  13. package/dist/commands/evaluators/run.js +8 -7
  14. package/dist/commands/evaluators/update.js +6 -7
  15. package/dist/commands/experiments/create.js +9 -8
  16. package/dist/commands/experiments/get.js +1 -1
  17. package/dist/commands/experiments/list.js +1 -1
  18. package/dist/commands/logs/create.js +9 -7
  19. package/dist/commands/logs/get.js +1 -1
  20. package/dist/commands/logs/list.js +4 -3
  21. package/dist/commands/logs/summary.js +3 -2
  22. package/dist/commands/prompts/create-version.js +9 -9
  23. package/dist/commands/prompts/create.js +5 -4
  24. package/dist/commands/prompts/get.js +1 -1
  25. package/dist/commands/prompts/list.js +1 -1
  26. package/dist/commands/prompts/update.js +3 -6
  27. package/dist/commands/prompts/versions.js +1 -1
  28. package/dist/commands/setup/index.js +32 -177
  29. package/dist/commands/traces/get.js +1 -1
  30. package/dist/commands/traces/list.js +8 -10
  31. package/dist/commands/traces/summary.js +6 -2
  32. package/dist/commands/users/get.js +1 -1
  33. package/dist/commands/users/list.js +5 -7
  34. package/dist/lib/base-command.d.ts +2 -0
  35. package/dist/lib/base-command.js +11 -1
  36. package/dist/lib/eval/runner.d.ts +15 -0
  37. package/dist/lib/eval/runner.js +292 -0
  38. package/dist/lib/eval/schema.d.ts +67 -0
  39. package/dist/lib/eval/schema.js +22 -0
  40. package/dist/lib/skill-refs.generated.d.ts +5 -0
  41. package/dist/lib/skill-refs.generated.js +1168 -0
  42. package/oclif.manifest.json +181 -189
  43. package/package.json +7 -3
  44. 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
+ `;