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 |
|
Parallel execution |
Fan-out nodes + edges |
Crew parallel |
ParallelAgent |
|
Conditional routing |
conditional_edges |
N/A (LLM decides) |
Custom BaseAgent |
|
Loops |
Conditional back-edges |
N/A |
LoopAgent |
|
Typed output |
Pydantic via output_parser |
Pydantic |
output_schema |
|
Built-in testing |
No |
No |
No |
|
IDE autocomplete |
Partial |
Partial |
Yes |
Yes (typed stubs) |
Visualization |
LangGraph Studio |
No |
No |
|
Streaming |
Yes |
No |
Yes |
|
State management |
TypedDict |
Shared memory |
Session state |
|
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
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
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
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
BaseAgentsubclasses with_run_async_implYou’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 callsYou want to produce native ADK objects compatible with
adk web,adk run, andadk deployYou want IDE autocomplete and type checking during development