Framework Comparison

adk-fluent produces native ADK objects — not a wrapper, not a new runtime, not a competing framework. Every .build() returns the same LlmAgent, SequentialAgent, or ParallelAgent you’d create by hand. The difference is how you get there.

The key differentiator

LangGraph, CrewAI, and AutoGen all create their own runtime objects. adk-fluent creates Google ADK objects. This means adk-fluent agents work with adk web, adk run, adk deploy, Agent Engine, and every ADK integration — with zero adaptation layer.

        flowchart LR
    subgraph others["Other frameworks"]
        LG["LangGraph"] --> LGR["LangGraph<br>Runnable"]
        CA["CrewAI"] --> CAR["CrewOutput"]
    end
    subgraph fluent["adk-fluent"]
        AF["Agent('name')<br>.build()"] --> ADK["Native ADK<br>LlmAgent"]
    end
    ADK --> D1["adk web"]
    ADK --> D2["adk run"]
    ADK --> D3["adk deploy"]
    ADK --> D4["Agent Engine"]

    style others fill:#fef2f2,stroke:#e94560,color:#1A1A1A
    style fluent fill:#ecfdf5,stroke:#10b981,color:#1A1A1A
    

Below, three real patterns are implemented in all four frameworks. Count the lines, but more importantly, count the concepts you need to learn.

Feature Matrix

Feature

LangGraph

CrewAI

Native ADK

adk-fluent

Lines per agent

15-25

10-15

8-15

1-3

Pipeline composition

StateGraph + edges

Crew sequential

SequentialAgent

>> operator

Parallel execution

Fan-out nodes + edges

Crew parallel

ParallelAgent

| operator

Conditional routing

conditional_edges

N/A (LLM decides)

Custom BaseAgent

Route() builder

Loops

Conditional back-edges

N/A

LoopAgent

* N or * until()

Typed output

Pydantic via output_parser

Pydantic

output_schema

@ Schema

Built-in testing

No

No

No

.mock(), .test()

IDE autocomplete

Partial

Partial

Yes

Yes (typed stubs)

Visualization

LangGraph Studio

No

No

.explain(), Mermaid

Streaming

Yes

No

Yes

.stream()

State management

TypedDict

Shared memory

Session state

S.* module

Result type

LangGraph Runnable

CrewOutput

ADK Agent

ADK Agent (native)

Pattern 1: Document Processing Pipeline

A contract review system: extract key terms, analyze legal risks, produce an executive summary.

LangGraph

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class ContractState(TypedDict):
    document: str
    terms: str
    risks: str
    summary: str

def extract_terms(state: ContractState) -> dict:
    # Call LLM to extract key terms
    result = llm.invoke(
        "Extract key terms from the contract: parties, dates, payment terms, "
        "termination clauses.\n\n" + state["document"]
    )
    return {"terms": result.content}

def analyze_risks(state: ContractState) -> dict:
    result = llm.invoke(
        "Analyze these terms for legal risks. Flag unusual clauses, "
        "missing protections, liability concerns.\n\n" + state["terms"]
    )
    return {"risks": result.content}

def summarize(state: ContractState) -> dict:
    result = llm.invoke(
        "Produce a one-page executive summary combining the extracted "
        "terms and risk analysis. Use clear, non-legal language.\n\n"
        f"Terms: {state['terms']}\nRisks: {state['risks']}"
    )
    return {"summary": result.content}

graph = StateGraph(ContractState)
graph.add_node("extract", extract_terms)
graph.add_node("analyze", analyze_risks)
graph.add_node("summarize", summarize)
graph.add_edge(START, "extract")
graph.add_edge("extract", "analyze")
graph.add_edge("analyze", "summarize")
graph.add_edge("summarize", END)

app = graph.compile()
# ~35 lines of setup

CrewAI

from crewai import Agent, Task, Crew

extractor = Agent(
    role="Contract Analyst",
    goal="Extract key terms from contracts",
    backstory="You are a legal document analyst specializing in contract review.",
    llm="gemini/gemini-2.5-flash",
)

analyst = Agent(
    role="Risk Assessor",
    goal="Identify legal risks in contract terms",
    backstory="You are a legal risk specialist with 20 years experience.",
    llm="gemini/gemini-2.5-flash",
)

