Building Task-Oriented vs Goal-Oriented AI Agents: Using LangChain
As AI agents become production-ready, understanding their architectural patterns is critical. This article demonstrates the implementation differences between task-oriented and goal-oriented agents using LangChain, complete with code, execution traces, and production considerations.
1. What is an AI Agent?
In artificial intelligence, an agent is a computational entity that:
This model, formalized by Stuart Russell and Peter Norvig in "Artificial Intelligence: A Modern Approach," provides the foundation for modern LLM-based agents.
2. The Fundamental Difference
Task-Oriented Agents:
Goal-Oriented Agents:
3. Implementation #1: Task-Oriented Agent
Architecture
Task-oriented agents execute one step at a time, requiring explicit user guidance between actions.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.tools import tool
# Initialize LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Simulated data
DOCTORS_DB = {
"Cardiology": ["Dr. Patel", "Dr. Lee"],
"Neurology": ["Dr. Jones", "Dr. Wang"]
}
APPOINTMENT_SLOTS = {
"Dr. Patel": ["Jan 21 9AM", "Jan 22 2PM"],
"Dr. Lee": ["Jan 20 10AM", "Jan 22 1PM"]
}
@tool
def FindDoctorsTool(specialty: str):
"""Return available doctors for the given specialty."""
# ✅ FIX: Case-insensitive lookup
specialty_capitalized = specialty.capitalize()
doctors = DOCTORS_DB.get(specialty_capitalized, [])
if doctors:
return f"Available {specialty_capitalized} doctors: {', '.join(doctors)}"
return f"No doctors found for specialty: {specialty}"
@tool
def CheckAvailabilityTool(doctor_name: str):
"""Return available appointment slots for the selected doctor."""
slots = APPOINTMENT_SLOTS.get(doctor_name, [])
if slots:
return f"Available slots for {doctor_name}: {', '.join(slots)}"
return f"No available slots found for {doctor_name}"
@tool
def BookAppointmentTool(details: str):
"""Book an appointment and return confirmation."""
return f"✅ Appointment successfully booked for: {details}"
tools = [FindDoctorsTool, CheckAvailabilityTool, BookAppointmentTool]
# Task-Oriented Prompt
prompt = ChatPromptTemplate.from_messages([
("system", """You are a task-oriented assistant.
- Only respond to the user's command step-by-step.
- Do NOT book automatically or make decisions unless explicitly asked.
- Execute only ONE action per request.
Available specialties: Dermatology, Cardiology, Neurology"""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
# Create agent
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
# Create executor
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=1,
handle_parsing_errors=True
)
print("=" * 60)
print("Step 1: Find dermatologists")
print("=" * 60)
response = executor.invoke({"input": "Find dermatologists"})
print("\n--- OUTPUT ---")
print(response['output'])
4. Implementation #2: Goal-Oriented Agent
Architecture
Goal-oriented agents accept high-level objectives and autonomously plan/execute multi-step workflows.
# =====================================================
# GOAL-ORIENTED PROMPT DESIGN
# =====================================================
prompt = ChatPromptTemplate.from_messages([
("system", """You are a **goal-oriented healthcare assistant AI**.
When a user provides a goal like "Book a dermatology appointment tomorrow afternoon":
YOUR JOB:
1. Find doctors in the requested specialty
2. Check availability for ALL doctors to find best options
3. Select the best doctor based on:
- Time preference match (morning/afternoon/specific time)
- Doctor rating (prefer 4.8+ ratings)
- Availability
4. Book the appointment automatically
5. Provide comprehensive confirmation
DECISION-MAKING RULES:
• Prefer afternoon slots if user says "afternoon" (12PM-5PM)
• Prefer morning slots if user says "morning" (6AM-12PM)
• Choose highest-rated doctor when multiple options exist
• If no perfect match, choose closest available time
• Always book the appointment - don't ask for confirmation
EXECUTE ALL STEPS AUTONOMOUSLY. Provide final confirmation only."""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
# =====================================================
# CREATE GOAL-ORIENTED AGENT
# =====================================================
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=10, # ← KEY: Multiple autonomous steps allowed
handle_parsing_errors=True
)
Key Takeaways
1. The difference is architectural: max_iterations=1 vs max_iterations=10+, single-action vs multi-action planning
2. The future is hybrid: Start goal-oriented, escalate to task-oriented on uncertainty
3. LangChain 0.3+ simplifies orchestration: create_tool_calling_agent handles tool routing, AgentExecutor manages iteration
5. Example (Finance domain)
# Simulated data
ACCOUNTS = {
"ACC-123": {"owner": "John Smith", "balance": 125430, "daily_limit": 50000, "used": 12000}
}
BENEFICIARIES = {
"BEN-456": {"name": "ABC Suppliers", "bank": "Chase", "status": "VERIFIED"}
}
# Tools
@tool
def VerifyAccountTool(account_id: str) -> str:
"""Verify account exists and is active."""
acc = ACCOUNTS.get(account_id)
if not acc:
return f"❌ Account {account_id} not found"
return f"✅ {account_id}: {acc['owner']}, Balance: ${acc['balance']:,}"
@tool
def CheckLimitTool(account_id: str) -> str:
"""Check daily transfer limit."""
acc = ACCOUNTS.get(account_id)
if not acc:
return f"❌ Account {account_id} not found"
remaining = acc['daily_limit'] - acc['used']
return f"Daily Limit: ${acc['daily_limit']:,}, Used: ${acc['used']:,}, Remaining: ${remaining:,}"
@tool
def ValidateBeneficiaryTool(beneficiary_id: str) -> str:
"""Validate beneficiary."""
ben = BENEFICIARIES.get(beneficiary_id)
if not ben:
return f"❌ Beneficiary {beneficiary_id} not found"
return f"✅ {ben['name']} at {ben['bank']} - {ben['status']}"
@tool
def PrepareTransferTool(from_id: str, to_id: str, amount: float) -> str:
"""Prepare transfer for approval."""
acc = ACCOUNTS.get(from_id)
ben = BENEFICIARIES.get(to_id)
if not acc or not ben:
return "❌ Invalid account or beneficiary"
if amount > acc['balance']:
return f"❌ Insufficient funds"
return f"""⚠️ WIRE TRANSFER READY FOR APPROVAL
FROM: {acc['owner']} ({from_id})
TO: {ben['name']}
AMOUNT: ${amount:,.2f}
Type 'CONFIRM' to execute"""
@tool
def ExecuteTransferTool(from_id: str, to_id: str, amount: float, code: str) -> str:
"""Execute transfer ONLY with confirmation code."""
if code != "CONFIRMED":
return "❌ Transfer rejected - missing confirmation"
# Execute
ACCOUNTS[from_id]['balance'] -= amount
ACCOUNTS[from_id]['used'] += amount
return f"""✅ TRANSFER EXECUTED
Confirmation: WIRE{abs(hash(from_id + to_id)) % 1000000:06d}
Amount: ${amount:,.2f}
New Balance: ${ACCOUNTS[from_id]['balance']:,.2f}"""
tools = [VerifyAccountTool, CheckLimitTool, ValidateBeneficiaryTool,
PrepareTransferTool, ExecuteTransferTool]
# Task-oriented prompt
prompt = ChatPromptTemplate.from_messages([
("system", """Financial transaction assistant.
RULES:
- Execute ONLY ONE step per request
- NEVER execute without explicit confirmation
- Human approval mandatory
Steps: Verify → Check limit → Validate beneficiary → Prepare → Execute"""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
# Create executor with max_iterations=1
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=1, # ← Task-oriented: one step at a time
handle_parsing_errors=True,
verbose=True
)
6. Conclusion
As we build increasingly capable AI agents, understanding these fundamental architectural patterns becomes essential. Task-oriented agents provide control and transparency, while goal-oriented agents deliver efficiency and autonomy.