Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/deepagents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Deep Agents + Braintrust

Deep Agents is built on LangChain and LangGraph, so Braintrust traces it through the existing LangChain integration. There is no separate `setup_deepagents()` step.

## Run

```bash
# Loads BRAINTRUST_API_KEY and OPENAI_API_KEY from ../../.env automatically.
uv sync
uv run python example.py
```

The trace includes the Deep Agents/LangGraph root span, model spans, tool spans, and Deep Agents metadata such as `ls_integration="deepagents"` and `lc_agent_name`.
45 changes: 45 additions & 0 deletions examples/deepagents/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python
"""Deep Agents traced via Braintrust's LangChain auto-instrumentation.

Deep Agents is built on LangChain/LangGraph, so `braintrust.auto_instrument()`
installs the LangChain callback handler that traces Deep Agents runs.
"""

import importlib
from pathlib import Path

import braintrust
from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI


ROOT = Path(__file__).resolve().parents[2]
load_dotenv(ROOT / ".env")

braintrust.auto_instrument()
braintrust.init_logger(project="example-deepagents")


@tool
def get_weather(city: str) -> str:
"""Return the current demo weather for a city."""
return f"The weather in {city} is sunny and 72°F."


deepagents = importlib.import_module("deepagents")
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = deepagents.create_deep_agent(model=model, tools=[get_weather], name="example-deep-agent")

result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "Use the get_weather tool to answer: What is the weather in Paris?",
}
]
}
)
print(result["messages"][-1].content)
braintrust.flush()
14 changes: 14 additions & 0 deletions examples/deepagents/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "braintrust-deepagents-example"
version = "0.1.0"
description = "Deep Agents traced through Braintrust's LangChain integration"
requires-python = ">=3.11"
dependencies = [
"braintrust",
"deepagents",
"langchain-openai",
"python-dotenv",
]

[tool.uv.sources]
braintrust = { path = "../../py", editable = true }
14 changes: 14 additions & 0 deletions py/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,20 @@ def test_langchain(session, version):
_run_tests(session, f"{INTEGRATION_DIR}/langchain/test_anthropic.py", version=version)


DEEPAGENTS_VERSIONS = _get_matrix_versions("deepagents")


@nox.session()
@nox.parametrize("version", DEEPAGENTS_VERSIONS, ids=DEEPAGENTS_VERSIONS)
def test_deepagents(session, version):
if sys.version_info < (3, 11):
session.skip("Deep Agents requires Python 3.11+")
_install_test_deps(session)
_install_group_locked(session, "test-deepagents")
_install_matrix_dep(session, "deepagents", version)
_run_tests(session, f"{INTEGRATION_DIR}/langchain/test_deepagents.py", version=version)


LLAMAINDEX_VERSIONS = _get_matrix_versions("llama-index-core")


Expand Down
12 changes: 11 additions & 1 deletion py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ test-langchain = [
"langgraph==1.1.6",
]

test-deepagents = [
{include-group = "test"},
"langchain-openai==1.1.14",
]

