@respan/cli 0.9.0 → 0.11.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 (62) 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/doctor.d.ts +0 -1
  29. package/dist/commands/setup/doctor.js +25 -40
  30. package/dist/commands/setup/gateway.d.ts +16 -0
  31. package/dist/commands/setup/gateway.js +33 -0
  32. package/dist/commands/setup/index.d.ts +4 -33
  33. package/dist/commands/setup/index.js +11 -907
  34. package/dist/commands/setup/tracing.d.ts +16 -0
  35. package/dist/commands/setup/tracing.js +33 -0
  36. package/dist/commands/traces/get.js +1 -1
  37. package/dist/commands/traces/list.js +8 -10
  38. package/dist/commands/traces/summary.js +6 -2
  39. package/dist/commands/users/get.js +1 -1
  40. package/dist/commands/users/list.js +5 -7
  41. package/dist/lib/agents.d.ts +16 -0
  42. package/dist/lib/agents.js +75 -0
  43. package/dist/lib/auth.js +24 -0
  44. package/dist/lib/base-command.d.ts +2 -0
  45. package/dist/lib/base-command.js +11 -1
  46. package/dist/lib/colors.d.ts +7 -0
  47. package/dist/lib/colors.js +10 -0
  48. package/dist/lib/eval/runner.d.ts +15 -0
  49. package/dist/lib/eval/runner.js +292 -0
  50. package/dist/lib/eval/schema.d.ts +67 -0
  51. package/dist/lib/eval/schema.js +22 -0
  52. package/dist/lib/integrate.d.ts +6 -0
  53. package/dist/lib/integrate.js +11 -0
  54. package/dist/lib/setup-base-command.d.ts +84 -0
  55. package/dist/lib/setup-base-command.js +649 -0
  56. package/dist/lib/skill-content.d.ts +1 -0
  57. package/dist/lib/skill-content.js +16 -0
  58. package/dist/lib/skill-refs.generated.d.ts +6 -0
  59. package/dist/lib/skill-refs.generated.js +1489 -0
  60. package/oclif.manifest.json +385 -230
  61. package/package.json +8 -3
  62. package/dist/commands/datasets/add-spans.js +0 -24
