TL;DR:
- Agent frameworks add real overhead — latency, cost, and debugging complexity — that many use cases don’t justify
- If your task has a fixed, predictable structure, a direct chain of LLM calls with structured outputs will be faster, cheaper, and easier to maintain
- Reserve true agents for tasks that require genuine dynamic decision-making, tool selection, and multi-step planning you can’t anticipate in advance
The AI tooling ecosystem has a bias toward frameworks. Every month brings a new agent library promising to simplify the complexity of LLM-powered workflows. And while frameworks like LangGraph, CrewAI, and AutoGen genuinely solve hard problems, they can also be the wrong tool — one that adds complexity without delivering commensurate value.
The question worth asking before reaching for any agent framework is: does this task actually require an agent?
What Makes a Task “Agentic”
An agent earns its complexity by doing something a static pipeline cannot: choosing dynamically what to do next based on context it couldn’t have anticipated at design time. A genuine agent needs to:
- Select from multiple tools based on intermediate results
- Iterate on its own outputs when they don’t meet requirements
- Navigate state that changes between steps in unpredictable ways
- Handle branching logic that can’t be enumerated upfront
If your task doesn’t require all of these, you probably don’t need a full agent. You need a well-designed pipeline.
The Simpler Alternatives
Direct API Calls with Structured Outputs
For single-turn extraction, classification, or generation tasks, a single API call with a schema-constrained response is often all you need. Using Anthropic’s tool-use API or OpenAI’s structured outputs to enforce JSON output eliminates the parsing overhead and prompt engineering required to get consistent results from free-text responses.
import anthropic
import json
client = anthropic.Anthropic()
def classify_support_ticket(ticket_text: str) -> dict:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
tools=[{
"name": "classify_ticket",
"description": "Classify a support ticket",
"input_schema": {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "technical", "account", "other"]},
"urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
"summary": {"type": "string"}
},
"required": ["category", "urgency", "summary"]
}
}],
tool_choice={"type": "tool", "name": "classify_ticket"},
messages=[{"role": "user", "content": ticket_text}]
)
return response.content[0].input
This is deterministic, fast, and trivially testable. An agent framework adds nothing here.
Sequential Chains (the “Pipeline” Pattern)
When your task requires multiple steps — extract, then transform, then generate — but the steps are known in advance and don’t require dynamic branching, a sequential chain of API calls is the right architecture.
def process_document(raw_text: str) -> dict:
# Step 1: Extract entities
entities = extract_entities(raw_text)
# Step 2: Enrich with external data
enriched = fetch_context_for_entities(entities)
# Step 3: Generate summary
summary = generate_summary(raw_text, enriched)
return {"entities": entities, "summary": summary}
Each function is independently testable. The pipeline is deterministic. Debugging is simple — add a print() at any stage. No framework, no abstractions, no surprises.
Map-Reduce Patterns
Parallel processing of many items followed by aggregation is another task that looks agentic but isn’t. Run concurrent API calls across your dataset, then pass results to a final synthesis step. Python’s asyncio and httpx handle this cleanly without any orchestration layer.
When You Actually Need an Agent
Some tasks genuinely require dynamic orchestration:
Open-ended research: A task like “research this company and produce a due diligence report” requires deciding which sources to query, what to follow up on based on what you find, and how to handle gaps in information. The path from query to report can’t be enumerated in advance.
Code debugging and iteration: An agent that writes code, runs it, observes the error, decides whether to fix the code or change the approach, and iterates — this genuinely requires dynamic state management that a static pipeline can’t handle.
Complex multi-source data integration: When the schema of data you’ll encounter is unknown until runtime, an agent can decide on the fly how to parse and reconcile sources that weren’t anticipated at design time.
The Cost of Unnecessary Complexity
Every layer of abstraction in an agent framework has a cost:
Latency: Agents typically make multiple round-trips, including planning steps and tool calls that add seconds to each invocation. A direct chain of two API calls beats a framework-orchestrated agent on response time for most structured tasks.
Debugging surface: When a pipeline fails, you can print intermediate states and trace the error in minutes. When an agent fails, you’re tracing through an orchestration graph, checking memory state, and inspecting tool call sequences. This is fine when the complexity is necessary; it’s painful when it isn’t.
Prompt fragility: Agent frameworks often inject significant prompt context — system instructions, tool descriptions, memory summaries — that can interfere with your actual task prompts. Keeping these surfaces small reduces the surface area for unexpected model behaviour.
Cost: An agent that makes 10 LLM calls to complete a task you could do in 2 is 5x more expensive per invocation. At scale, this matters.
A Decision Framework
Before choosing your architecture:
- Is the structure known upfront? If you can draw the steps on a whiteboard before writing code, use a pipeline.
- Does the task require tool selection? If the LLM needs to choose which tools to call based on intermediate results, you’re looking at agentic behaviour.
- Is iteration required? If the output needs to be evaluated and refined in a loop, you may need an agent — but first consider whether a fixed retry loop handles your actual cases.
- What’s the error recovery story? Pipelines fail at known points and can be restarted cleanly. Agents can fail mid-run in complex state. Design accordingly.
The best agent architecture is often no agent at all — just a clear sequence of well-designed steps that does exactly what’s needed and nothing more.