Skip to main content

LangGraph

LangGraph agents become a lot more interesting when each node can use the best provider for its job โ€” without rewriting the graph, juggling auth, or maintaining N provider SDKs. Ferro Labs AI Gateway gives every node one URL; only the model name changes.

Three providers. Three trace_ids. One gateway URL. The agent code imports langchain_ferrolabsai and langgraph โ€” nothing else.

Installโ€‹

pip install langchain-ferrolabsai "langgraph>=0.2.0,<0.3.0"

Multi-provider agent in 30 linesโ€‹

import os
from typing import TypedDict
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_ferrolabsai import FerroChatModel
from langgraph.graph import END, StateGraph

class State(TypedDict):
request: str
plan: str
code: str
summary: str

def _chat(model: str) -> FerroChatModel:
return FerroChatModel(
model=model,
base_url=os.environ["FERRO_BASE_URL"],
api_key=os.environ["FERRO_API_KEY"],
)

PLANNER, CODER, SUMMARIZER = _chat("gpt-4o"), _chat("claude-3-5-sonnet-20241022"), _chat("gemini-1.5-flash")

def plan(s): r = PLANNER.invoke([SystemMessage(content="Plan in 3-5 steps."), HumanMessage(content=s["request"])]); return {**s, "plan": r.content}
def code(s): r = CODER.invoke([SystemMessage(content="Implement the plan as Python."), HumanMessage(content=s["plan"])]); return {**s, "code": r.content}
def summarize(s): r = SUMMARIZER.invoke([SystemMessage(content="One sentence."), HumanMessage(content=s["code"])]); return {**s, "summary": r.content}

g = StateGraph(State)
g.add_node("planner", plan); g.add_node("coder", code); g.add_node("summarizer", summarize)
g.set_entry_point("planner"); g.add_edge("planner", "coder"); g.add_edge("coder", "summarizer"); g.add_edge("summarizer", END)
app = g.compile()
print(app.invoke({"request": "Build a UTC-now CLI", "plan": "", "code": "", "summary": ""})["summary"])

That's the whole pattern โ€” one FerroChatModel(model=...) per node, all pointed at the same FERRO_BASE_URL.

Surface trace_id per stepโ€‹

Every FerroChatModel response carries the gateway's trace_id on response_metadata. Collect it per-step to correlate the agent's flow with whatever observability backend you've wired into the gateway:

def plan(state):
response = PLANNER.invoke([HumanMessage(content=state["request"])])
print(f"[planner] trace_id={response.response_metadata['trace_id']}")
return {**state, "plan": response.content}

Pair this with the LangSmith bridge (or any other plugin) on the gateway side, and each of your nodes shows up as a separate run in the LLMOps backend of your choice โ€” without the agent importing any LLMOps SDK.

Verifyโ€‹

export FERRO_BASE_URL=http://localhost:8080
export FERRO_API_KEY=sk-ferro-...
python agent.py

Expected output (abridged):

Routing through Ferro gateway:
[planner ยท openai ยท trace_id=abc-123]
[coder ยท anthropic ยท trace_id=def-456]
[summarizer ยท google ยท trace_id=ghi-789]

Three different providers, three different trace_ids โ€” proof that one LangGraph definition fanned out to three best-in-class models.

Runnable exampleโ€‹

ai-gateway-cookbook/python/02-langgraph-multi-provider-agent โ€” the full Dockerized version of the snippet above.

git clone https://github.com/ferro-labs/ai-gateway-cookbook
cd ai-gateway-cookbook/python/02-langgraph-multi-provider-agent
cp .env.example .env
make run

Why this mattersโ€‹

Most LangGraph examples either pick one provider and stay there (limiting agent quality) or paper over multiple providers with hand-rolled adapters (cost: every node now knows about auth, retries, billing). Ferro pushes that complexity into the gateway. The agent author writes a graph; the gateway routes, retries, tracks cost, propagates traces. Swapping model="gpt-4o" for model="claude-3-5-sonnet-20241022" is the only change required to A/B a different provider on a node.

See alsoโ€‹

  • LangChain (Python) โ€” full FerroChatModel / FerroEmbeddings / FerroLLM reference
  • LangSmith โ€” turn the per-node trace_ids into LangSmith runs
  • Routing policies โ€” what the gateway does behind the scenes
  • Use cases โ€” more multi-provider patterns