@@ -0,0 +1,1489 @@
1
+ // AUTO-GENERATED by scripts/embed-skill-refs.mjs
2
+ // Source: skills/SKILL.md and skills/references/{tracing,gateway,prompts,evals,monitors}.md
3
+ // Do not edit directly — edit the markdown sources and rebuild.
4
+ export const SKILL_MD = `# Respan
5
+
6
+ Use the Respan CLI and SDK for LLM observability — tracing, evals, prompts, datasets, and gateway routing.
7
+
8
+ ## When To Use
9
+
10
+ - **Set up tracing** (instrument your app to capture LLM calls — install + decorators, propagation, processors) → read [references/tracing.md](references/tracing.md)
11
+ - **Set up gateway** (route LLM calls through the Respan proxy — wiring + model switching, fallbacks, caching) → read [references/gateway.md](references/gateway.md)
12
+ - **Prompt management** (create, version, deploy) → read [references/prompts.md](references/prompts.md)
13
+ - **Evals** (datasets, evaluators, experiments) → read [references/evals.md](references/evals.md)
14
+ - **Monitors & automation** (alerts, online evals, webhooks) → read [references/monitors.md](references/monitors.md)
15
+
16
+ Tracing and gateway are separate setups — configure one at a time, never both in the same pass.
17
+
18
+ ## Core Principles
19
+
20
+ 1. **Read the reference first.** Each reference file has the exact setup steps, API patterns, MCP tools, and CLI commands.
21
+ 2. **Use MCP tools** for platform operations (prompts, datasets, evaluators, experiments, traces, logs).
22
+ 3. **Use CLI** when MCP is not available: \`respan traces list\`, \`respan prompts list\`, etc.
23
+ 4. **Fetch docs** for integration-specific details not covered in references.
24
+
25
+ ## Quick Reference
26
+
27
+ | Task | Reference / Command |
28
+ |------|--------------------|
29
+ | Set up tracing (steps + decorators, propagation) | [references/tracing.md](references/tracing.md) |
30
+ | Set up gateway (steps + features) | [references/gateway.md](references/gateway.md) |
31
+ | Prompt management | [references/prompts.md](references/prompts.md) |
32
+ | Evals & experiments | [references/evals.md](references/evals.md) |
33
+ | Monitors & automation | [references/monitors.md](references/monitors.md) |
34
+ | List traces | \`respan traces list --limit 10\` |
35
+ | View a trace | \`respan traces get <id>\` |
36
+ | Check auth | \`respan auth status\` |
37
+
38
+ ## Documentation Access
39
+
40
+ Any doc page can be fetched as markdown:
41
+ \`https://respan.ai/docs/integrations/openai-sdk.md\`
42
+ \`https://respan.ai/docs/sdks/typescript-sdk/overview.md\`
43
+
44
+ Full docs index: \`https://www.respan.ai/docs/llms.txt\`
45
+
46
+ Platform: \`https://platform.respan.ai\`
47
+ `;
48
+ export const TRACING_MD = `# Tracing
49
+
50
+ Set up and configure Respan SDK tracing: instrument an app to capture LLM calls as
51
+ structured traces, then refine with decorators, context propagation, and processors.
52
+
53
+ This file covers **both** the initial setup steps and the advanced reference. Tracing
54
+ is a separate setup from the gateway — do one at a time, never both in the same pass.
55
+
56
+ Docs: \`https://respan.ai/docs/documentation/features/tracing/traces/tracing-sdk.md\`
57
+ Full docs index: \`https://www.respan.ai/docs/llms.txt\`
58
+
59
+ ---
60
+
61
+ ## Setup
62
+
63
+ Use this section when the user asks to set up Respan tracing in their project.
64
+
65
+ ### Hard Rules
66
+
67
+ - **Interactive mode:** Ask the user questions when you need input. Do not assume.
68
+ - **Only add Respan code.** Do not refactor or modify unrelated code.
69
+ - **Pin exact versions.** Never use \`latest\` or unpinned ranges.
70
+ - **Do not guess APIs.** Use only the patterns from the integration docs linked below.
71
+ - **If Respan is already installed/configured, do not duplicate work.** Check for existing \`respan\` imports first.
72
+ - **Read the code before proposing changes.** Understand the actual workflow, not just the dependencies.
73
+
74
+ The API key is stored in \`.env\` as \`RESPAN_API_KEY\`.
75
+
76
+ ### 1. Analyze the Project
77
+
78
+ **1a. Detect language and package manager:**
79
+ - Check \`package.json\` (JS/TS) or \`pyproject.toml\` / \`requirements.txt\` (Python)
80
+ - Detect package manager from lock files
81
+
82
+ **1b. Detect libraries in priority order:**
83
+
84
+ Check higher-priority categories first. If a match is found, use that instrumentation — do NOT also add lower-level SDK instrumentation.
85
+
86
+ **Priority 1 — Agent Frameworks & High-Level SDKs:**
87
+
88
+ | Library | Python package | JS/TS package | Respan instrumentation (Python) | Respan instrumentation (JS/TS) | Docs |
89
+ |---------|---------------|---------------|--------------------------------|-------------------------------|------|
90
+ | Vercel AI SDK | — | \`ai\` | — | \`@respan/instrumentation-vercel\` | [docs](https://respan.ai/docs/integrations/vercel-ai-sdk.md) |
91
+ | OpenAI Agents SDK | \`openai-agents\` | \`@openai/agents\` | \`respan-instrumentation-openai-agents\` | \`@respan/instrumentation-openai-agents\` | [docs](https://respan.ai/docs/integrations/openai-agents-sdk.md) |
92
+ | Claude Agent SDK | \`claude-agent-sdk\` | — | \`respan-instrumentation-claude-agent-sdk\` | — | [docs](https://respan.ai/docs/integrations/claude-agents-sdk.md) |
93
+ | Pydantic AI | \`pydantic-ai\` | — | \`respan-instrumentation-pydantic-ai\` | — | [docs](https://respan.ai/docs/integrations/pydantic-ai.md) |
94
+ | LangChain | \`langchain\` | \`langchain\` | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langchain.md) |
95
+ | LangGraph | \`langgraph\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langgraph.md) |
96
+ | CrewAI | \`crewai\` | — | \`respan-instrumentation-crewai\` | — | [docs](https://respan.ai/docs/integrations/crewai.md) |
97
+ | LlamaIndex | \`llama-index\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/llama-index.md) |
98
+ | Haystack | \`haystack-ai\` | — | \`respan-instrumentation-haystack\` | — | [docs](https://respan.ai/docs/integrations/haystack.md) |
99
+ | Mastra | — | \`mastra\` | — | via OTEL | [docs](https://respan.ai/docs/integrations/mastra.md) |
100
+ | Google ADK | \`google-adk\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/google-adk.md) |
101
+
102
+ If a Priority 1 framework is found, use its instrumentation. Do NOT also add Priority 2 instrumentation for the same provider.
103
+
104
+ **Priority 2 — Direct LLM SDKs** (only if no P1 framework covers this provider):
105
+
106
+ These are **auto-instrumented** — just \`Respan()\` / \`new Respan()\`, no extra packages needed:
107
+
108
+ | Library | Python package | JS/TS package | Docs |
109
+ |---------|---------------|---------------|------|
110
+ | OpenAI SDK | \`openai\` | \`openai\` | [docs](https://respan.ai/docs/integrations/openai-sdk.md) |
111
+ | Anthropic SDK | \`anthropic\` | \`@anthropic-ai/sdk\` | [docs](https://respan.ai/docs/integrations/anthropic.md) |
112
+ | Azure OpenAI | \`openai\` (azure config) | \`openai\` | [docs](https://respan.ai/docs/integrations/providers/azure.md) |
113
+ | Google Vertex AI | \`google-cloud-aiplatform\` | — | [docs](https://respan.ai/docs/integrations/vertex-ai.md) |
114
+ | AWS Bedrock | \`boto3\` | — | [docs](https://respan.ai/docs/integrations/aws-bedrock.md) |
115
+ | Cohere | \`cohere\` | — | [docs](https://respan.ai/docs/integrations/providers/cohere.md) |
116
+ | Together AI | \`together\` | — | [docs](https://respan.ai/docs/integrations/together-ai.md) |
117
+
118
+ **Note:** LiteLLM in JS uses the OpenAI-compatible API, so the OpenAI auto-instrument covers it. For Python LiteLLM, see [LiteLLM guide](https://respan.ai/docs/integrations/litellm.md). For Google GenAI (\`@google/genai\`), see [Google GenAI guide](https://respan.ai/docs/integrations/google-genai.md).
119
+
120
+ **1c. Read the actual code and understand the workflow:**
121
+
122
+ This is the most important step. Read the entrypoint and all files that make LLM calls. Map out:
123
+
124
+ - What is the **overall workflow**? (e.g. "user sends question → retrieve context → generate answer → format response")
125
+ - What are the **individual steps/tasks**? (e.g. "embed query", "search DB", "call GPT", "parse output")
126
+ - Are there **agent loops**? (e.g. a loop that calls tools until done)
127
+ - Are there **tool calls**? (e.g. functions the LLM invokes)
128
+
129
+ ### 2. Propose an Implementation Plan
130
+
131
+ Present the user with a concrete plan before making any changes. The plan should include:
132
+
133
+ **a) Packages to install** — core SDK + instrumentation package (with exact versions)
134
+
135
+ **b) Initialization code** — where to add it (which file, which line)
136
+
137
+ **c) Workflow structure** — how to wrap the existing code:
138
+
139
+ For **agent frameworks** (Priority 1): The framework instrumentation auto-captures the workflow structure. Usually just need init code, no manual wrapping needed. Fetch and follow the integration doc.
140
+
141
+ For **direct LLM SDKs** (Priority 2): Individual LLM calls will be auto-traced as flat spans. Propose wrapping the logical workflow with Respan decorators/wrappers to get structured nested traces:
142
+
143
+ TypeScript example:
144
+ \`\`\`typescript
145
+ // Before: flat traces — each LLM call is an isolated span
146
+ const outline = await openai.chat.completions.create({...});
147
+ const draft = await openai.chat.completions.create({...});
148
+
149
+ // After: structured traces — nested spans showing the workflow
150
+ const result = await withWorkflow({ name: "write_article" }, async () => {
151
+ const outline = await withTask({ name: "generate_outline" }, async () => {
152
+ return await openai.chat.completions.create({...});
153
+ });
154
+ const draft = await withTask({ name: "write_draft" }, async () => {
155
+ return await openai.chat.completions.create({...});
156
+ });
157
+ return draft;
158
+ });
159
+ \`\`\`
160
+
161
+ Python example:
162
+ \`\`\`python
163
+ # Before: flat traces
164
+ outline = client.chat.completions.create(...)
165
+ draft = client.chat.completions.create(...)
166
+
167
+ # After: structured traces
168
+ @workflow(name="write_article")
169
+ def write_article(topic):
170
+ outline = generate_outline(topic)
171
+ return write_draft(outline)
172
+
173
+ @task(name="generate_outline")
174
+ def generate_outline(topic):
175
+ return client.chat.completions.create(...)
176
+
177
+ @task(name="write_draft")
178
+ def write_draft(outline):
179
+ return client.chat.completions.create(...)
180
+ \`\`\`
181
+
182
+ **Ask the user which approach they prefer:**
183
+ 1. **Auto-trace only** — just add init code, every LLM call is automatically captured as a flat span. Zero code changes beyond initialization. Good for quick setup or simple projects.
184
+ 2. **Structured traces** — wrap existing code with workflow/task decorators for nested spans showing how the app flows. Better for complex projects with multiple LLM calls.
185
+
186
+ If the user picks option 1, skip the wrappers entirely — just install + init code.
187
+
188
+ If the user picks option 2:
189
+ - **If multiple independent workflows are detected** (e.g. \`writeArticle()\`, \`summarizeDoc()\`, \`classifyEmail()\`), list them and ask which ones to instrument. Don't assume all of them.
190
+ - **Show the user what the trace will look like** — describe the span hierarchy:
191
+ \`\`\`
192
+ workflow: write_article
193
+ ├── task: generate_outline
194
+ │ └── llm: openai.chat (auto-captured)
195
+ └── task: write_draft
196
+ └── llm: openai.chat (auto-captured)
197
+ \`\`\`
198
+
199
+ Wait for user confirmation before proceeding.
200
+
201
+ ### 3. Implement
202
+
203
+ **a) Install packages:**
204
+
205
+ For direct LLM SDKs (Priority 2) — just the core SDK:
206
+ \`\`\`bash
207
+ # Python
208
+ pip install respan-ai
209
+
210
+ # TypeScript
211
+ npm install @respan/respan
212
+ \`\`\`
213
+
214
+ For agent frameworks (Priority 1) — also install the instrumentor. Check the docs link in the table above for the exact packages.
215
+
216
+ **b) Add initialization code** — at the top of the entrypoint, before any LLM client is created:
217
+
218
+ For **direct LLM SDKs** (auto-instrumented):
219
+ \`\`\`python
220
+ # Python
221
+ from respan import Respan
222
+ Respan()
223
+ \`\`\`
224
+ \`\`\`typescript
225
+ // TypeScript
226
+ import { Respan } from "@respan/respan";
227
+ const respan = new Respan();
228
+ await respan.initialize();
229
+ \`\`\`
230
+
231
+ For **agent frameworks** (explicit instrumentor — fetch the docs URL from the table for the exact pattern):
232
+ \`\`\`python
233
+ # Python example (OpenAI Agents)
234
+ from respan import Respan
235
+ from respan_instrumentation_openai_agents import OpenAIAgentsInstrumentor
236
+ Respan(instrumentations=[OpenAIAgentsInstrumentor()])
237
+ \`\`\`
238
+ \`\`\`typescript
239
+ // TypeScript example (OpenAI Agents)
240
+ import { Respan } from "@respan/respan";
241
+ import { OpenAIAgentsInstrumentor } from "@respan/instrumentation-openai-agents";
242
+ const respan = new Respan({ instrumentations: [new OpenAIAgentsInstrumentor()] });
243
+ await respan.initialize();
244
+ \`\`\`
245
+
246
+ **c) Add workflow wrappers** — if the user chose structured traces in the plan.
247
+
248
+ ### 4. Verify (final test — always run this)
249
+
250
+ As the final step, **run the user's program once with a small request** and confirm tracing works end to end:
251
+
252
+ - The app runs without errors
253
+ - A trace appears at https://platform.respan.ai or via \`respan traces list --limit 5\`
254
+ - If wrappers were added, the trace shows the expected nested span hierarchy
255
+
256
+ If no trace appears, the instrumentation isn't taking effect — debug (init placement, missing \`flush()\`, wrong entrypoint) and re-run before declaring setup done.
257
+
258
+ ---
259
+
260
+ ## Reference
261
+
262
+ Advanced tracing configuration: decorators, context propagation, processors, and span attributes.
263
+
264
+ ### Decorators
265
+
266
+ Wrap functions to create structured span hierarchies. All decorators share the same signature.
267
+
268
+ | Decorator | Purpose |
269
+ |-----------|---------|
270
+ | \`@workflow\` / \`withWorkflow\` | Root span — top-level pipeline |
271
+ | \`@task\` / \`withTask\` | Step within a workflow |
272
+ | \`@agent\` / \`withAgent\` | Agent loop span |
273
+ | \`@tool\` / \`withTool\` | Tool/function call span |
274
+
275
+ #### Python
276
+
277
+ \`\`\`python
278
+ from respan import Respan, workflow, task, agent, tool
279
+
280
+ Respan()
281
+
282
+ @workflow(name="write_article")
283
+ def write_article(topic: str):
284
+ outline = generate_outline(topic)
285
+ return write_draft(outline)
286
+
287
+ @task(name="generate_outline")
288
+ def generate_outline(topic: str):
289
+ return client.chat.completions.create(...)
290
+
291
+ @task(name="write_draft")
292
+ def write_draft(outline: str):
293
+ return client.chat.completions.create(...)
294
+ \`\`\`
295
+
296
+ #### TypeScript
297
+
298
+ \`\`\`typescript
299
+ import { Respan, withWorkflow, withTask } from "@respan/respan";
300
+
301
+ const respan = new Respan();
302
+ await respan.initialize();
303
+
304
+ const result = await withWorkflow({ name: "write_article" }, async () => {
305
+ const outline = await withTask({ name: "generate_outline" }, async () => {
306
+ return await client.chat.completions.create({...});
307
+ });
308
+ return await withTask({ name: "write_draft" }, async () => {
309
+ return await client.chat.completions.create({...});
310
+ });
311
+ });
312
+ \`\`\`
313
+
314
+ #### Decorator Parameters
315
+
316
+ \`\`\`python
317
+ @workflow(
318
+ name="my_workflow", # Display name (default: function name)
319
+ version=1, # Version number
320
+ processors="my_processor", # Route to specific processor(s)
321
+ sample_rate=0.5, # 0.0-1.0, fraction of spans exported
322
+ export_filter={ # Conditional export
323
+ "metadata.env": {"operator": "", "value": "production"}
324
+ },
325
+ )
326
+ \`\`\`
327
+
328
+ All parameters are optional. Without any, the function name is used as the span name.
329
+
330
+ #### Async Support
331
+
332
+ Decorators work with \`async def\`, generators, and async generators automatically.
333
+
334
+ ### Context Propagation
335
+
336
+ Attach attributes to **all spans** within a scope, including auto-instrumented LLM calls.
337
+
338
+ #### propagate_attributes
339
+
340
+ \`\`\`python
341
+ from respan import Respan, propagate_attributes
342
+
343
+ Respan()
344
+
345
+ with propagate_attributes(
346
+ customer_identifier="user_123",
347
+ thread_identifier="conv_abc",
348
+ environment="production",
349
+ metadata={"plan": "pro", "team": "search"},
350
+ ):
351
+ # All spans here (including auto-instrumented OpenAI calls) get these attributes
352
+ result = run_pipeline()
353
+ \`\`\`
354
+
355
+ ##### Available attributes
356
+
357
+ | Attribute | Description |
358
+ |-----------|-------------|
359
+ | \`customer_identifier\` | User/customer ID — enables per-user analytics |
360
+ | \`customer_email\` | Customer email |
361
+ | \`customer_name\` | Customer display name |
362
+ | \`thread_identifier\` | Conversation thread ID — groups related calls |
363
+ | \`custom_identifier\` | Indexed custom identifier for fast queries |
364
+ | \`group_identifier\` | Group related traces together |
365
+ | \`environment\` | Environment name (e.g. \`"production"\`, \`"staging"\`) |
366
+ | \`metadata\` | Dict of custom key-value pairs (merged in nested contexts) |
367
+ | \`prompt\` | Dict with \`prompt_id\` and \`variables\` for prompt logging |
368
+
369
+ ##### TypeScript
370
+
371
+ \`\`\`typescript
372
+ const result = await respan.propagateAttributes(
373
+ {
374
+ customerIdentifier: "user_123",
375
+ threadIdentifier: "conv_abc",
376
+ metadata: { plan: "pro" },
377
+ },
378
+ async () => {
379
+ return await runPipeline();
380
+ }
381
+ );
382
+ \`\`\`
383
+
384
+ ##### Nesting behavior
385
+
386
+ - Nested \`propagate_attributes\` calls merge with the outer context
387
+ - \`metadata\` dicts are merged (not replaced)
388
+ - For duplicate keys, inner values override outer
389
+ - Async-safe via \`contextvars\`
390
+
391
+ #### respan_span_attributes
392
+
393
+ Attaches attributes to the **current active span only** (not auto-instrumented child spans):
394
+
395
+ \`\`\`python
396
+ from respan import respan_span_attributes
397
+
398
+ with respan_span_attributes({
399
+ "customer_identifier": "user-123",
400
+ "metadata": {"priority": "high"},
401
+ }):
402
+ pass
403
+ \`\`\`
404
+
405
+ 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.
406
+
407
+ ### Imperative Span Creation
408
+
409
+ For runtime-determined span names, use the client API instead of decorators:
410
+
411
+ \`\`\`python
412
+ from respan import get_client
413
+
414
+ client = get_client()
415
+
416
+ with client.start_span(name="dynamic_step", kind="task") as span:
417
+ result = do_work()
418
+ # span is automatically closed and exported
419
+ \`\`\`
420
+
421
+ ### Updating the Current Span
422
+
423
+ Add attributes or change status on the active span at runtime:
424
+
425
+ \`\`\`python
426
+ from respan import get_client
427
+
428
+ client = get_client()
429
+
430
+ # Inside a decorated function or start_span context:
431
+ client.update_current_span(
432
+ respan_params={
433
+ "customer_identifier": "user_123",
434
+ "metadata": {"step": "final"},
435
+ },
436
+ name="renamed_span",
437
+ )
438
+
439
+ # Add events
440
+ client.add_event("checkpoint_reached", attributes={"items": 42})
441
+
442
+ # Record exceptions (span continues)
443
+ try:
444
+ risky_operation()
445
+ except Exception as e:
446
+ client.record_exception(e)
447
+ \`\`\`
448
+
449
+ ### Custom Processors
450
+
451
+ Route spans to multiple destinations with filtering:
452
+
453
+ \`\`\`python
454
+ from respan_tracing import RespanTelemetry
455
+
456
+ telemetry = RespanTelemetry(api_key="...", base_url="...")
457
+
458
+ # Add a second exporter for debug logging
459
+ from opentelemetry.sdk.trace.export import ConsoleSpanExporter
460
+
461
+ telemetry.add_processor(
462
+ exporter=ConsoleSpanExporter(),
463
+ name="debug",
464
+ filter_fn=lambda span: span.attributes.get("environment") == "debug",
465
+ )
466
+ \`\`\`
467
+
468
+ Route specific spans to a processor using the \`processors\` parameter on decorators:
469
+
470
+ \`\`\`python
471
+ @task(name="sensitive_step", processors="debug")
472
+ def sensitive_step():
473
+ ...
474
+ \`\`\`
475
+
476
+ ### Flush
477
+
478
+ Always call \`flush()\` before process exit to ensure all spans are exported:
479
+
480
+ \`\`\`python
481
+ respan = Respan()
482
+ # ... your code ...
483
+ respan.flush()
484
+ \`\`\`
485
+
486
+ In serverless/Lambda: call \`flush()\` at the end of every handler invocation.
487
+
488
+ ### Trace Hierarchy Example
489
+
490
+ \`\`\`
491
+ workflow: handle_request
492
+ +-- task: classify_intent
493
+ | +-- llm: openai.chat (auto-captured)
494
+ +-- agent: support_agent
495
+ | +-- tool: lookup_order
496
+ | +-- llm: openai.chat (auto-captured)
497
+ | +-- tool: process_refund
498
+ +-- task: generate_response
499
+ +-- llm: openai.chat (auto-captured)
500
+ \`\`\`
501
+
502
+ LLM calls within decorated functions are automatically nested as child spans.
503
+ `;
504
+ export const GATEWAY_MD = `# Gateway
505
+
506
+ Set up and use the Respan **gateway** — routing an app's LLM calls through the Respan
507
+ proxy for logging, caching, key management, fallbacks, and model switching.
508
+
509
+ This file covers **both** the setup steps and the feature reference. Gateway is a
510
+ separate setup from tracing — do one at a time, never both in the same pass. Gateway
511
+ setup does **not** install instrumentation packages; it repoints the LLM client's base
512
+ URL at the Respan proxy and applies the framework's specific wiring.
513
+
514
+ Docs: \`https://respan.ai/docs/documentation/features/gateway/gateway-quickstart.md\`
515
+ Full docs index: \`https://www.respan.ai/docs/llms.txt\`
516
+
517
+ ---
518
+
519
+ ## Setup
520
+
521
+ Use this section when the user asks to set up the Respan gateway.
522
+
523
+ ### Hard Rules
524
+
525
+ - **Interactive mode:** Ask the user questions when you need input. Do not assume.
526
+ - **Only add Respan wiring.** Do not refactor or modify unrelated code.
527
+ - **The gateway serves only the completion / text-generation operation**, exposed per provider:
528
+ - OpenAI-compatible → \`https://api.respan.ai/api/chat/completions\`
529
+ - Anthropic Messages → \`https://api.respan.ai/api/anthropic/\`
530
+ - Google Gemini → \`https://api.respan.ai/api/google/gemini/...\`
531
+
532
+ **Use the exact base path from the per-SDK gateway doc you fetch in Step 4** — it is authoritative. Do **NOT** repoint clients used for any other operation — embeddings, moderation, images, audio/speech, assistants, batch, fine-tuning — they are not supported and will fail (e.g. 404). Leave those untouched.
533
+ - **Use chat completions, not the Responses API.** The gateway serves chat completions (\`/api/chat/completions\`), not the OpenAI Responses API (\`/api/responses\`). SDKs that *default* to the Responses API are still fully supported — you just switch them to chat-completions mode, per their per-SDK doc:
534
+ - **OpenAI Agents SDK** — Python: \`set_default_openai_client(AsyncOpenAI(base_url=..., api_key=os.environ["RESPAN_API_KEY"]))\` + \`set_default_openai_api("chat_completions")\` (TS: \`setDefaultOpenAIClient(...)\` + \`setOpenAIAPI("chat_completions")\`).
535
+ - **Vercel AI SDK / \`@ai-sdk/openai\`** — use \`provider.chat(model)\`, not the default \`provider(model)\` (which targets the Responses API).
536
+ - **Scope the wiring — do not blanket-rewrite.** Wire only the client(s) that make the completion call you are setting up. Do **NOT** repoint every client instance in the repo (especially in example/monorepo projects with many independent scripts).
537
+ - **Do NOT install instrumentation packages.** Gateway routing does not use them. (That is tracing setup — see \`tracing.md\`.)
538
+ - **Do not guess APIs or doc slugs.** Use only the patterns from the live integration doc you fetch, and the exact slug from the map below.
539
+ - **Pin exact versions** if you install a framework SDK that is genuinely missing.
540
+ - **If the gateway is already wired up, do not duplicate work.** Check for an existing \`base_url\`/\`baseURL\` pointing at \`api.respan.ai\` first.
541
+ - **Read the code before proposing changes.** Find where the client is actually instantiated.
542
+
543
+ The API key is stored in \`.env\` as \`RESPAN_API_KEY\`. Gateway base URL: \`https://api.respan.ai/api\` (the proxy completions path is \`https://api.respan.ai/api/chat/completions\`).
544
+
545
+ ### 1. Detect
546
+
547
+ Identify:
548
+ - **Language & package manager** — \`package.json\` (JS/TS), \`pyproject.toml\` / \`requirements.txt\` (Python), \`Gemfile\` (Ruby). Detect the package manager from lock files.
549
+ - **LLM library / framework in use** — match the project's dependencies against the **Detection → slug map** below.
550
+
551
+ ### 2. Map to slug
552
+
553
+ Match the detected package(s) against the map. **Use the slug from the map verbatim — never auto-derive a slug from a display name** (the kebab-casing is irregular).
554
+
555
+ Apply the **priority rule:** a high-level framework wins over the raw SDK beneath it.
556
+ For example, a project with both \`crewai\` and \`openai\` maps to \`crew-ai\` (not \`open-ai-sdk\`),
557
+ because their gateway wiring differs. **If two frameworks match, ask the user which to configure.**
558
+
559
+ #### Detection → slug map
560
+
561
+ URL pattern: \`https://respan.ai/docs/integrations/gateway/<slug>.md\`
562
+
563
+ | Detected package(s) | Slug |
564
+ |---|---|
565
+ | \`@openai/agents\` / \`openai-agents\` | \`open-ai-agents\` |
566
+ | \`claude-agent-sdk\` | \`claude-agent-sdk\` |
567
+ | \`ai\` (Vercel AI SDK) | \`vercel-ai-sdk\` |
568
+ | \`pydantic-ai\` | \`pydantic-ai\` |
569
+ | \`crewai\` | \`crew-ai\` |
570
+ | \`haystack-ai\` | \`haystack\` |
571
+ | \`langchain\` | \`lang-chain\` |
572
+ | \`llama-index\` | \`llama-index\` |
573
+ | \`autogen\` / \`autogen-agentchat\` | \`auto-gen\` |
574
+ | \`dspy\` / \`dspy-ai\` | \`ds-py\` |
575
+ | \`google-adk\` | \`google-adk\` |
576
+ | \`openai\` | \`open-ai-sdk\` |
577
+ | \`anthropic\` / \`@anthropic-ai/sdk\` | \`anthropic-sdk\` |
578
+ | \`google-genai\` / \`@google/genai\` | \`google-gen-ai\` |
579
+ | \`litellm\` | \`lite-llm\` |
580
+ | \`ruby_llm\` (gem) | \`ruby-llm\` |
581
+ | \`google-cloud-aiplatform\` | \`vertex-ai\` |
582
+
583
+ > Namespace note: OpenAI, Anthropic, and Vertex each have both an SDK-level gateway page (\`open-ai-sdk\`, \`anthropic-sdk\`, \`vertex-ai\`) and a \`model-providers/*\` page. The SDK-level slugs above are the correct targets for this map.
584
+
585
+ ### 3. Confirm
586
+
587
+ Show the user what was detected and which doc will be used. Wait for confirmation.
588
+
589
+ > Detected:
590
+ > - Language: [TypeScript/Python/Ruby]
591
+ > - Framework / SDK: [e.g. OpenAI Agents]
592
+ > - Gateway doc: https://respan.ai/docs/integrations/gateway/open-ai-agents.md
593
+ >
594
+ > Set up the gateway for this? (yes/no)
595
+
596
+ ### 4. Fetch the live doc
597
+
598
+ Fetch \`https://respan.ai/docs/integrations/gateway/<slug>.md\` for the chosen slug.
599
+ Use the slug from the map verbatim.
600
+
601
+ If a pinned URL fails to resolve, that signals the **map is stale** and the table should be
602
+ updated — it does **not** mean you should guess an alternative slug.
603
+
604
+ ### 5. Read the project's client code
605
+
606
+ Find where the LLM client / \`base_url\` is instantiated, and identify **which client(s) make
607
+ completion / text-generation calls** (chat completions, Anthropic \`messages.create\`, Gemini
608
+ \`generate_content\`) — that is the only path you repoint. This is the step that determines the
609
+ correct wiring — do not skip it.
610
+
611
+ If the project has multiple clients or many scripts (e.g. an examples repo), repoint **only**
612
+ the completion ones. Leave clients that call embeddings, moderation, images, audio,
613
+ assistants, batch, or fine-tuning exactly as they are — the gateway does not serve them.
614
+
615
+ ### 6. Apply the framework-specific wiring
616
+
617
+ Apply the exact pattern from the fetched doc:
618
+
619
+ - Ensure \`RESPAN_API_KEY\` is set (it is in \`.env\`).
620
+ - Repoint \`base_url\` / \`baseURL\` to the gateway path from the fetched doc (e.g. \`https://api.respan.ai/api\` for OpenAI-compatible, \`…/api/anthropic/\` for Anthropic) — **only on the completion client(s)** identified in step 5, not on every client in the project.
621
+ - Apply the framework's exact wiring — e.g. OpenAI Agents requires \`set_default_openai_client(AsyncOpenAI(base_url=...))\`, **not** env vars.
622
+ - Install the framework SDK only if it is genuinely missing.
623
+ - **Do NOT install instrumentation packages.**
624
+
625
+ ### 7. Verify (final test — always run this)
626
+
627
+ As the final step, **run the user's program once with a small request** through the
628
+ configured gateway and confirm it works:
629
+
630
+ \`\`\`bash
631
+ respan logs list --limit 5
632
+ \`\`\`
633
+
634
+ Confirm:
635
+ - The app runs without errors
636
+ - The request appears in the gateway logs (via the CLI above, or the Respan platform at https://platform.respan.ai)
637
+
638
+ If no log entry appears, the request is not actually routing through the gateway — debug
639
+ the \`base_url\`/wiring and re-run before declaring setup done.
640
+
641
+ ---
642
+
643
+ ## Reference
644
+
645
+ ### Wiring Patterns
646
+
647
+ Point the LLM client's \`base_url\` at the Respan gateway. The Respan API key authenticates both the proxy and tracing.
648
+
649
+ #### Python (OpenAI SDK)
650
+
651
+ \`\`\`python
652
+ import os
653
+ from openai import OpenAI
654
+
655
+ client = OpenAI(
656
+ api_key=os.environ["RESPAN_API_KEY"],
657
+ base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
658
+ )
659
+
660
+ response = client.chat.completions.create(
661
+ model="gpt-4o-mini",
662
+ messages=[{"role": "user", "content": "Hello!"}],
663
+ )
664
+ \`\`\`
665
+
666
+ #### TypeScript (OpenAI SDK)
667
+
668
+ \`\`\`typescript
669
+ import OpenAI from "openai";
670
+
671
+ const client = new OpenAI({
672
+ apiKey: process.env.RESPAN_API_KEY,
673
+ baseURL: process.env.RESPAN_BASE_URL || "https://api.respan.ai/api",
674
+ });
675
+
676
+ const response = await client.chat.completions.create({
677
+ model: "gpt-4o-mini",
678
+ messages: [{ role: "user", content: "Hello!" }],
679
+ });
680
+ \`\`\`
681
+
682
+ #### Python (Anthropic SDK)
683
+
684
+ \`\`\`python
685
+ import os
686
+ import anthropic
687
+
688
+ client = anthropic.Anthropic(
689
+ api_key=os.environ["RESPAN_API_KEY"],
690
+ base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
691
+ )
692
+ \`\`\`
693
+
694
+ #### Environment variables
695
+
696
+ Set once, works everywhere:
697
+
698
+ \`\`\`bash
699
+ export RESPAN_API_KEY="your-key"
700
+ export OPENAI_API_KEY="$RESPAN_API_KEY"
701
+ export OPENAI_BASE_URL="https://api.respan.ai/api"
702
+ \`\`\`
703
+
704
+ ### Framework-Specific Gateway Setup
705
+
706
+ #### CrewAI
707
+
708
+ \`\`\`python
709
+ import os
710
+ os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
711
+ os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
712
+
713
+ from crewai import Agent, Task, Crew
714
+ # CrewAI uses the env vars automatically
715
+ \`\`\`
716
+
717
+ #### Haystack
718
+
719
+ \`\`\`python
720
+ import os
721
+ os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
722
+ os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
723
+
724
+ from haystack.components.generators import OpenAIGenerator
725
+ # OpenAIGenerator picks up env vars automatically
726
+ \`\`\`
727
+
728
+ #### OpenAI Agents SDK
729
+
730
+ \`\`\`python
731
+ import os
732
+ os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
733
+ os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
734
+ \`\`\`
735
+
736
+ #### Pydantic AI
737
+
738
+ \`\`\`python
739
+ import os
740
+ os.environ["OPENAI_API_KEY"] = os.environ["RESPAN_API_KEY"]
741
+ os.environ["OPENAI_BASE_URL"] = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
742
+
743
+ from pydantic_ai import Agent
744
+ agent = Agent(model="openai:gpt-4o-mini")
745
+ \`\`\`
746
+
747
+ ### Model Switching
748
+
749
+ The gateway supports 250+ models across providers. Use any model name:
750
+
751
+ \`\`\`python
752
+ # OpenAI models
753
+ response = client.chat.completions.create(model="gpt-4o", ...)
754
+
755
+ # Anthropic models (via OpenAI SDK!)
756
+ response = client.chat.completions.create(model="claude-sonnet-4-20250514", ...)
757
+
758
+ # Google models
759
+ response = client.chat.completions.create(model="gemini-2.0-flash", ...)
760
+
761
+ # Open-source models
762
+ response = client.chat.completions.create(model="llama-3.1-70b", ...)
763
+ \`\`\`
764
+
765
+ No code changes needed — just change the model string. View all models at \`https://platform.respan.ai/platform/models\`.
766
+
767
+ ### Advanced Features
768
+
769
+ #### Fallback Models
770
+
771
+ Automatic failover if the primary model fails:
772
+
773
+ \`\`\`python
774
+ response = client.chat.completions.create(
775
+ model="gpt-4o",
776
+ messages=[...],
777
+ extra_body={
778
+ "fallback_models": ["claude-sonnet-4-20250514", "gemini-2.0-flash"],
779
+ },
780
+ )
781
+ \`\`\`
782
+
783
+ #### Retries
784
+
785
+ \`\`\`python
786
+ response = client.chat.completions.create(
787
+ model="gpt-4o",
788
+ messages=[...],
789
+ extra_body={
790
+ "retries": 3,
791
+ },
792
+ )
793
+ \`\`\`
794
+
795
+ #### Caching
796
+
797
+ Enable response caching to save cost on repeated calls:
798
+
799
+ \`\`\`python
800
+ response = client.chat.completions.create(
801
+ model="gpt-4o",
802
+ messages=[...],
803
+ extra_body={
804
+ "cache_enabled": True,
805
+ "cache_ttl": 3600, # seconds
806
+ },
807
+ )
808
+ \`\`\`
809
+
810
+ #### Streaming
811
+
812
+ Streaming works normally through the gateway:
813
+
814
+ \`\`\`python
815
+ stream = client.chat.completions.create(
816
+ model="gpt-4o-mini",
817
+ messages=[{"role": "user", "content": "Tell me a story"}],
818
+ stream=True,
819
+ )
820
+ for chunk in stream:
821
+ print(chunk.choices[0].delta.content, end="")
822
+ \`\`\`
823
+
824
+ #### Disable Logging
825
+
826
+ For sensitive data, disable logging on specific requests:
827
+
828
+ \`\`\`python
829
+ response = client.chat.completions.create(
830
+ model="gpt-4o",
831
+ messages=[...],
832
+ extra_body={
833
+ "disable_log": True,
834
+ },
835
+ )
836
+ \`\`\`
837
+
838
+ ### Key Benefits
839
+
840
+ - **Unified key management** — one Respan API key for all providers
841
+ - **Automatic logging** — every request logged as a span
842
+ - **Cost tracking** — per-request cost tracking across providers
843
+ - **Caching** — cache repeated LLM calls to reduce cost and latency
844
+ - **Fallbacks** — automatic failover between providers
845
+ - **Model switching** — swap models without code changes
846
+ - **~50-150ms added latency** — minimal overhead
847
+ `;
848
+ export const PROMPTS_MD = `# Prompts Reference
849
+
850
+ Create, version, deploy, and use prompt templates from the Respan platform.
851
+
852
+ Docs: \`https://respan.ai/docs/documentation/features/prompt-management/prompt-management-quickstart.md\`
853
+
854
+ ---
855
+
856
+ ## Overview
857
+
858
+ Prompts are version-controlled templates stored on the Respan platform. The workflow is:
859
+
860
+ 1. **Create** a prompt with a name
861
+ 2. **Add versions** with messages, model config, and \`{{variables}}\`
862
+ 3. **Deploy** the version you want live
863
+ 4. **Fetch** the prompt in code using the API or MCP tools
864
+ 5. **Iterate** — commit new versions and deploy without code changes
865
+
866
+ ---
867
+
868
+ ## Create a Prompt
869
+
870
+ ### Via MCP
871
+
872
+ \`\`\`
873
+ Use create_prompt to create a prompt:
874
+ name: "classify_intent"
875
+ description: "Classifies user messages into intent categories"
876
+
877
+ Then use create_prompt_version to add content:
878
+ prompt_id: "<id from above>"
879
+ model: "gpt-4o-mini"
880
+ messages:
881
+ - role: "system"
882
+ content: "You are an intent classifier. Classify the user message into one of: question, complaint, request, feedback."
883
+ - role: "user"
884
+ content: "{{user_message}}"
885
+ \`\`\`
886
+
887
+ ### Via CLI
888
+
889
+ \`\`\`bash
890
+ respan prompts create --name "classify_intent"
891
+ respan prompts versions <prompt-id>
892
+ \`\`\`
893
+
894
+ ---
895
+
896
+ ## Variables
897
+
898
+ Use \`{{variable_name}}\` in message content. Variables are filled at runtime.
899
+
900
+ \`\`\`json
901
+ {
902
+ "messages": [
903
+ {"role": "system", "content": "You are a {{role}} assistant."},
904
+ {"role": "user", "content": "{{user_input}}"}
905
+ ]
906
+ }
907
+ \`\`\`
908
+
909
+ ### Jinja templates
910
+
911
+ Prompts support Jinja2 syntax:
912
+
913
+ - **Conditionals:** \`{% if context %}Use this context: {{context}}{% endif %}\`
914
+ - **JSON access:** \`{{ input.key }}\`
915
+ - **Filters:** \`{{ var | upper }}\`
916
+ - **Loops:** \`{% for item in items %}{{item}}{% endfor %}\`
917
+ - **Default values:** Set via the API or platform UI
918
+
919
+ ---
920
+
921
+ ## Versioning and Deployment
922
+
923
+ Each prompt can have multiple versions. Only one version is **deployed** (live) at a time.
924
+
925
+ ### Workflow
926
+
927
+ 1. \`create_prompt_version\` — creates a new version (always starts as NOT deployed)
928
+ 2. Test the version in the playground or via API
929
+ 3. Deploy via platform UI when ready
930
+ 4. Code using \`prompt_id\` automatically gets the deployed version
931
+
932
+ ### Version pinning
933
+
934
+ \`\`\`python
935
+ # Always use the deployed version (default)
936
+ prompt = get_prompt(prompt_id="abc123")
937
+
938
+ # Pin to a specific version
939
+ prompt = get_prompt(prompt_id="abc123", version=3)
940
+ \`\`\`
941
+
942
+ ---
943
+
944
+ ## Using Prompts in Code
945
+
946
+ ### Python (via Gateway)
947
+
948
+ Fetch the prompt config and pass to the LLM:
949
+
950
+ \`\`\`python
951
+ import os
952
+ from openai import OpenAI
953
+
954
+ client = OpenAI(
955
+ api_key=os.environ["RESPAN_API_KEY"],
956
+ base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
957
+ )
958
+
959
+ # The gateway resolves the prompt template and fills variables
960
+ response = client.chat.completions.create(
961
+ model="gpt-4o-mini",
962
+ messages=[], # Messages come from the prompt template
963
+ extra_body={
964
+ "prompt_id": "your-prompt-id",
965
+ "prompt_variables": {
966
+ "user_message": "I want to return my order",
967
+ },
968
+ },
969
+ )
970
+ \`\`\`
971
+
972
+ ### Linking traces to prompts
973
+
974
+ Use \`propagate_attributes\` to tag traces with the prompt used:
975
+
976
+ \`\`\`python
977
+ from respan import propagate_attributes
978
+
979
+ with propagate_attributes(
980
+ prompt={"prompt_id": "abc123", "variables": {"user_message": "Hello"}},
981
+ ):
982
+ response = client.chat.completions.create(...)
983
+ \`\`\`
984
+
985
+ ---
986
+
987
+ ## Prompt Composition
988
+
989
+ Variables can reference other prompts (child prompts rendered first, injected as text).
990
+
991
+ Configure in the platform UI by changing a variable's type from **Text** to **Prompt**.
992
+
993
+ Max nesting depth: 2 (parent -> child).
994
+
995
+ ---
996
+
997
+ ## Structured Output (JSON Schema)
998
+
999
+ Attach a JSON schema to a prompt version for structured responses:
1000
+
1001
+ \`\`\`json
1002
+ {
1003
+ "response_format": {
1004
+ "type": "json_schema",
1005
+ "json_schema": {
1006
+ "name": "extraction",
1007
+ "strict": true,
1008
+ "schema": {
1009
+ "type": "object",
1010
+ "properties": {
1011
+ "intent": {"type": "string"},
1012
+ "confidence": {"type": "number"}
1013
+ },
1014
+ "required": ["intent", "confidence"],
1015
+ "additionalProperties": false
1016
+ }
1017
+ }
1018
+ }
1019
+ }
1020
+ \`\`\`
1021
+
1022
+ ---
1023
+
1024
+ ## MCP Tools Reference
1025
+
1026
+ | Action | Tool |
1027
+ |--------|------|
1028
+ | List all prompts | \`list_prompts\` |
1029
+ | Get prompt details | \`get_prompt_detail(prompt_id)\` |
1030
+ | Create a prompt | \`create_prompt(name, description)\` |
1031
+ | Update name/description | \`update_prompt(prompt_id, name, description)\` |
1032
+ | Create a version | \`create_prompt_version(prompt_id, messages, model, ...)\` |
1033
+ | Update a version | \`update_prompt_version(prompt_id, version, ...)\` |
1034
+ | List versions | \`list_prompt_versions(prompt_id)\` |
1035
+ | Get version detail | \`get_prompt_version_detail(prompt_id, version)\` |
1036
+
1037
+ ---
1038
+
1039
+ ## CLI Commands
1040
+
1041
+ \`\`\`bash
1042
+ respan prompts list # List all prompts
1043
+ respan prompts get <id> # Get prompt details
1044
+ respan prompts create --name <name> # Create a prompt
1045
+ respan prompts update <id> # Update a prompt
1046
+ respan prompts versions <id> # List prompt versions
1047
+ \`\`\`
1048
+ `;
1049
+ export const EVALS_MD = `# Evals Reference
1050
+
1051
+ Build evaluation pipelines: datasets, evaluators, and experiments.
1052
+
1053
+ Docs: \`https://respan.ai/docs/documentation/features/evals/concepts.md\`
1054
+
1055
+ ---
1056
+
1057
+ ## Overview
1058
+
1059
+ Two evaluation modes:
1060
+
1061
+ - **Offline** — build datasets, run experiments across prompt/model variants, compare scores
1062
+ - **Online** — automatically score live production traffic (see [monitors.md](monitors.md))
1063
+
1064
+ Offline eval workflow:
1065
+
1066
+ \`\`\`
1067
+ Create dataset -> Create evaluator -> Create experiment -> Run & compare
1068
+ \`\`\`
1069
+
1070
+ ---
1071
+
1072
+ ## Datasets
1073
+
1074
+ A dataset is a collection of test rows (input/output pairs) for experiments.
1075
+
1076
+ ### Create from scratch
1077
+
1078
+ \`\`\`
1079
+ Use create_dataset:
1080
+ name: "intent_classification_testset"
1081
+ description: "Test cases for intent classifier"
1082
+
1083
+ Then add rows with create_dataset_span:
1084
+ dataset_id: "<id>"
1085
+ input: '{"user_message": "I want a refund"}'
1086
+ output: '{"intent": "refund_request"}'
1087
+ \`\`\`
1088
+
1089
+ ### Create from production traces
1090
+
1091
+ Sample real spans from traced data:
1092
+
1093
+ \`\`\`
1094
+ 1. Use list_traces or list_logs to find relevant spans
1095
+ 2. Use add_spans_to_dataset to add span IDs to a dataset:
1096
+ dataset_id: "<id>"
1097
+ span_ids: ["span_1", "span_2", "span_3"]
1098
+ \`\`\`
1099
+
1100
+ Or use the platform UI: Datasets -> Insert by sampling (filter, set sampling %, preview, confirm).
1101
+
1102
+ ### CSV import
1103
+
1104
+ Upload CSV via the platform UI. Columns map to prompt variables. Optional \`ideal_output\` column for expected outputs. Max 500 rows per import.
1105
+
1106
+ ### MCP tools
1107
+
1108
+ | Action | Tool |
1109
+ |--------|------|
1110
+ | Create dataset | \`create_dataset(name, description)\` |
1111
+ | List datasets | \`list_datasets\` |
1112
+ | Get dataset | \`get_dataset(dataset_id)\` |
1113
+ | Update dataset | \`update_dataset(dataset_id, name, description)\` |
1114
+ | Add a row manually | \`create_dataset_span(dataset_id, input, output)\` |
1115
+ | Add existing spans | \`add_spans_to_dataset(dataset_id, span_ids)\` |
1116
+ | List rows | \`list_dataset_spans(dataset_id)\` |
1117
+ | Get a row | \`retrieve_dataset_span(dataset_id, log_id)\` |
1118
+ | Update a row | \`update_dataset_span(dataset_id, log_id, ...)\` |
1119
+
1120
+ ---
1121
+
1122
+ ## Evaluators
1123
+
1124
+ An evaluator scores LLM outputs. It is a workflow built from **graders** connected with conditions.
1125
+
1126
+ ### Grader types
1127
+
1128
+ | Type | How it works |
1129
+ |------|-------------|
1130
+ | **LLM grader** | A language model judges the output. Uses variables: \`{{output}}\`, \`{{input}}\`, \`{{expected_output}}\`, \`{{metadata}}\`, \`{{metrics}}\` |
1131
+ | **Code grader** | A Python function returns a score. Signature: \`main(eval_inputs)\` where \`eval_inputs["output"]\` is required |
1132
+ | **Human grader** | Team members review and score outputs manually |
1133
+
1134
+ ### Score value types
1135
+
1136
+ | Type | Use case |
1137
+ |------|----------|
1138
+ | \`numerical\` | Rating scales (e.g. 0-5) |
1139
+ | \`boolean\` | Pass/fail checks |
1140
+ | \`categorical\` | Multi-choice classifications |
1141
+ | \`comment\` | Qualitative text feedback |
1142
+
1143
+ ### Create an evaluator
1144
+
1145
+ \`\`\`
1146
+ Use create_evaluator:
1147
+ name: "response_quality"
1148
+ score_value_type: "numerical"
1149
+ type: "llm"
1150
+ description: "Scores response quality 1-5"
1151
+ evaluator_slug: "response-quality"
1152
+ \`\`\`
1153
+
1154
+ For complex evaluators with grader workflows, conditions, and compute blocks — use the platform UI (Evaluators -> Create).
1155
+
1156
+ ### Evaluator workflow blocks
1157
+
1158
+ - **Markers:** \`Original input\`, \`Final result\` (entry/exit)
1159
+ - **Graders:** LLM, Code, or Human scoring
1160
+ - **Conditions:** \`If...Then...Else\` branching based on scores
1161
+ - **Compute:** Average/weighted average of multiple grader scores
1162
+ - **Metrics:** Built-in: completion tokens, cost, latency, model, prompt tokens, total tokens
1163
+ - **Constants:** Fixed threshold values
1164
+
1165
+ ### MCP tools
1166
+
1167
+ | Action | Tool |
1168
+ |--------|------|
1169
+ | List evaluators | \`list_evaluators\` |
1170
+ | Get evaluator | \`get_evaluator(evaluator_id)\` |
1171
+ | Create evaluator | \`create_evaluator(name, score_value_type, ...)\` |
1172
+ | Update evaluator | \`update_evaluator(evaluator_id, ...)\` |
1173
+ | Run evaluator | \`run_evaluator(evaluator_id, dataset_id or log_ids)\` |
1174
+
1175
+ ---
1176
+
1177
+ ## Experiments
1178
+
1179
+ An experiment runs evaluators across a dataset to compare prompt/model configurations.
1180
+
1181
+ ### Workflow
1182
+
1183
+ 1. Create a dataset with test rows
1184
+ 2. Create evaluator(s) to score outputs
1185
+ 3. Create an experiment linking dataset + evaluators
1186
+ 4. Run the experiment — it generates outputs for each row and scores them
1187
+ 5. Compare results across configurations
1188
+
1189
+ ### Create an experiment
1190
+
1191
+ \`\`\`
1192
+ Use create_experiment:
1193
+ name: "v2_vs_v3_comparison"
1194
+ dataset_id: "<dataset-id>"
1195
+ evaluator_slugs: ["response-quality", "hallucination-check"]
1196
+ workflows:
1197
+ - type: "prompt"
1198
+ config: { prompt_id: "<prompt-v2-id>" }
1199
+ - type: "prompt"
1200
+ config: { prompt_id: "<prompt-v3-id>" }
1201
+ \`\`\`
1202
+
1203
+ ### Workflow types
1204
+
1205
+ | Type | Description |
1206
+ |------|-------------|
1207
+ | \`prompt\` | Run a saved prompt template against dataset rows |
1208
+ | \`completion\` | Run a model completion with config |
1209
+ | \`custom\` | User-defined logic |
1210
+
1211
+ ### MCP tools
1212
+
1213
+ | Action | Tool |
1214
+ |--------|------|
1215
+ | List experiments | \`list_experiments\` |
1216
+ | Get experiment | \`get_experiment(experiment_id)\` |
1217
+ | Create experiment | \`create_experiment(name, dataset_id, ...)\` |
1218
+ | List experiment spans | \`list_experiment_spans(experiment_id)\` |
1219
+ | Get experiment span | \`get_experiment_span(experiment_id, log_id)\` |
1220
+ | Update experiment span | \`update_experiment_span(experiment_id, log_id, ...)\` |
1221
+ | Get summary stats | \`get_experiment_spans_summary(experiment_id, start_time, end_time)\` |
1222
+
1223
+ ---
1224
+
1225
+ ## End-to-End Example
1226
+
1227
+ \`\`\`
1228
+ Step 1: Create dataset
1229
+ create_dataset(name="qa_testset")
1230
+ create_dataset_span(dataset_id="...", input="What is Python?", output="A programming language")
1231
+ create_dataset_span(dataset_id="...", input="Who made Rust?", output="Mozilla Research")
1232
+
1233
+ Step 2: Create evaluator
1234
+ create_evaluator(
1235
+ name="accuracy_check",
1236
+ score_value_type="boolean",
1237
+ type="llm",
1238
+ evaluator_slug="accuracy-check"
1239
+ )
1240
+
1241
+ Step 3: Create experiment
1242
+ create_experiment(
1243
+ name="gpt4o_vs_claude",
1244
+ dataset_id="...",
1245
+ evaluator_slugs=["accuracy-check"]
1246
+ )
1247
+
1248
+ Step 4: Review results
1249
+ list_experiment_spans(experiment_id="...")
1250
+ get_experiment_spans_summary(experiment_id="...", start_time="...", end_time="...")
1251
+ \`\`\`
1252
+
1253
+ ---
1254
+
1255
+ ## CLI Commands
1256
+
1257
+ \`\`\`bash
1258
+ respan datasets list # List datasets
1259
+ respan datasets create --name <name> # Create a dataset
1260
+ respan datasets get <id> # Get dataset details
1261
+ respan datasets spans <id> # List dataset spans
1262
+ respan evaluators list # List evaluators
1263
+ respan evaluators get <id> # Get evaluator details
1264
+ respan evaluators create # Create an evaluator
1265
+ respan evaluators run <id> # Run an evaluator
1266
+ respan experiments list # List experiments
1267
+ respan experiments get <id> # Get experiment details
1268
+ respan experiments create # Create an experiment
1269
+ respan eval <file> # Run a one-shot eval defined in a JSON file
1270
+ \`\`\`
1271
+
1272
+ ## One-Shot Evals via JSON
1273
+
1274
+ \`respan eval <file.json>\` runs a complete eval pipeline from a single JSON spec —
1275
+ creates the prompt, dataset, evaluators, and experiment in one command.
1276
+ IDs are written back into the file so reruns reuse existing resources.
1277
+
1278
+ ### Minimal example
1279
+
1280
+ \`\`\`json
1281
+ {
1282
+ "name": "Movie matcher",
1283
+ "prompt": {
1284
+ "name": "movie-matcher",
1285
+ "model": "gpt-5-mini",
1286
+ "messages": [
1287
+ { "role": "system", "content": "Identify the movie. Reply with only the title." },
1288
+ { "role": "user", "content": "{{description}}" }
1289
+ ]
1290
+ },
1291
+ "dataset": {
1292
+ "name": "movie-descriptions",
1293
+ "rows": [
1294
+ { "description": "A detective investigates seven deadly sins.", "expected_output": "Se7en" },
1295
+ { "description": "A hacker learns reality is a simulation.", "expected_output": "The Matrix" }
1296
+ ]
1297
+ },
1298
+ "experiment": {
1299
+ "evaluators": [
1300
+ {
1301
+ "name": "Title match",
1302
+ "type": "llm",
1303
+ "score_value_type": "boolean",
1304
+ "llm_config": {
1305
+ "model": "openai/gpt-5-mini",
1306
+ "evaluator_definition": "Expected: {{expected_output}}. Got: {{output}}. Correct? Reply true or false."
1307
+ }
1308
+ }
1309
+ ]
1310
+ }
1311
+ }
1312
+ \`\`\`
1313
+
1314
+ Run:
1315
+
1316
+ \`\`\`bash
1317
+ respan eval movie-matcher.eval.json
1318
+ \`\`\`
1319
+
1320
+ The runner creates the prompt (and deploys version 1), creates the dataset (and bulk-loads rows),
1321
+ creates each evaluator + its workflow (and deploys), then creates the experiment linking them.
1322
+ Each step writes the resulting IDs back to the JSON file so subsequent runs are idempotent —
1323
+ re-running with the same file reuses the existing resources.
1324
+ `;
1325
+ export const MONITORS_MD = `# Monitors & Automation Reference
1326
+
1327
+ Set up monitoring alerts, online evaluations, and automated workflows.
1328
+
1329
+ Docs: \`https://respan.ai/docs/documentation/features/monitoring/monitors.md\`
1330
+
1331
+ ---
1332
+
1333
+ ## Monitors
1334
+
1335
+ A monitor watches a metric over time and sends a notification when a threshold is crossed.
1336
+
1337
+ ### Examples
1338
+
1339
+ - Error rate > 5% over 10 minutes
1340
+ - Total cost > $1.00 over 5 minutes
1341
+ - Average latency > 3 seconds over 15 minutes
1342
+ - P95 latency > 5 seconds over 1 hour
1343
+
1344
+ ### Setup
1345
+
1346
+ Monitors are configured in the platform UI: **Monitoring -> Monitors -> Create**.
1347
+
1348
+ A monitor has two parts:
1349
+
1350
+ #### 1. Trigger
1351
+
1352
+ Template: \`When [metric] of [source] is [threshold] over [time window]\`
1353
+
1354
+ - **Metric:** cost, latency, error rate, token usage, request count, etc.
1355
+ - **Source:** all requests, specific model, specific customer, etc.
1356
+ - **Threshold:** condition (greater than, less than, equals)
1357
+ - **Time window:** evaluation period (1 min, 5 min, 10 min, 1 hour, etc.)
1358
+
1359
+ Add **Where** conditions to narrow scope:
1360
+ - Filter by model, project, environment, customer, or custom metadata
1361
+
1362
+ #### 2. Notifications
1363
+
1364
+ Destinations:
1365
+
1366
+ | Destination | Details |
1367
+ |-------------|---------|
1368
+ | **Email** | Customizable subject and body |
1369
+ | **Slack** | Customizable message |
1370
+ | **Webhook** | Raw URL — receives JSON payload |
1371
+
1372
+ A single monitor can send to multiple destinations.
1373
+
1374
+ ### Manage
1375
+
1376
+ - **Deploy** to make a monitor live
1377
+ - **Pause/Resume** deployed monitors
1378
+ - **Send test alert** to validate before deploying
1379
+ - **View versions** and restore earlier versions as drafts
1380
+ - **Create from chart** — select a metric on the dashboard, click to create a monitor with metric/source/interval prefilled
1381
+
1382
+ ---
1383
+
1384
+ ## Online Evaluations (Automation)
1385
+
1386
+ Automatically score production traffic in real time using deployed evaluators.
1387
+
1388
+ ### Setup
1389
+
1390
+ Configure in the platform UI: **Evaluators -> Automations -> Create**.
1391
+
1392
+ 1. **Create a condition** — filter rules for which logs to evaluate:
1393
+ - **Single log** — evaluate individual spans matching criteria
1394
+ - **Aggregation** — evaluate aggregated metrics over a window
1395
+
1396
+ 2. **Create an automation** — connect:
1397
+ - Condition (which logs)
1398
+ - Evaluator (which scoring workflow)
1399
+ - Sampling rate (0.0-1.0, fraction of matching logs to evaluate)
1400
+
1401
+ 3. **Check results** — view eval scores in the automation table
1402
+
1403
+ ### Use case
1404
+
1405
+ > "Score 10% of production chat responses for hallucination using my LLM evaluator"
1406
+
1407
+ \`\`\`
1408
+ Condition: log_type = "chat" AND model = "gpt-4o"
1409
+ Evaluator: hallucination-check
1410
+ Sampling: 0.1
1411
+ \`\`\`
1412
+
1413
+ ---
1414
+
1415
+ ## Webhooks
1416
+
1417
+ Receive event notifications via HTTP webhooks.
1418
+
1419
+ ### Setup
1420
+
1421
+ 1. Go to platform: **Settings -> Webhooks -> Create**
1422
+ 2. Enter your endpoint URL
1423
+ 3. Select event types to subscribe to
1424
+ 4. Optionally set a webhook secret for HMAC-SHA256 verification
1425
+
1426
+ ### Verification
1427
+
1428
+ Respan signs webhook payloads with HMAC-SHA256. Verify the signature:
1429
+
1430
+ \`\`\`python
1431
+ import hashlib
1432
+ import hmac
1433
+
1434
+ def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
1435
+ expected = hmac.new(
1436
+ secret.encode(),
1437
+ payload,
1438
+ hashlib.sha256,
1439
+ ).hexdigest()
1440
+ return hmac.compare_digest(expected, signature)
1441
+ \`\`\`
1442
+
1443
+ The signature is sent in the request headers.
1444
+
1445
+ ---
1446
+
1447
+ ## Metrics Dashboard
1448
+
1449
+ Track key indicators in the platform: **Dashboard -> Metrics**.
1450
+
1451
+ ### Available metrics
1452
+
1453
+ - **Requests:** total count, requests per model/customer/API key
1454
+ - **Tokens:** prompt tokens, completion tokens, total tokens
1455
+ - **Cost:** per-request, per-model, per-customer
1456
+ - **Latency:** average, P50, P95, P99
1457
+ - **Errors:** error count, error rate
1458
+ - **Time to first token (TTFT):** streaming latency
1459
+
1460
+ ### Breakdowns
1461
+
1462
+ Slice metrics by:
1463
+ - Model
1464
+ - Customer (\`customer_identifier\`)
1465
+ - API key
1466
+ - Prompt template
1467
+ - Environment
1468
+ - Custom metadata keys
1469
+
1470
+ ### Per-user analytics
1471
+
1472
+ Use \`customer_identifier\` in spans to enable:
1473
+ - Per-user cost tracking and budgets
1474
+ - Per-user rate limiting
1475
+ - User-level quality metrics
1476
+
1477
+ ---
1478
+
1479
+ ## CLI Commands
1480
+
1481
+ \`\`\`bash
1482
+ respan traces list --limit 10 # List recent traces
1483
+ respan traces get <trace-id> # Get trace details
1484
+ respan traces summary # Trace summary stats
1485
+ respan logs list --limit 10 # List recent logs
1486
+ respan logs get <log-id> # Get log details
1487
+ respan logs summary # Log summary stats
1488
+ \`\`\`
1489
+ `;