summarizer = Agent(
    role="Executive Writer",
    goal="Produce clear executive summaries",
    backstory="You translate legal analysis into plain language.",
    llm="gemini/gemini-2.5-flash",
)

tasks = [
    Task(description="Extract key terms: parties, dates, payment, termination.", agent=extractor),
    Task(description="Analyze extracted terms for legal risks.", agent=analyst),
    Task(description="Write a one-page executive summary.", agent=summarizer),
]

crew = Crew(agents=[extractor, analyst, summarizer], tasks=tasks, process="sequential")
result = crew.kickoff()
# ~30 lines, but each agent needs role + goal + backstory

Native ADK

from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.sequential_agent import SequentialAgent

extractor = LlmAgent(
    name="extractor",
    model="gemini-2.5-flash",
    instruction=(
        "Extract key terms from the contract: parties involved, "
        "effective dates, payment terms, and termination clauses."
    ),
)
analyst = LlmAgent(
    name="risk_analyst",
    model="gemini-2.5-flash",
    instruction=(
        "Analyze the extracted terms for legal risks. Flag any "
        "unusual clauses, missing protections, or liability concerns."
    ),
)
summarizer = LlmAgent(
    name="summarizer",
    model="gemini-2.5-flash",
    instruction=(
        "Produce a one-page executive summary combining the extracted "
        "terms and risk analysis. Use clear, non-legal language."
    ),
)
pipeline = SequentialAgent(
    name="contract_review",
    description="Extract, analyze, and summarize contracts",
    sub_agents=[extractor, analyst, summarizer],
)
# ~20 lines -- clean but verbose for the topology complexity

adk-fluent

from adk_fluent import Agent

extractor = Agent("extractor", "gemini-2.5-flash").instruct(
    "Extract key terms: parties, dates, payment terms, termination clauses."
)
analyst = Agent("risk_analyst", "gemini-2.5-flash").instruct(
    "Analyze extracted terms for legal risks."
)
summarizer = Agent("summarizer", "gemini-2.5-flash").instruct(
    "Produce a one-page executive summary in plain language."
)

pipeline = extractor >> analyst >> summarizer
# 4 lines of agent definition + 1 line of composition
# Result: native SequentialAgent -- same as hand-built

Line count: LangGraph ~35 | CrewAI ~30 | Native ADK ~20 | adk-fluent ~5

See cookbook example

Pattern 2: Multi-Source Research with Quality Loop

Decompose a query, search 3 sources in parallel, synthesize findings, review quality in a loop, and produce a typed report.

LangGraph

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class ResearchState(TypedDict):
    query: str
    web_results: str
    academic_results: str
    news_results: str
    synthesis: str
    quality_score: float

def analyze_query(state): ...
def search_web(state): ...
def search_academic(state): ...
def search_news(state): ...
def synthesize(state): ...
def review_quality(state): ...
def revise(state): ...

def should_continue(state):
    return "revise" if state["quality_score"] < 0.85 else "report"

graph = StateGraph(ResearchState)
graph.add_node("analyze", analyze_query)
graph.add_node("search_web", search_web)
graph.add_node("search_academic", search_academic)
graph.add_node("search_news", search_news)
graph.add_node("synthesize", synthesize)
graph.add_node("review", review_quality)
graph.add_node("revise", revise)
graph.add_node("report", write_report)
graph.add_edge(START, "analyze")
graph.add_edge("analyze", "search_web")
graph.add_edge("analyze", "search_academic")
graph.add_edge("analyze", "search_news")
graph.add_edge("search_web", "synthesize")
graph.add_edge("search_academic", "synthesize")
graph.add_edge("search_news", "synthesize")
graph.add_edge("synthesize", "review")
graph.add_conditional_edges("review", should_continue)
graph.add_edge("revise", "review")
graph.add_edge("report", END)
app = graph.compile()
# ~55 lines of graph wiring -- the topology IS the code

adk-fluent

from adk_fluent import Agent, S, C