test-livekit-agents = [
{include-group = "test"},
"livekit-plugins-openai",
Expand Down Expand Up @@ -286,6 +291,7 @@ conflicts = [
{group = "test-agentscope"},
{group = "test-strands"},
{group = "test-langchain"},
{group = "test-deepagents"},
{group = "test-livekit-agents"},
{group = "lint"},
],
Expand Down Expand Up @@ -389,6 +395,10 @@ latest = "google-genai==2.10.0"
latest = "dspy==3.2.1"
"2.6.0" = "dspy==2.6.0"

[tool.braintrust.matrix.deepagents]
latest = "deepagents==0.6.12"
"0.6.0" = "deepagents==0.6.0"

[tool.braintrust.matrix.crewai]
# 1.13.0 is the first release with the full causal-id surface (event_id /
# parent_event_id / started_event_id on BaseEvent) plus the ``usage`` field on
Expand Down Expand Up @@ -477,7 +487,7 @@ dspy = ["dspy"]
google_genai = ["google-genai"]
huggingface_hub = ["huggingface-hub"]
instructor = ["instructor"]
langchain = ["langchain-core"]
langchain = ["langchain-core", "deepagents"]
litellm = ["litellm"]
livekit_agents = ["livekit-agents"]
llamaindex = ["llama-index-core"]
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

104 changes: 104 additions & 0 deletions py/src/braintrust/integrations/langchain/test_deepagents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import importlib

import pytest
from braintrust import logger
from braintrust.integrations.langchain import BraintrustCallbackHandler, set_global_handler, setup_langchain
from braintrust.integrations.langchain.helpers import find_spans_by_attributes
from braintrust.test_helpers import init_test_logger


PROJECT_NAME = "deepagents-py"


@pytest.fixture
def logger_memory_logger():
test_logger = init_test_logger(PROJECT_NAME)
with logger._internal_with_memory_background_logger() as bgl:
yield (test_logger, bgl)


@pytest.fixture(autouse=True)
def clear_langchain_handler():
context = importlib.import_module("braintrust.integrations.langchain.context")
context.clear_global_handler()
yield
context.clear_global_handler()


def _chat_model():
langchain_openai = importlib.import_module("langchain_openai")
return langchain_openai.ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
max_completion_tokens=20,
)


def _create_deep_agent(*, name="bt-deep-agent"):
deepagents = importlib.import_module("deepagents")
return deepagents.create_deep_agent(
model=_chat_model(),
tools=[],
name=name,
)


def _invoke_agent(agent, content="Reply with exactly the word blue."):
return agent.invoke({"messages": [{"role": "user", "content": content}]})


def _assert_deepagents_spans(spans, root_name):
assert spans

root_spans = find_spans_by_attributes(spans, name=root_name, type="task")
llm_spans = find_spans_by_attributes(spans, name="ChatOpenAI", type="llm")
assert len(root_spans) == 1
assert len(llm_spans) >= 1

root_span = root_spans[0]
deepagents_metadata = root_span["metadata"]["metadata"]
assert deepagents_metadata["ls_integration"] == "deepagents"
assert deepagents_metadata["lc_agent_name"] == root_name
if "lc_versions" in deepagents_metadata:
assert "deepagents" in deepagents_metadata["lc_versions"]
assert "messages" in root_span["output"]


@pytest.mark.vcr
def test_setup_langchain_traces_deepagents_run(logger_memory_logger):
test_logger, memory_logger = logger_memory_logger
assert not memory_logger.pop()

assert setup_langchain(project_name=PROJECT_NAME)
set_global_handler(BraintrustCallbackHandler(logger=test_logger))

agent = _create_deep_agent(name="bt-deep-agent")
_invoke_agent(agent)

_assert_deepagents_spans(memory_logger.pop(), "bt-deep-agent")


@pytest.mark.vcr
def test_langchain_callback_traces_prebuilt_deep_agent(logger_memory_logger):
test_logger, memory_logger = logger_memory_logger
assert not memory_logger.pop()

stdout = importlib.import_module("langchain_core.tracers.stdout")
existing_callback = stdout.ConsoleCallbackHandler()
handler = BraintrustCallbackHandler(logger=test_logger)

agent = _create_deep_agent(name="manual-agent").with_config(
{
"callbacks": [existing_callback, handler],
"metadata": {"user_metadata": "kept"},
}
)
assert existing_callback in agent.config["callbacks"]
assert handler in agent.config["callbacks"]

_invoke_agent(agent, content="Reply with exactly the word green.")

spans = memory_logger.pop()
_assert_deepagents_spans(spans, "manual-agent")
root_spans = find_spans_by_attributes(spans, name="manual-agent", type="task")
assert root_spans[0]["metadata"]["metadata"]["user_metadata"] == "kept"
513 changes: 263 additions & 250 deletions py/uv.lock

Large diffs are not rendered by default.