Building Task-Oriented vs Goal-Oriented AI Agents: Using LangChain

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:

  1. Perceives its environment through observations
  2. Reasons about goals and actions
  3. Acts using available tools/functions
  4. Learns from outcomes (optional in rule-based agents)

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:

  • Execute one action per user request
  • Require explicit user guidance for each step
  • max_iterations = 1 in LangChain
  • High transparency, low autonomy

Goal-Oriented Agents:

  • Execute entire workflows autonomously
  • Accept high-level goals, plan all steps
  • max_iterations = 10+ in LangChain
  • Low transparency, high autonomy

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'])        
Article content
Article content
Article content

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
)        
Article content
Article content
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
)
        
Article content

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.

To view or add a comment, sign in

More articles by Rambaksh Prajapati

Explore content categories