Interop: connect foreign-framework agents
This is why Weave exists: take an agent someone already built (in LangChain, LangGraph, CrewAI, or plain Python) and connect it to your agent without rewriting it.
A foreign agent is wrapped into a Weave BaseAgent. Because it becomes an agent, it composes in Pipeline, Parallel, and any nesting exactly like a native one, and the connection protocol auto-calibrates the handoff (type-checks, injects transforms).
Duck-typed, zero new dependencies. These bridges call the foreign object's documented entrypoint (a LangChain Runnable's
ainvoke/invoke, a CrewAI crew'skickoff) rather than importing the framework. Weave's core stays dependency-free, and any object that speaks the interface plugs in, with no version pinning.
Any framework, really?
Yes, as long as you can call it from Python. There are two routes in:
- A dedicated bridge when the foreign object speaks a known interface:
from_langchain(LangChain/LangGraphRunnable) andfrom_crewai(CrewAIkickoff). One line, nothing to configure beyond the object itself. from_callablefor everything else: wrap any sync or async function, a bound method, an HTTP request, or an SDK call. This is the universal escape hatch, so a framework with its own bespoke API still plugs in.
from weave import from_callable, Pipeline
# A foreign framework with its own arbitrary API (no ainvoke, no kickoff):
class AcmeBot:
def run_task(self, prompt: str) -> str: ...
acme = from_callable(AcmeBot().run_task, name="acme")
out = await Pipeline([acme, my_weave_agent]).run("hello") # connected, no rewrite
Once wrapped, a foreign agent is a BaseAgent, so it composes in Pipeline, Parallel, and arbitrary nestings, and the connection protocol auto-calibrates each handoff. The only thing that cannot be auto-bridged is an object that exposes neither a recognizable entrypoint nor a callable you can hand to from_callable.
LangChain / LangGraph
from_langchain targets the Runnable protocol (ainvoke/invoke) that LangChain chains, chat models, agents, and compiled LangGraph graphs all implement.
from weave import from_langchain, Pipeline
# `chain` is any LangChain Runnable / LangGraph graph you already have.
theirs = from_langchain(chain, name="researcher")
# Connect it directly to your native Weave agent, no rewrite:
out = await Pipeline([theirs, my_agent]).run("a question")
The default extract pulls text out of common shapes: a str, a BaseMessage (.content), or an agent dict ({"output": ...}). Override it for custom shapes.
CrewAI
from_crewai targets kickoff / kickoff_async. CrewAI's kickoff(inputs=...) takes a dict, so map your Weave value into it with prepare.
from weave import from_crewai
theirs = from_crewai(my_crew, name="writers", prepare=lambda v: {"topic": v})
out = await theirs.run("electric cars")
The default extract returns a str or a CrewOutput.raw.
Anything else: from_callable
If a foreign agent isn't a LangChain/CrewAI object, wrap any sync or async value -> value function. This is the universal escape hatch:
from weave import from_callable
def persons_agent(text: str) -> str: # could call any SDK, HTTP API, etc.
return external_sdk.run(text)
theirs = from_callable(persons_agent, name="person_a")
Shaping the boundary
Every bridge accepts two optional callables:
| Hook | Signature | Purpose |
|---|---|---|
prepare | weave_value -> foreign_input | adapt the Weave value to what the foreign agent expects |
extract | foreign_output -> weave_value | pull a Weave-typed value back out |
Plus the standard port args: input, output (a DataType or PortSchema), name, and tags. Declare output=DataType.STRUCTURED_JSON when the foreign agent returns a dict you want to flow as structured data.
Extending to a new framework
Each framework is its own module under weave/interop/ that builds on the shared from_callable core. Adding one is a new file (its entrypoint name plus an extract default), never an edit to existing code (Open/Closed).