docs: add Respan observability integration guide#3269
docs: add Respan observability integration guide#3269drPod wants to merge 2 commits intoBoundaryML:canaryfrom
Conversation
Add a new documentation page showing how to integrate BAML with the Respan observability platform. Covers three integration approaches: Respan decorators, on_log_event callback forwarding, and Collector API with rich trace data. Includes Python and TypeScript examples. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@drPod is attempting to deploy a commit to the Boundary Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📝 WalkthroughWalkthroughAdded a new documentation page introducing Respan integration approaches with BAML, covering three methods: using Respan SDK decorators to create workflow and task spans, forwarding per-LLM-call events via BAML tracing callbacks, and capturing rich trace data via BAML Collector for Respan ingestion. Updated navigation to link to this new guide. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~5 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 35ea7201-5df8-4b9e-8b1c-5eb29fdbf311
📒 Files selected for processing (2)
fern/01-guide/07-observability/respan.mdxfern/docs.yml
| --- | ||
| title: Respan | ||
| --- |
There was a problem hiding this comment.
Add required subtitle to frontmatter.
This guide starts with frontmatter but omits subtitle, which is required for every .mdx file.
Proposed fix
---
title: Respan
+subtitle: Learn how to integrate BAML with Respan observability
---As per coding guidelines: "Every .mdx file must start with frontmatter containing title and subtitle in the specified format" and "Subtitles should be concise and short, with some starting with 'Learn how to …' for guides".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| --- | |
| title: Respan | |
| --- | |
| --- | |
| title: Respan | |
| subtitle: Learn how to integrate BAML with Respan observability | |
| --- |
| There are three ways to integrate BAML with Respan, depending on how much control you need: | ||
|
|
||
| 1. **Wrap BAML calls with Respan decorators** — easiest, gives you workflow-level traces | ||
| 2. **Use `on_log_event` to forward events** — real-time event streaming to Respan | ||
| 3. **Use `Collector` for rich trace data** — most detailed, includes token usage, HTTP requests, and timing | ||
|
|
There was a problem hiding this comment.
Tighten wording to a neutral technical tone.
Phrases like “easiest,” “most detailed,” and “simplest approach” read promotional. Prefer neutral, factual phrasing.
As per coding guidelines: "Use scientific research tone—professional, factual, and straightforward" and "Do not use marketing/promotional language".
Also applies to: 36-36
| <CodeGroup> | ||
| ```bash Python | ||
| pip install respan-tracing | ||
| ``` | ||
|
|
||
| ```bash TypeScript | ||
| npm install @respan/tracing | ||
| ``` | ||
| </CodeGroup> | ||
|
|
There was a problem hiding this comment.
Use CodeBlocks instead of CodeGroup for multi-language snippets.
The page consistently uses CodeGroup, but the docs standard requires CodeBlocks for grouped language examples.
Example migration pattern
-<CodeGroup>
-```python Python
+<CodeBlocks>
+```python
...-typescript TypeScript +typescript
...
-</CodeGroup>
+</CodeBlocks>
As per coding guidelines: "Use CodeBlocks component for groups of code with multiple languages, starting with Python as the default language".
Also applies to: 38-92, 97-187, 192-282
There was a problem hiding this comment.
Pull request overview
Adds a new Observability guide page documenting how to integrate BAML tracing data with the Respan observability platform, and wires it into the docs navigation.
Changes:
- Added
Respandocumentation page with 3 integration approaches (decorators,on_log_event, Collector). - Updated docs navigation to include the new Respan page under Observability.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
fern/docs.yml |
Adds the new Respan page to the Observability navigation. |
fern/01-guide/07-observability/respan.mdx |
New integration guide with Python/TypeScript examples and links to relevant Respan/BAML references. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "input": str(log.raw_llm_response), | ||
| "output": str(result), |
There was a problem hiding this comment.
raw_llm_response is the model’s raw response text, not the request input. Using it as the Respan span input will invert the data in the dashboard. Consider sending the original function args / rendered prompt as input, and raw_llm_response (and/or parsed result) as output or metadata.
| span_name: log.functionName, | ||
| input: log.rawLlmResponse, | ||
| output: JSON.stringify(result), |
There was a problem hiding this comment.
Same issue here: log.rawLlmResponse is the model output, not the input. Mapping it to input will swap request/response in Respan. Prefer using the function args/prompt as input and the raw response / parsed result as output or metadata.
| requests.post( | ||
| RESPAN_INGEST_URL, | ||
| headers={ | ||
| "Authorization": f"Bearer {RESPAN_API_KEY}", | ||
| "Content-Type": "application/json", | ||
| }, | ||
| json=[ | ||
| { | ||
| "trace_unique_id": event.metadata.root_event_id, | ||
| "span_unique_id": event.metadata.event_id, | ||
| "span_parent_id": event.metadata.parent_id, | ||
| "span_name": "baml_llm_call", | ||
| "input": event.prompt, | ||
| "output": event.raw_output, | ||
| "start_time": event.start_time, | ||
| "timestamp": event.start_time, | ||
| "metadata": { | ||
| "source": "baml", | ||
| "parsed_output": event.parsed_output, | ||
| }, | ||
| } | ||
| ], | ||
| ) |
There was a problem hiding this comment.
The on_log_event callback runs synchronously in the tracing pipeline; making a blocking network call (requests.post) here will add latency to every LLM call and can stall the app if the ingest endpoint is slow. Consider enqueueing events to a background worker/batch sender, and at minimum set a short timeout + handle non-2xx responses/exceptions.
| requests.post( | |
| RESPAN_INGEST_URL, | |
| headers={ | |
| "Authorization": f"Bearer {RESPAN_API_KEY}", | |
| "Content-Type": "application/json", | |
| }, | |
| json=[ | |
| { | |
| "trace_unique_id": event.metadata.root_event_id, | |
| "span_unique_id": event.metadata.event_id, | |
| "span_parent_id": event.metadata.parent_id, | |
| "span_name": "baml_llm_call", | |
| "input": event.prompt, | |
| "output": event.raw_output, | |
| "start_time": event.start_time, | |
| "timestamp": event.start_time, | |
| "metadata": { | |
| "source": "baml", | |
| "parsed_output": event.parsed_output, | |
| }, | |
| } | |
| ], | |
| ) | |
| try: | |
| response = requests.post( | |
| RESPAN_INGEST_URL, | |
| headers={ | |
| "Authorization": f"Bearer {RESPAN_API_KEY}", | |
| "Content-Type": "application/json", | |
| }, | |
| json=[ | |
| { | |
| "trace_unique_id": event.metadata.root_event_id, | |
| "span_unique_id": event.metadata.event_id, | |
| "span_parent_id": event.metadata.parent_id, | |
| "span_name": "baml_llm_call", | |
| "input": event.prompt, | |
| "output": event.raw_output, | |
| "start_time": event.start_time, | |
| "timestamp": event.start_time, | |
| "metadata": { | |
| "source": "baml", | |
| "parsed_output": event.parsed_output, | |
| }, | |
| } | |
| ], | |
| timeout=2.0, | |
| ) | |
| if not (200 <= response.status_code < 300): | |
| # In a real app, consider using structured logging instead of print | |
| print( | |
| f"Respan ingest returned status {response.status_code}: " | |
| f"{response.text}" | |
| ) | |
| except requests.exceptions.RequestException as exc: | |
| # Swallow exceptions to avoid breaking the tracing pipeline | |
| print(f"Error sending event to Respan: {exc}") |
| | Approach | Effort | Detail Level | Best For | | ||
| |----------|--------|-------------|----------| | ||
| | **Respan decorators** | Low | Workflow-level spans | Quick setup, structured traces | | ||
| | **`on_log_event`** | Medium | Per-LLM-call events | Real-time streaming, custom filtering | | ||
| | **`Collector` + API** | Medium | Full LLM metadata (tokens, HTTP, timing) | Cost tracking, debugging, detailed analytics | |
There was a problem hiding this comment.
The comparison table is written with double leading pipes (|| ...), which renders as an extra empty column in Markdown. Use a single leading | for the header/separator/rows so the table displays correctly.
| result = await b.ExtractResume( | ||
| "John Doe, Software Engineer...", | ||
| baml_options={"collector": collector}, | ||
| ) |
There was a problem hiding this comment.
This example uses await at top-level (result = await ...). Top-level await is not valid in normal Python scripts, so readers copying this will get a syntax error. Wrap the snippet in an async def main() + asyncio.run(main()), or remove await and show the synchronous call style used elsewhere in the docs.
| span = { | ||
| "trace_unique_id": f"baml-{log.id}", | ||
| "span_unique_id": log.id, | ||
| "span_name": log.function_name, | ||
| "input": str(log.raw_llm_response), | ||
| "output": str(result), | ||
| "start_time": log.timing.start_time.isoformat(), | ||
| "latency": log.timing.duration_ms / 1000, |
There was a problem hiding this comment.
log.timing.start_time does not exist on the Python Timing object; the public fields are start_time_utc_ms and duration_ms (and duration_ms can be None). Update this snippet to use start_time_utc_ms (convert to ISO-8601 if needed) and handle a missing duration_ms safely.
| span = { | |
| "trace_unique_id": f"baml-{log.id}", | |
| "span_unique_id": log.id, | |
| "span_name": log.function_name, | |
| "input": str(log.raw_llm_response), | |
| "output": str(result), | |
| "start_time": log.timing.start_time.isoformat(), | |
| "latency": log.timing.duration_ms / 1000, | |
| from datetime import datetime, timezone | |
| start_time_iso = datetime.fromtimestamp( | |
| log.timing.start_time_utc_ms / 1000.0, tz=timezone.utc | |
| ).isoformat() | |
| latency_seconds = ( | |
| log.timing.duration_ms / 1000.0 if log.timing.duration_ms is not None else None | |
| ) | |
| span = { | |
| "trace_unique_id": f"baml-{log.id}", | |
| "span_unique_id": log.id, | |
| "span_name": log.function_name, | |
| "input": str(log.raw_llm_response), | |
| "output": str(result), | |
| "start_time": start_time_iso, | |
| "latency": latency_seconds, |
| "output": str(result), | ||
| "start_time": log.timing.start_time.isoformat(), | ||
| "latency": log.timing.duration_ms / 1000, | ||
| "model": log.calls[-1].model if log.calls else None, |
There was a problem hiding this comment.
log.calls[-1].model is not a field on the Python collector call objects. Use an available field (e.g. client_name / provider info) or omit model if it can’t be derived reliably from the collector call.
| "model": log.calls[-1].model if log.calls else None, |
Summary
fern/01-guide/07-observability/respan.mdxshowing how to integrate BAML with Respan, an open-source LLM observability platform@workflow/@task) wrapping BAML function callson_log_eventcallback forwarding events to Respan's trace ingestion APICollectorAPI for rich trace data (tokens, timing, HTTP details) sent to Respanfern/docs.yml)Test plan
fern check --strict-broken-linkspasses (only pre-existingcustom.jserror remains)🤖 Generated with Claude Code
Summary by CodeRabbit