Workflow vs Agents
Workflow vs Agents
When we automate tasks with LLMs, there are two types:
- Workflow-based applications.
- Agent-based applications.
First lets define the skeleton of automating tasks with LLMs.
Task (f): This is "like" a function. Where we give input and we want to get an output. If we can breakdown a task into composable steps (f1, f2, f3, ...)
then f = f1 o f2 o f3 o ...
Example 1: Airport Security Task
Task: Security check at the airport, where an officer checks your ID card and flight boarding pass to verify your identity.
INPUT: ID card, flight boarding pass, Person Live photo OUTPUT: Pass/Fail (Boolean Output1 and Output2)
โโโโโโโโโโโโโโโ
โ START โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ
โ ID VERIFIER โ โ FLIGHT DETAILS โ
โโโโโโโโโโโโโโโโ โ VERIFIER โ
โ โโโโโโโโโโโฌโโโโโโโโโโโ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ โ
โ โ โ โ
โผ โผ โผ โผ
โโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ live โ โ id โ โ flight โ โ flight โ
โ foto โ โ image โ โ boar. โ โ boar. โ
โโโโโฌโโโโโ โโโโโโฌโโโโโ โ pass โ โ pass โ
โ โ โโโโโโโโโโโ โโโโโโโโฌโโโโโโโ
โ โผ โ
โ โญโโโโโโโโโโฎ โผ
โ โ extract โ โญโโโโโโโโโโฎ
โ โ id# โ โ extract โ
โ โฐโโโโโฌโโโโโฏ โ details โ
โ โ id# โฐโโโโโฌโโโโโฏ
โ โโโโโโโโโดโโโโโโโโโ โ
โ โ โ โผ
โ โผ โผ โโโโโโโโโโโโโโโโ
โ โโโโโโโ โโโโโโโ โ - name โ
โ โ get โ โ get โ โ - flight # โ
โ โphotoโ โname โ โโโโโโโโฌโโโโโโโโ
โ โfrom โ โfrom โ โ
โ โid dbโ โid dbโ โ
โ โโโโโโโ โโโโโโโ โ
โ โ db_photo โ โ
โ โโโโโโโโโโฌโโโโโโโโ โ
โผ โ โ
โญโโโโโโโโโโฎ โ โ
โsimilarityโโโโโโโโโ โ
โ check โ โ
โฐโโโโโฌโโโโโฏ โ
โ โ
โ โญโโโโโโโโโโโฎ โ
โโโโโโโโโโโโโโโบโ equality โโโโโโโโโโโโโโโโโโโโโโโ
โ check โ
โฐโโโโโฌโโโโโโฏ
โ
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
โ โ
โผ โผ
BOOLEAN OUTPUT1 BOOLEAN OUTPUT2
The above task can be automated like this:
Workflow Breakdown
The process starts at START and branches into two parallel verification paths:
ID VERIFIER Path
Inputs:
live foto: Live photo of the personid image: Image of the ID card
Steps:
- extract id#: Extract the ID number from the
id image - get photo from id db: Query the ID database using the extracted
id#to retrieve the stored photo (db photo) - get name from id db: Query the ID database using the extracted
id#to retrieve the stored name - similarity check: Compare the
live fotowith thedb photofrom the database - BOOLEAN OUTPUT1: Result of the photo similarity check (True/False)
FLIGHT DETAILS VERIFIER Path
Inputs:
flight boar. pass: Flight boarding pass
Steps:
- extract details: Extract information from the boarding pass:
- Name
- Flight number
- Data display node contains the extracted details
Convergence and Final Verification
equality check: Compare the name retrieved from the ID database (from ID VERIFIER path) with the name extracted from the flight boarding pass (from FLIGHT DETAILS VERIFIER path)
BOOLEAN OUTPUT2: Result of the name equality check (True/False)
Here the steps we have are:
- extract id#
- get photo from id db
- get name from id db
- "foto" similarity check
- extract details
- equality check
Here we can clearly define the order of the steps and the dependencies. For optimization, we are running steps (2-4) and (3-5) in parallel. However, both parallel branches must wait for step 1 to complete first.
Key Insight: Whenever you can break down a task into a deterministic DAG (Directed Acyclic Graph) structure with clearly defined step ordering and dependencies, we call that a WORKFLOW.
๐ก WORKFLOW = Deterministic task breakdown with explicit dependencies and execution order
Lets take a look at another example.
Example 2: Medical Diagnosis Agent
Task: Diagnose a patient's condition based on symptoms
INPUT: Initial symptoms, patient age, medical history
OUTPUT: Diagnosis with confidence level and treatment recommendations
Why This Requires an Agent (Not a Workflow)
Unlike the airport security example where we knew all the steps upfront, medical diagnosis cannot be predetermined because:
- The diagnostic path depends on what you discover at each step
- Each test result changes the probability distribution of possible diagnoses
- Some findings rule out entire branches, others open new investigation paths
- May need to backtrack if initial hypothesis proves wrong
- Different patients with the same initial symptom require completely different tool sequences
Two Patient Scenarios
Let's see how the same initial complaint leads to completely different diagnostic paths:
Scenario A: 28-year-old with fever and dry cough
Scenario B: 55-year-old with fatigue and unexplained weight loss
โโโโโโโโโโโโโโโ
โ START โ
โ + symptoms โ
โ + age โ
โ + history โ
โโโโโโโโฌโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL: symptom_analyzerโ
โ Extract key symptoms โ
โ & severity scores โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
โ โ
[Patient A: fever + cough] [Patient B: fatigue + weight loss]
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ AGENT REASONING: โ โ AGENT REASONING: โ
โ Acute respiratory + โ โ Chronic systemic + โ
โ Recent pandemic โ โ Age 55+ = concern โ
โ โ Check infection โ โ โ Check metabolic โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL: vital_checker โ โ TOOL: vital_checker โ
โ Temp: 101.2ยฐF โ โ BP: normal โ
โ SpO2: 96% โ โ HR: 52 bpm (LOW) โ
โ Resp: 22/min โ โ Temp: 97.1ยฐF (LOW) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ AGENT REASONING: โ โ AGENT REASONING: โ
โ Fever confirmed + โ โ Low HR + Low temp = โ
โ Good O2 = likely โ โ HYPOTHYROID pattern โ
โ upper respiratory โ โ Weight loss unusual โ
โ โ Pandemic test first โ โ โ Thyroid labs โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL: covid_test โ โ TOOL: lab_order โ
โ Result: POSITIVE โ โ TSH, T3, T4 โ
โ โ โ โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ AGENT REASONING: โ โ TOOL: lab_results โ
โ Confirmed COVID-19 โ โ TSH: 0.1 (very LOW) โ
โ No severe symptoms โ โ T4: HIGH โ
โ โ Check risk factors โ โ T3: HIGH โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL: history_checker โ โ AGENT REASONING: โ
โ No chronic conditions โ โ WAIT! High thyroid โ
โ Vaccinated โ โ but WEIGHT LOSS? โ
โ โ โ Should gain weight! โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โ โ HYPERthyroid โ
โ โ โ Check antibodies โ
โผ โโโโโโโโโโโโฌโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ DIAGNOSIS: โ โผ
โ COVID-19 (mild) โ โโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ TOOL: antibody_test โ
โ RECOMMENDATION: โ โ Anti-TSH receptor โ
โ - Home isolation โ โ Result: POSITIVE โ
โ - Symptomatic care โ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ - Monitor O2 โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโ โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ AGENT REASONING: โ
โ TSH-receptor Ab + โ
โ Confirms Graves' โ
โ โ Check for โ
โ complications โ
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL: symptom_scan โ
โ Check: tremors, โ
โ heat intolerance, โ
โ eye changes โ
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ DIAGNOSIS: โ
โ Graves' Disease โ
โ (Hyperthyroidism) โ
โ โ
โ RECOMMENDATION: โ
โ - Endocrinology ref โ
โ - Anti-thyroid meds โ
โ - Beta blockers โ
โโโโโโโโโโโโโโโโโโโโโโโ
Why An Agent Is Essential
Tool Execution Comparison
Patient A Path:
symptom_analyzer โ vital_checker โ covid_test โ history_checker โ DONE
(4 tools, linear path)
Patient B Path:
symptom_analyzer โ vital_checker โ lab_order โ lab_results
โ [PIVOT] โ antibody_test โ symptom_scan โ DONE
(6 tools, with mid-course correction)
Key Decision Points (Why Predetermined DAG Fails)
After symptom_analyzer:
- Agent must choose between infectious disease tools vs. metabolic panels
- Choice depends on: age, acuity, symptom pattern
After vital_checker (Patient B):
- Low HR + Low temp suggests hypothyroid
- But agent keeps open mind (doesn't commit to diagnosis yet)
After lab_results (Patient B) - CRITICAL PIVOT:
- Results CONTRADICT initial hypothesis!
- Hypothyroid would show HIGH TSH + LOW T4
- Actually shows LOW TSH + HIGH T4 = Hyperthyroid
- Weight loss now makes sense (hypermetabolic state)
- Agent must backtrack reasoning and take new path
Dynamic tool selection:
- Patient A: Never needed thyroid tests, antibody tests, or symptom scans
- Patient B: Never needed respiratory tests
- Impossible to know upfront which tools to use
Non-Deterministic Edges
โโโโโโโโโโโโโโโโโโ โ START โ โ โ โ - symptoms โ โ - age โ โ - history โ โโโโโโโโโโฌโโโโโโโโ โ โผ โโโโโโโโโโโโ โ vitals โ โ _check โ โโโโโโโโโโโโ โ * vitals โผ โโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โ covid โ โญโโโโโโโโโโโโโโโฎ โ CASE NOTES/ โ โ _test โ โ โ โ AGENT MEMORY โ โโโโโโโโโโโโ โ Diagnosis โ- - - >โ TOOL SET โ โฒ โ Agent โ โ โ โ โ โ โ - CRUD of memory โ โ โ โ โ โ โ โฐโโโโโโโฌโโโโโโโโฏ โ - Summarise history โ โโโโโโโโโโโโ โ โ - Get a specific โ โ lab โ โ โ detail. โ โ _order โ โ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโ โ โฒ โ โ โ โ โ โ โ โ โ โ โโค โโโโโโโโโโโโ โ โ antibody โ โ โ _tst โ โ โโโโโโโโโโโโ โ โฒ โ โ โ โ โ โ โ โ โ โ โโค โ โโโโโโโโโโโโ โ โ ask โ โ โ _patient โโโโโโโโโโโโโโโ โโโโโโโโโโโโ
The Impossibility of Workflow Approach
A workflow would need to:
1. Check ALL possible symptoms โ (expensive, time-consuming)
2. Run ALL possible tests โ (harmful to patient, costly)
3. Have branches for every disease combination โ (exponential complexity)
An agent instead:
1. โ Reasons about probabilities
2. โ Selects minimal necessary tests
3. โ Adapts when findings contradict hypothesis
4. โ Uses domain knowledge to guide exploration
5. โ Terminates when confidence threshold reached
Key Insight: Medical diagnosis requires an intelligent agent that can dynamically plan its investigation strategy based on what it learns at each step. The DAG cannot be drawn before execution - it emerges through the agent's reasoning process.
๐ก AGENT = Non-deterministic task breakdown where the execution path is determined dynamically based on findings at each step
How do we build this with DSPy?
EXAMPLE 1: Airport Security Task
import dspy
# DISCLAIMER: as of nov 2025, this doesn't work because dspy doesn't support image input/output. But just for the sake of example, let's pretend it does.
class ExtractIdNumber(dspy.Signature):
"""Extract the ID number from the ID card"""
id_card: Image = dspy.InputField()
id_number: str = dspy.OutputField()
class SimilarityCheck(dspy.Signature):
"""Check if the live foto is similar to the photo from the ID database"""
live_foto: Image = dspy.InputField()
db_photo: Image = dspy.InputField()
is_similar: bool = dspy.OutputField()
class AirportSecurityTask(dspy.Module):
def __init__(self):
super().__init__()
self.extract_id_number = dspy.ChainOfThought(ExtractIdNumber)
self.similarity_check = dspy.ChainOfThought(SimilarityCheck)
def forward(self, live_foto: Image, id_card: Image, flight_boarding_pass: Image) -> bool:
id_number = self.extract_id_number.run(id_card)
# Run face match and name match in parallel
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# Submit face match task
face_match_future = executor.submit(
lambda: (
self.get_photo_from_id_db(id_number),
lambda db_photo: self.similarity_check(live_foto, db_photo)
)
)
# Submit name match task
name_match_future = executor.submit(
lambda: (
self.get_name_from_id_db(id_number),
self.extract_flight_details(flight_boarding_pass)
)
)
# Get face match result
db_photo, similarity_fn = face_match_future.result()
is_similar = similarity_fn(db_photo)
# Get name match result
name, flight_details = name_match_future.result()
is_name_equal = name == flight_details['name']
return is_similar and is_name_equal
@staticmethod
def get_photo_from_id_db(id_number: str) -> Image:
pass
@staticmethod
def get_name_from_id_db(id_number: str) -> str:
pass
@staticmethod
def extract_flight_details(flight_boarding_pass: Image) -> Dict[str, str]:
pass
EXAMPLE 2: Medical Diagnosis Agent
import dspy
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
# Define the tools available to the agent
@dataclass
class PatientData:
symptoms: List[str]
age: int
medical_history: List[str]
class Diagnosis:
diagnosis: str
confidence: int
class MedicalDiagnosisAgentSign(dspy.Signature):
"""Analyze patient symptoms and extract key symptoms with severity scores"""
symptoms: List[str] = dspy.InputField(desc="List of patient symptoms")
age: int = dspy.InputField(desc="Patient age")
medical_history: List[str] = dspy.InputField(desc="Patient medical history")
diagnosis: List[Diagnosis] = dspy.OutputField(desc="List of possible diagnoses")
def check_vitals():
"""Check patient vital signs"""
pass
def covid_test():
"""Check if the patient has COVID-19"""
pass
def lab_order():
"""Order laboratory tests"""
pass
def ask_patient_questions(questions: List[str]) -> List[str]:
"""Ask the patient questions to get more information"""
pass
class MedicalDiagnosisAgent(dspy.Module):
"""A Medical Diagnosis Agent."""
def __init__(self, memory: Memory):
super().__init__()
self.memory_tools = MemoryTools(memory)
mem_tools = [
store_memory,
search_memories,
get_all_memories,
update_memory,
delete_memory
] # lets assume that these are the memory tools that we want to use.
self.tools = [check_vitals, covid_test, lab_order, ask_patient_questions, get_current_time, *mem_tools]
self.react = dspy.ReAct(
signature=MedicalDiagnosisAgentSign,
tools=self.tools,
max_iters=6
)
def forward(self, symptoms: List[str], age: int, medical_history: List[str]) -> List[Diagnosis]:
"""Diagnose the patient's condition."""
return self.react(symptoms=symptoms, age=age, medical_history=medical_history)
Why I use DSPy for building automations with LLMs?
My task breakdown into sub-tasks is focused on the composable functions' input-output, that is what defines a sub-task for me. In this library, I can exactly define the sub-tasks perfectly as per my task breakdown. Thereby making it the most intuitive way to build automations with LLMs (for me, and in my opinion for everyone else too).