feat: add Braintrust tracing integration - #11677
Conversation
…handler using only braintrust SDK
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR adds Braintrust integration to Langflow for capturing and forwarding component and flow traces. It includes documentation, sidebar navigation updates, a new BraintrustTracer implementation with environment-based configuration, and integration into the existing tracing service infrastructure. Changes
Sequence DiagramsequenceDiagram
participant LF as Langflow Flow
participant TS as TracingService
participant BT as BraintrustTracer
participant BR as Braintrust Backend
LF->>TS: start_tracers()
TS->>BT: _initialize_braintrust_tracer()
BT->>BT: _get_config() from env
BT->>BR: Initialize root span (flow metadata)
BR-->>BT: Root span created
LF->>BT: add_trace(component inputs)
BT->>BR: Create child span
BR-->>BT: Child span stored
LF->>BT: end_trace(component outputs/logs)
BT->>BR: Finish child span with outputs/logs
BR-->>BT: Span finalized
LF->>BT: end(flow outputs/error)
BT->>BR: Finish root span with flow I/O
BR-->>BT: Root span closed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 3 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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 |
…remove inline handler
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@docs/docs/Develop/integrations-braintrust.mdx`:
- Around line 1-4: The MDX frontmatter is missing required fields; update the
YAML frontmatter for the Braintrust doc by adding the missing `description` and
`sidebar_position` keys alongside the existing `title` (e.g., set a short
descriptive string for `description` and an integer for `sidebar_position`) so
the frontmatter contains `title`, `description`, and `sidebar_position`.
- Around line 31-33: Several bash code blocks (instances of the triple-backtick
fences that start with "```bash") are missing the required title attribute;
update each code fence (e.g., the ones currently written as "```bash") to
include a title like title="Terminal" (so they read e.g. ```bash
title="Terminal") or another descriptive title per block; ensure you change
every bash block in the file (including the blocks noted around the earlier
examples) so each specifies both the language and a title attribute.
In `@src/backend/base/langflow/services/tracing/braintrust.py`:
- Around line 50-68: The Braintrust tracer constructor (__init__ in
braintrust.py) accepts user_id and session_id but _initialize_braintrust_tracer
in service.py does not forward them, so root span metadata always has None;
update _initialize_braintrust_tracer to accept (or extract) the service-layer
user_id and session_id and pass them into the Braintrust tracer constructor when
instantiating the tracer (same pattern used by langfuse/opik tracers) so the
root span metadata includes the correct user_id and session_id.
In `@src/backend/base/langflow/services/tracing/service.py`:
- Around line 229-238: The Braintrust tracer is not receiving user_id and
session_id from the TraceContext, so root span metadata will always be None;
update _initialize_braintrust_tracer to pass trace_context.user_id and
trace_context.session_id into the braintrust_tracer call (alongside trace_name,
trace_type, project_name, trace_id) so the tracer's constructor receives those
values (TraceContext and braintrust_tracer are the relevant symbols to update).
🧹 Nitpick comments (3)
src/backend/base/langflow/services/tracing/braintrust.py (3)
104-106:logger.exceptionis too noisy for a missing optional dependency.
logger.exceptionlogs the full stack trace. For an expected condition (optionalbraintrustpackage not installed), a simplelogger.infoorlogger.warningwith the message is more appropriate. Other tracers in the codebase handleImportErrormore quietly.Proposed fix
except ImportError: - logger.exception("Could not import braintrust. Please install it with `pip install braintrust`.") + logger.info("Could not import braintrust. Please install it with `pip install braintrust`.") return False
212-213: Redundantisinstancecheck —HumanMessageandSystemMessageare subclasses ofBaseMessage.Checking
isinstance(value, (BaseMessage, HumanMessage, SystemMessage))is equivalent toisinstance(value, BaseMessage)since the latter two inherit from it. The extra types can be removed for clarity.Proposed fix
- if isinstance(value, (BaseMessage, HumanMessage, SystemMessage)): + if isinstance(value, BaseMessage): return value.content
208-217:Messagecheck must precedeBaseMessage— andstr(None)produces"None"string.Two observations on the ordering and behavior:
Order is correct —
Messageis checked beforeBaseMessage, which is important sinceMessagemay share an ancestor. Good.
types.NoneTypein the tuple at line 216 convertsNoneto the string"None"rather than passing through asNone. If this is intentional, a brief comment would help; otherwise theNoneTypecheck can be dropped soNonepasses through as-is fromreturn value.
| --- | ||
| title: Braintrust | ||
| slug: /integrations-braintrust | ||
| --- |
There was a problem hiding this comment.
Frontmatter is missing description and sidebar_position.
As per coding guidelines, YAML frontmatter must include title, description, and sidebar_position.
Proposed fix
---
title: Braintrust
slug: /integrations-braintrust
+description: Configure Langflow to send tracing data to Braintrust for debugging and analyzing flow executions.
+sidebar_position: 1
---As per coding guidelines: "Markdown files must include YAML frontmatter with title, description, and sidebar_position".
📝 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: Braintrust | |
| slug: /integrations-braintrust | |
| --- | |
| --- | |
| title: Braintrust | |
| slug: /integrations-braintrust | |
| description: Configure Langflow to send tracing data to Braintrust for debugging and analyzing flow executions. | |
| sidebar_position: 1 | |
| --- |
🤖 Prompt for AI Agents
In `@docs/docs/Develop/integrations-braintrust.mdx` around lines 1 - 4, The MDX
frontmatter is missing required fields; update the YAML frontmatter for the
Braintrust doc by adding the missing `description` and `sidebar_position` keys
alongside the existing `title` (e.g., set a short descriptive string for
`description` and an integer for `sidebar_position`) so the frontmatter contains
`title`, `description`, and `sidebar_position`.
| ```bash | ||
| export BRAINTRUST_API_KEY=YOUR_API_KEY | ||
| ``` |
There was a problem hiding this comment.
Code blocks are missing the title attribute.
Per coding guidelines, code blocks must include a title attribute and specify the language (e.g., ```bash title="Terminal"). All bash code blocks in this file are missing title.
As per coding guidelines: "Code blocks must include a title attribute and specify the language".
Also applies to: 38-40, 52-58, 63-65, 75-77
🤖 Prompt for AI Agents
In `@docs/docs/Develop/integrations-braintrust.mdx` around lines 31 - 33, Several
bash code blocks (instances of the triple-backtick fences that start with
"```bash") are missing the required title attribute; update each code fence
(e.g., the ones currently written as "```bash") to include a title like
title="Terminal" (so they read e.g. ```bash title="Terminal") or another
descriptive title per block; ensure you change every bash block in the file
(including the blocks noted around the earlier examples) so each specifies both
the language and a title attribute.
| def __init__( | ||
| self, | ||
| trace_name: str, | ||
| trace_type: str, | ||
| project_name: str, | ||
| trace_id: UUID, | ||
| user_id: str | None = None, | ||
| session_id: str | None = None, | ||
| ) -> None: | ||
| self.trace_name = trace_name | ||
| self.trace_type = trace_type | ||
| self.trace_id = trace_id | ||
| self.user_id = user_id | ||
| self.session_id = session_id | ||
| self.flow_id = trace_name.split(" - ")[-1] | ||
| self.spans: dict[str, Any] = {} | ||
|
|
||
| config = self._get_config() | ||
| self._ready: bool = self._setup_braintrust(config, project_name) if config else False |
There was a problem hiding this comment.
Constructor does not accept user_id / session_id from the service layer.
The __init__ signature accepts user_id and session_id, and stores them in metadata, but _initialize_braintrust_tracer in service.py (line 233) does not pass them. Compare with langfuse and opik tracers which do. This means user_id and session_id will always be None in the root span metadata.
🤖 Prompt for AI Agents
In `@src/backend/base/langflow/services/tracing/braintrust.py` around lines 50 -
68, The Braintrust tracer constructor (__init__ in braintrust.py) accepts
user_id and session_id but _initialize_braintrust_tracer in service.py does not
forward them, so root span metadata always has None; update
_initialize_braintrust_tracer to accept (or extract) the service-layer user_id
and session_id and pass them into the Braintrust tracer constructor when
instantiating the tracer (same pattern used by langfuse/opik tracers) so the
root span metadata includes the correct user_id and session_id.
| def _initialize_braintrust_tracer(self, trace_context: TraceContext) -> None: | ||
| if self.deactivated: | ||
| return | ||
| braintrust_tracer = _get_braintrust_tracer() | ||
| trace_context.tracers["braintrust"] = braintrust_tracer( | ||
| trace_name=trace_context.run_name, | ||
| trace_type="chain", | ||
| project_name=trace_context.project_name, | ||
| trace_id=trace_context.run_id, | ||
| ) |
There was a problem hiding this comment.
user_id and session_id are not forwarded to the Braintrust tracer.
Other tracers that support user/session context (e.g., langfuse at line 188–189, opik at line 212–213, traceloop at line 225–226) pass user_id and session_id from trace_context. The Braintrust tracer's __init__ accepts these parameters and includes them in root span metadata, but they will always be None because they aren't passed here.
Proposed fix
braintrust_tracer = _get_braintrust_tracer()
trace_context.tracers["braintrust"] = braintrust_tracer(
trace_name=trace_context.run_name,
trace_type="chain",
project_name=trace_context.project_name,
trace_id=trace_context.run_id,
+ user_id=trace_context.user_id,
+ session_id=trace_context.session_id,
)🤖 Prompt for AI Agents
In `@src/backend/base/langflow/services/tracing/service.py` around lines 229 -
238, The Braintrust tracer is not receiving user_id and session_id from the
TraceContext, so root span metadata will always be None; update
_initialize_braintrust_tracer to pass trace_context.user_id and
trace_context.session_id into the braintrust_tracer call (alongside trace_name,
trace_type, project_name, trace_id) so the tracer's constructor receives those
values (TraceContext and braintrust_tracer are the relevant symbols to update).
… component (langflow-ai#11626) * feat: add smart column ordering and clean output toggle to Split Text component Add smart_column_order() method to DataFrame that prioritizes content columns (text, content, output, etc.) and de-prioritizes system metadata columns (timestamp, sender, session_id, etc.). Add Clean Output boolean input to Split Text component that strips metadata columns by default. * fix: update code_hash for Knowledge Ingestion and Vector Store RAG components * test: update split text tests for clean_output toggle * [autofix.ci] apply automated fixes * fix: change default value of clean_output toggle to False in Split Text component * [autofix.ci] apply automated fixes (attempt 2/3) * fix: update code_hash for Knowledge Ingestion and Vector Store RAG components * fix: add clean_output option to Knowledge Ingestion and Vector Store RAG components * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes (attempt 3/3) * [autofix.ci] apply automated fixes * fix: Add missing get_current_active_user_mcp to lfx AuthService The base class declares get_current_active_user_mcp as abstract but the default lfx AuthService did not implement it, causing instantiation to fail in tests. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* Fixing 404 error response. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes (attempt 3/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…en (langflow-ai#11606) * fix: add public config endpoint to fix shareable playground grey screen The shareable playground feature was showing a grey/blank screen when accessed by unauthenticated users with LANGFLOW_AUTO_LOGIN=False. This was caused by the frontend calling the protected /api/v1/config endpoint which requires authentication, resulting in 403 errors. This fix adds a new /api/v1/public_config endpoint that returns a subset of configuration values safe for public access (max_file_size_upload, event_delivery, voice_mode_available, frontend_timeout). The frontend now conditionally uses this public endpoint when in the playground page context. Changes: - Add PublicConfigResponse schema in backend - Add /api/v1/public_config endpoint (no auth required) - Add useGetPublicConfig hook in frontend - Update PlaygroundPage to use public config - Update InputWrapper components to conditionally use public/auth config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * refactor: extract usePlaygroundConfig hook to reduce code duplication Created a custom hook that encapsulates the logic for choosing between authenticated config (useGetConfig) and public config (useGetPublicConfig) based on whether we're on a playground page. - Added src/frontend/src/hooks/use-playground-config.ts - Added unit tests with 8 test cases covering all scenarios - Updated both input-wrapper.tsx files to use the new hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * test: add unit tests for public_config endpoint and remove unused code - Added 4 test cases for /api/v1/public_config endpoint: - Verifies endpoint is accessible without authentication - Verifies response contains expected fields - Verifies sensitive fields are not exposed - Verifies response field types are correct - Removed unused usePlaygroundConfig import and config variable from playgroundComponent's input-wrapper.tsx * test: add 500 error test for public_config endpoint * fix: address ruff linting error in 500 error test * refactor: merge /public_config into /config endpoint with auth-based response - Add get_optional_user dependency that returns User | None - Modify /config endpoint to return ConfigResponse for authenticated users, PublicConfigResponse for unauthenticated users - Remove separate /public_config endpoint - Update frontend to use single useGetConfig hook with type guards - Delete use-get-public-config.ts (no longer needed) - Simplify usePlaygroundConfig hook - Update tests for new behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: use BaseConfig pattern for config type hierarchy Introduce BaseConfig interface as the foundation, with PublicConfigResponse as a type alias and ConfigResponse extending the base. This provides clearer semantic "is-a" relationships in the type hierarchy. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * fix: remove unused playgroundPage prop from playgroundComponent InputWrapper The playgroundPage prop was declared and passed through the component chain but never actually used in InputWrapper. This removes the dead code from: - InputWrapperProps interface - InputWrapper destructuring - ChatInput component - ChatInputType type definition - flow-page-sliding-container ChatInput call Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve Ruff errors in auth/utils.py - Fix get_current_user_for_sse to delegate to _auth_service() - Fix get_optional_user to use _auth_service().get_current_user() instead of undefined get_current_user_by_jwt - Fix get_webhook_user to delegate to _auth_service().get_webhook_user() - Remove duplicate get_current_active_user and get_current_active_superuser definitions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: remove unnecessary usePlaygroundConfig hook wrapper - Remove usePlaygroundConfig hook that was just a thin wrapper around useGetConfig - Update input-wrapper.tsx to use useGetConfig directly - Delete associated test file - Improves code maintainability by reducing unnecessary abstraction * feat: add type discriminator to config response schemas - Add type: Literal["public"] = "public" to PublicConfigResponse - Add type: Literal["full"] = "full" to ConfigResponse - Update frontend isFullConfig type guard to use config.type === "full" This makes the discriminator explicit and robust, rather than relying on checking for the presence of specific fields like "auto_saving". Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * fix: update test to mock get_optional_user dependency The test was failing because the /config endpoint now has get_optional_user as a dependency. When testing error handling for get_settings_service(), the dependency chain was interfering with the patch, causing FastAPI to return 422 instead of 500. The fix mocks get_optional_user to return None directly, bypassing its dependency chain so we can properly test the error handling in the endpoint body. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * fix: remove explicit return None to satisfy RET501 lint rule Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * fix: use FastAPI dependency_overrides for proper dependency mocking The previous monkeypatch approach didn't work because FastAPI's Depends() captures the function reference at import time. Using app.dependency_overrides properly overrides the dependency at runtime. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: remove explicit return None to satisfy RET501 lint rule Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add type discriminator field verification to config tests Add assertions to verify that: - Unauthenticated /config returns type='public' - Authenticated /config returns type='full' This improves test coverage for the new type discriminator field. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use correct FastAPI dependency syntax in get_optional_user Changed db parameter from Annotated[AsyncSession, Depends(...)] to AsyncSession = Depends(...) to match the working get_current_user pattern. This fixes 422 errors in tests caused by FastAPI not properly resolving the Annotated syntax for async generator dependencies. * schemas updated * fix: add type discriminator to test config mocks Tests that mock /api/v1/config need to include type: "full" so the frontend's isFullConfig() check passes and applies the mocked settings like auto_saving, webhook_polling_interval, and webhook_auth_enable. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Himavarsha <40851462+HimavarshaVS@users.noreply.github.com>
…n_chain_stream (langflow-ai#11362) * fix: improve condition check for streaming token callback in handle_on_chain_stream * [autofix.ci] apply automated fixes * bug: fixed test for streaming skip empty chunks --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Summary
Adds native Braintrust observability support to Langflow, following the same
BaseTracerpattern used by existing monitoring integrations (Langfuse, LangSmith, Opik, Arize Phoenix, LangWatch, Traceloop).src/backend/base/langflow/services/tracing/braintrust.pyimplementingBaseTracer_get_braintrust_tracer()and_initialize_braintrust_tracer()toservice.pydocs/docs/Develop/integrations-braintrust.mdxpage with setup instructionsdocs/sidebars.jsHow it works
The integration activates automatically when the
BRAINTRUST_API_KEYenvironment variable is set (same pattern as all other monitoring integrations). It provides:get_langchain_callback()that captures LLM calls with token metrics, time-to-first-token, tool usage, retriever queries, and chain execution detailsDependencies
braintrust-- the only required dependency. The tracer includes an inline LangChain callback handler built entirely on thebraintrustSDK primitives (start_span,log,end), so no additional packages likebraintrust-langchainare needed.Environment variables
BRAINTRUST_API_KEYBRAINTRUST_API_URLhttps://api.braintrust.devBRAINTRUST_PROJECTTest plan
BRAINTRUST_API_KEY, start Langflow, run a flow, verify traces appear in Braintrust dashboardBRAINTRUST_API_KEYis not set (no errors, no impact)Summary by CodeRabbit
New Features
Documentation