analyzer = Agent("analyzer", MODEL).instruct("Decompose the query into sub-questions.").writes("plan")
web = Agent("web", MODEL).instruct("Search web sources.").context(C.from_state("plan")).writes("web_results")
papers = Agent("papers", MODEL).instruct("Search academic papers.").context(C.from_state("plan")).writes("academic_results")
news = Agent("news", MODEL).instruct("Search recent news.").context(C.from_state("plan")).writes("news_results")
synthesizer = Agent("synth", MODEL).instruct("Synthesize findings.").writes("synthesis")
reviewer = Agent("reviewer", MODEL).instruct("Score quality 0-1.").writes("quality_score")
reviser = Agent("reviser", MODEL).instruct("Revise based on feedback.").writes("synthesis")
writer = Agent("writer", MODEL).instruct("Write final report.") @ ResearchReport

research = (
    analyzer
    >> (web | papers | news)
    >> synthesizer
    >> (reviewer >> reviser) * until(lambda s: float(s.get("quality_score", 0)) >= 0.85)
    >> writer
)
# 9 lines of agents + 1 expression for the full topology
# Parallel search, quality loop, typed output -- all in one expression

Line count: LangGraph ~55 | Native ADK ~45 | adk-fluent ~10

See cookbook example

Pattern 3: Customer Support Triage

Classify customer intent and route to the right specialist team.

LangGraph

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class SupportState(TypedDict):
    message: str
    intent: str
    response: str

def classify(state):
    result = llm.invoke("Classify intent: billing, technical, or general.\n\n" + state["message"])
    return {"intent": result.content.strip()}

def handle_billing(state):
    result = llm.invoke("You are a billing specialist. Help with: " + state["message"])
    return {"response": result.content}

def handle_technical(state):
    result = llm.invoke("You are tech support. Diagnose: " + state["message"])
    return {"response": result.content}

def handle_general(state):
    result = llm.invoke("You are general support. Help with: " + state["message"])
    return {"response": result.content}

def route_intent(state):
    return state["intent"]

graph = StateGraph(SupportState)
graph.add_node("classify", classify)
graph.add_node("billing", handle_billing)
graph.add_node("technical", handle_technical)
graph.add_node("general", handle_general)
graph.add_edge(START, "classify")
graph.add_conditional_edges(
    "classify", route_intent,
    {"billing": "billing", "technical": "technical", "general": "general"},
)
graph.add_edge("billing", END)
graph.add_edge("technical", END)
graph.add_edge("general", END)
app = graph.compile()
# ~40 lines -- routing requires conditional_edges + routing function

adk-fluent

from adk_fluent import Agent, S, C
from adk_fluent._routing import Route

classifier = Agent("classifier", MODEL).instruct(
    "Classify intent: billing, technical, or general.\nMessage: {message}"
).context(C.none()).writes("intent")

billing = Agent("billing", MODEL).instruct("Help with billing issues.")
technical = Agent("technical", MODEL).instruct("Diagnose technical issues.")
general = Agent("general", MODEL).instruct("Handle general inquiries.")

support = (
    S.capture("message")
    >> classifier
    >> Route("intent")
        .eq("billing", billing)
        .eq("technical", technical)
        .otherwise(general)
)
# 8 lines of agents + 1 routing expression
# Route() replaces conditional_edges + routing function

Line count: LangGraph ~40 | Native ADK ~35 | adk-fluent ~8

See cookbook example

When to Use Each Framework

Use LangGraph when

  • You need fine-grained control over graph execution and state transitions

  • Your workflow has complex conditional paths that don’t map to sequential/parallel/routing patterns

  • You want LangGraph Studio for visual debugging

  • You’re already invested in the LangChain ecosystem

Use CrewAI when

  • You want role-based agent personas with rich backstories

  • Your workflow is primarily sequential with LLM-driven task delegation

  • You prefer a higher-level abstraction over graph construction

Use native ADK when

  • You need custom BaseAgent subclasses with _run_async_impl

  • You’re building framework-level tooling around ADK

  • You need direct access to ADK internals not exposed through builders

Use adk-fluent when

  • You want the shortest path from idea to working agent topology

  • You need to compose pipelines, fan-out, loops, and routing declaratively

  • You want .mock() and .test() for deterministic testing without API calls

  • You want to produce native ADK objects compatible with adk web, adk run, and adk deploy

  • You want IDE autocomplete and type checking during development