Workflow vs Agent Design¶
What You'll Learn¶
By the end of this lesson, you'll understand: - The fundamental difference between workflows and agents - When deterministic workflows outperform autonomous agents - When agents are the right choice - Hybrid architectures that combine both approaches - Production reliability considerations
Time to Complete: 35 minutes Difficulty: Advanced
Workflows vs Agents: The Core Difference¶
Workflow: A pre-defined sequence of steps where the developer controls the flow. The LLM is a component within a deterministic pipeline.
Agent: An autonomous system where the LLM decides what to do next. The developer defines tools and goals, but the LLM controls the flow.
WORKFLOW (Developer controls flow) AGENT (LLM controls flow)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────────────────────┐
│Step 1│→ │Step 2│→ │Step 3│ │ LLM decides next step│
│(LLM) │ │(Code)│ │(LLM) │ │ ↓ ↑ │
└──────┘ └──────┘ └──────┘ │ Execute Observe │
│ ↓ ↑ │
Fixed path, predictable │ Tool/Action → Result │
└──────────────────────┘
Dynamic path, flexible
When to Use Workflows¶
Workflows excel when the task is well-understood and the steps are predictable.
Example: Document Processing Pipeline¶
class DocumentProcessor:
"""A workflow: fixed steps, predictable execution."""
def __init__(self, llm_client):
self.client = llm_client
def process(self, document: str) -> dict:
# Step 1: Extract key information (LLM)
extracted = self._extract(document)
# Step 2: Validate format (code -- no LLM needed)
validated = self._validate(extracted)
# Step 3: Classify document type (LLM)
classification = self._classify(document)
# Step 4: Generate summary (LLM)
summary = self._summarize(document, classification)
# Step 5: Store results (code)
return {
"extracted_data": validated,
"classification": classification,
"summary": summary
}
def _extract(self, document: str) -> dict:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Extract the following fields from this document: title, date, author, key_points.
Document:
{document}
Return JSON."
}],
temperature=0
)
import json
return json.loads(response.choices[0].message.content)
def _validate(self, data: dict) -> dict:
"""Deterministic validation -- no LLM needed."""
required_fields = ["title", "date", "author", "key_points"]
for field in required_fields:
if field not in data:
data[field] = "MISSING"
return data
def _classify(self, document: str) -> str:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Classify this document as one of: REPORT, MEMO, PROPOSAL, OTHER.
{document[:1000]}"
}],
temperature=0
)
return response.choices[0].message.content.strip()
def _summarize(self, document: str, doc_type: str) -> str:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Summarize this {doc_type} in 3 bullet points:
{document}"
}],
temperature=0
)
return response.choices[0].message.content
Workflow Advantages¶
- Predictable: Same input always follows the same path
- Debuggable: Easy to identify which step failed
- Cost-controlled: Fixed number of LLM calls per execution
- Testable: Each step can be unit tested independently
- Fast: No decision overhead between steps
When to Use Agents¶
Agents excel when the task is open-ended or the steps cannot be predicted in advance.
Example: Research Agent¶
class ResearchAgent:
"""An agent: LLM decides what to do next."""
def __init__(self, llm_client, tools: dict):
self.client = llm_client
self.tools = tools
def research(self, question: str) -> str:
messages = [{
"role": "system",
"content": (
"You are a research agent. Use your tools to find information "
"and answer the question thoroughly. When you have enough "
"information, provide your final answer."
)
}, {
"role": "user",
"content": question
}]
for _ in range(10): # Max iterations
response = self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=self._format_tools()
)
message = response.choices[0].message
if not message.tool_calls:
return message.content # Agent is done
# Execute tools the agent chose
messages.append(message)
for call in message.tool_calls:
result = self.tools[call.function.name](call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result
})
return "Research could not be completed in the allowed steps."
Agent Advantages¶
- Flexible: Can handle novel, unpredictable tasks
- Adaptive: Adjusts approach based on intermediate results
- Capable: Can solve complex problems humans did not anticipate
- Composable: Add new tools without changing the core logic
The Decision Framework¶
Is the task predictable?
/ \
YES NO
| |
Are steps known in advance? Does it need tools?
/ \ / \
YES NO YES NO
| | | |
WORKFLOW HYBRID AGENT REACTIVE
(pipeline) (workflow + (autonomous (single
agent for tool use) LLM call)
unknowns)
| Factor | Workflow | Agent |
|---|---|---|
| Predictability | High | Low |
| Cost control | Easy | Hard (variable LLM calls) |
| Debugging | Simple (step-by-step) | Complex (non-deterministic) |
| Flexibility | Low (fixed path) | High (dynamic path) |
| Reliability | High | Medium (can get stuck) |
| Development speed | Fast for known tasks | Fast for novel tasks |
Hybrid Architecture¶
The best production systems often combine both patterns.
class HybridSystem:
"""Workflow for known steps, agent for uncertain ones."""
def __init__(self, llm_client, tools: dict):
self.client = llm_client
self.agent = ResearchAgent(llm_client, tools)
def process_request(self, request: dict) -> dict:
request_type = request.get("type")
if request_type == "summarize":
# WORKFLOW: known, predictable steps
return self._summarize_workflow(request["content"])
elif request_type == "analyze":
# WORKFLOW with AGENT fallback
result = self._analyze_workflow(request["content"])
if result.get("needs_research"):
# Hand off to agent for the uncertain part
research = self.agent.research(result["research_question"])
result["research"] = research
return result
elif request_type == "research":
# AGENT: open-ended, unpredictable
return {"answer": self.agent.research(request["question"])}
else:
# REACTIVE: simple response
return {"answer": self._simple_response(request["content"])}
def _summarize_workflow(self, content: str) -> dict:
"""Fixed 2-step workflow."""
# Step 1: Summarize
summary = self._llm_call("Summarize in 3 bullets:", content)
# Step 2: Extract keywords
keywords = self._llm_call("Extract 5 keywords:", content)
return {"summary": summary, "keywords": keywords}
def _analyze_workflow(self, content: str) -> dict:
"""Workflow that may need agent help."""
analysis = self._llm_call("Analyze this content. If external research is needed, say NEEDS_RESEARCH: [question]", content)
if "NEEDS_RESEARCH:" in analysis:
question = analysis.split("NEEDS_RESEARCH:")[-1].strip()
return {"analysis": analysis, "needs_research": True, "research_question": question}
return {"analysis": analysis, "needs_research": False}
def _llm_call(self, instruction: str, content: str) -> str:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{instruction}
{content}"}],
temperature=0
)
return response.choices[0].message.content
def _simple_response(self, content: str) -> str:
return self._llm_call("Respond helpfully:", content)
Production Recommendations¶
- Start with workflows for every task you can predict
- Add agents only where flexibility is required
- Put guardrails on agents: max steps, timeouts, cost limits
- Log everything: agent decisions are harder to debug than workflow steps
- Have a human fallback: when the agent gets stuck, escalate to a person
Resources¶
- Anthropic's "Building Effective Agents" -- Practical guide on when to use workflows vs agents
- Andrew Ng's Agentic Patterns -- Four patterns for agentic AI design
- LangGraph -- Framework that supports both workflow and agent patterns
Key Takeaways¶
- Workflows are deterministic pipelines where the developer controls flow -- use them for predictable tasks
- Agents are autonomous where the LLM controls flow -- use them for open-ended tasks
- Hybrid is usually best in production: workflow for the known parts, agent for the uncertain parts
- Workflows are more reliable and cheaper but less flexible
- Agents are more capable but harder to debug and cost-control
Module Complete!¶
Next Module: Multi-Agent Systems