RAG, Frameworks, Workflows & Multi-Agent Systems
| Feature | LangChain | LangGraph | Semantic Kernel | CrewAI |
|---|---|---|---|---|
| Language | Python, JS | Python, JS | C#, Python, Java | Python |
| Agent Type | Single agent | Graph workflows | Single / plugins | Multi-agent crews |
| RAG Support | β Built-in | β Via LangChain | β Memory plugins | β Via tools |
| Tool Calling | β @tool decorator | β Node functions | β KernelFunction | β @tool decorator |
| Memory | β Multiple types | β State graph | β Persistent | β Short/long term |
| Local LLM (Ollama) | β | β | β | β |
| Enterprise .NET | β οΈ Python only | β οΈ Python only | β C# native | β οΈ Python only |
| Multi-Agent | β οΈ Manual | β Graph nodes | β οΈ Manual | β Built-in |
| Learning Curve | π‘ Medium | π΄ Steep | π‘ Medium | π’ Easy |
from langchain_ollama import ChatOllama
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.tools import tool
# Tool
@tool
def lookup_order(order_id: str) -> str:
"""Look up order by ID. Returns status and ETA."""
return f'{{"order_id": "{order_id}", "status": "shipped", "eta": "2026-04-30"}}'
# LLM + Prompt
llm = ChatOllama(model="qwen2.5:3b", temperature=0.2)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a customer support agent. Use tools to help customers."),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
# Wire up
agent = create_tool_calling_agent(llm, [lookup_order], prompt)
executor = AgentExecutor(agent=agent, tools=[lookup_order], verbose=True)
# Run
result = executor.invoke({"input": "Where is my order #7845?"})
print(result["output"])
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using System.ComponentModel;
// Define a plugin (tool)
public class OrderPlugin
{
[KernelFunction, Description("Look up order by ID. Returns status and ETA.")]
public string LookupOrder([Description("The order ID")] string orderId)
{
return $"{{\"order_id\": \"{orderId}\", \"status\": \"shipped\", \"eta\": \"2026-04-30\"}}";
}
}
// Setup kernel
var builder = Kernel.CreateBuilder();
builder.AddOllamaChatCompletion("qwen2.5:3b", new Uri("http://localhost:11434"));
builder.Plugins.AddFromType<OrderPlugin>();
var kernel = builder.Build();
// Chat with auto function calling
var settings = new PromptExecutionSettings {
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory("You are a customer support agent.");
history.AddUserMessage("Where is my order #7845?");
var response = await chatService.GetChatMessageContentAsync(history, settings, kernel);
Console.WriteLine(response.Content);
from crewai import Agent, Task, Crew
from crewai_tools import tool
from langchain_ollama import ChatOllama
@tool("Order Lookup")
def lookup_order(order_id: str) -> str:
"""Look up order by ID. Returns status and ETA."""
return f'{{"order_id": "{order_id}", "status": "shipped", "eta": "2026-04-30"}}'
# Define agent with role
support_agent = Agent(
role="Customer Support Specialist",
goal="Resolve customer issues quickly and empathetically",
backstory="You are an experienced support agent for ShopEasy e-commerce.",
tools=[lookup_order],
llm=ChatOllama(model="qwen2.5:3b", temperature=0.2),
verbose=True
)
# Define task
resolve_task = Task(
description="Customer asks: 'Where is my order #7845?' Resolve this.",
expected_output="A helpful response with order status",
agent=support_agent
)
# Create crew and run
crew = Crew(agents=[support_agent], tasks=[resolve_task], verbose=True)
result = crew.kickoff()
print(result)
| Component | Version | Purpose |
|---|---|---|
| Python | 3.11+ | Agent development, LangChain, CrewAI |
| Ollama | Latest | Local LLM runtime |
| .NET SDK | 8.0+ | Semantic Kernel (Week 4) |
| VS Code | Latest | IDE with Copilot |
| Git | Latest | Version control |
# Install Ollama (Windows) winget install Ollama.Ollama # Start Ollama service ollama serve # Pull models ollama pull qwen2.5:3b # Main LLM (1.9 GB) ollama pull nomic-embed-text # Embedding model (274 MB) ollama pull llama3.1:8b # Alternative LLM β optional (4.7 GB) # Verify ollama list ollama run qwen2.5:3b "Hello, are you working?"
# Create project directory
mkdir customer-support-agent
cd customer-support-agent
# Create virtual environment
python -m venv venv
.\venv\Scripts\Activate.ps1 # Windows PowerShell
# source venv/bin/activate # Mac/Linux
# Install core dependencies
pip install langchain langchain-ollama langchain-core langchain-community
pip install langchain-chroma # Vector store for RAG
pip install langgraph # Graph workflows
pip install crewai crewai-tools # Multi-agent
pip install chromadb # Vector database
pip install streamlit # Web UI
pip install python-dotenv # Environment variables
pip install tiktoken # Tokenizer utilities
# Verify installation
python -c "from langchain_ollama import ChatOllama; print('LangChain + Ollama ready!')"
customer-support-agent/
βββ venv/ # Virtual environment
βββ .env # Environment variables
βββ requirements.txt # Python dependencies
β
βββ config/
β βββ settings.py # Model, temperature, prompts
β
βββ tools/
β βββ __init__.py
β βββ order_tools.py # lookup_order, cancel_order
β βββ policy_tools.py # get_refund_policy, get_faq
β βββ communication_tools.py # send_email, escalate
β
βββ knowledge/
β βββ policies.txt # Company policies
β βββ faqs.txt # FAQ documents
β βββ products.txt # Product information
β
βββ agents/
β βββ __init__.py
β βββ support_agent.py # Main ReAct agent
β βββ rag_agent.py # RAG-powered agent
β βββ crew_agent.py # Multi-agent crew
β
βββ memory/
β βββ chroma_db/ # Persistent vector store
β
βββ app_cli.py # CLI interface
βββ app_streamlit.py # Streamlit web UI
βββ tests/
βββ test_agent.py # Agent tests
# Agent Configuration OLLAMA_BASE_URL = "http://localhost:11434" LLM_MODEL = "qwen2.5:3b" EMBEDDING_MODEL = "nomic-embed-text" TEMPERATURE = 0.2 MAX_TOKENS = 1024 MAX_AGENT_ITERATIONS = 5 # System Prompt SYSTEM_PROMPT = """You are a customer support agent for ShopEasy, India's trusted e-commerce platform. You have access to tools to look up orders, check policies, and search the knowledge base. Rules: 1. ALWAYS use tools to verify information β never guess 2. Be empathetic, professional, and concise 3. Reference specific order IDs and amounts 4. If you cannot resolve, escalate: "Let me connect you to a specialist" 5. Never fabricate policies, discounts, or order information 6. Keep responses under 150 words """
"""Run this to verify your entire setup works."""
import sys
def check_ollama():
import requests
try:
r = requests.get("http://localhost:11434/api/tags", timeout=5)
models = [m["name"] for m in r.json().get("models", [])]
assert any("qwen" in m for m in models), "qwen2.5:3b not found!"
print(f"β
Ollama running. Models: {models}")
except Exception as e:
print(f"β Ollama not running: {e}")
sys.exit(1)
def check_langchain():
from langchain_ollama import ChatOllama
llm = ChatOllama(model="qwen2.5:3b", temperature=0)
response = llm.invoke("Say 'Hello Agent' and nothing else.")
print(f"β
LangChain + Ollama: {response.content[:50]}...")
def check_embeddings():
from langchain_ollama import OllamaEmbeddings
emb = OllamaEmbeddings(model="nomic-embed-text")
vector = emb.embed_query("test embedding")
print(f"β
Embeddings working. Dimension: {len(vector)}")
def check_chromadb():
import chromadb
client = chromadb.Client()
col = client.create_collection("test")
col.add(documents=["test doc"], ids=["1"])
results = col.query(query_texts=["test"], n_results=1)
print(f"β
ChromaDB working. Query result: {results['documents']}")
if __name__ == "__main__":
print("π Verifying setup...\n")
check_ollama()
check_langchain()
check_embeddings()
check_chromadb()
print("\nπ All checks passed! You're ready to build agents.")
# Verify .NET SDK dotnet --version # Should be 8.0+ # Create project dotnet new console -n ShopEasyAgent cd ShopEasyAgent # Add Semantic Kernel dotnet add package Microsoft.SemanticKernel dotnet add package Microsoft.SemanticKernel.Connectors.Ollama --prerelease # Verify dotnet build
verify_setup.py and ensure all checks passRAG (Retrieval-Augmented Generation) gives the LLM access to external knowledge at query time. Instead of relying only on training data, the LLM retrieves relevant documents and uses them to generate accurate, grounded answers.
Q: "What's ShopEasy's refund policy?"
A: "Most e-commerce companies offer
30-day refund windows..."
β HALLUCINATED β made up generic answer!Q: "What's ShopEasy's refund policy?"
[Retrieved: policies.txt β "7-day full refund,
after 7 days store credit only"]
A: "ShopEasy offers full refund within 7 days.
After 7 days, you get store credit."
β ACCURATE β grounded in real data!## Refund Policy - Full refund within 7 days of delivery for all products - After 7 days: store credit only (valid for 6 months) - Refund processed within 3-5 business days to original payment method - Electronics: 15-day return window - Fashion: 30-day return window - Damaged items: full refund + βΉ500 inconvenience credit (photo required) ## Shipping Policy - Standard delivery: 5-7 business days (free on orders above βΉ999) - Express delivery: 1-2 business days (βΉ149 flat fee) - Same-day delivery: available in metro cities (βΉ299) - Shipping delay >2 days: βΉ200 store credit automatically applied - Shipping delay >7 days: full refund option available ## Cancellation Policy - Orders can be cancelled before shipping at no charge - After shipping: must wait for delivery and initiate return - Digital products: non-cancellable after activation ## Payment Methods - UPI (Google Pay, PhonePe, Paytm) - Credit Cards (Visa, Mastercard, RuPay) - Debit Cards - Net Banking - EMI available on orders above βΉ3,000 - Cash on Delivery (βΉ49 handling fee) ## Warranty - All electronics: 1-year manufacturer warranty - Extended warranty available for βΉ999/year - Warranty does not cover physical damage - Warranty claims: upload proof of purchase + photos of issue
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
# 1. Load documents
loader = TextLoader("knowledge/policies.txt", encoding="utf-8")
documents = loader.load()
# 2. Split into chunks
splitter = RecursiveCharacterTextSplitter(
chunk_size=300, # Each chunk ~300 chars
chunk_overlap=50, # 50 char overlap between chunks
separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks")
# 3. Create embeddings and store in ChromaDB
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./memory/chroma_db",
collection_name="support_kb"
)
print(f"β
Vector store created with {vectorstore._collection.count()} documents")
# 4. Test retrieval
results = vectorstore.similarity_search("What is the refund policy?", k=3)
for i, doc in enumerate(results):
print(f"\n--- Result {i+1} ---")
print(doc.page_content)
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# Load existing vector store
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(
persist_directory="./memory/chroma_db",
collection_name="support_kb",
embedding_function=embeddings
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# LLM
llm = ChatOllama(model="qwen2.5:3b", temperature=0.2)
# RAG Prompt
rag_prompt = ChatPromptTemplate.from_template("""
You are a customer support agent for ShopEasy.
Answer the customer's question using ONLY the context below.
If the answer is not in the context, say: "I don't have that information. Let me connect you to a specialist."
Context:
{context}
Customer Question: {question}
Instructions:
- Be specific β quote policies with exact numbers/days
- Be empathetic and professional
- Keep response under 100 words
Your Response:""")
# Helper: format retrieved docs
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# RAG Chain
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
# Test it!
questions = [
"What's your refund policy for electronics?",
"Do you offer same-day delivery?",
"Can I pay with UPI?",
"I received a damaged item, what do I do?",
"What's the warranty on laptops?",
]
for q in questions:
print(f"\nβ {q}")
answer = rag_chain.invoke(q)
print(f"π¬ {answer}")
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_chroma import Chroma
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.memory import ConversationBufferWindowMemory
from langchain.tools import tool
import json
# --- Tools ---
@tool
def search_knowledge_base(query: str) -> str:
"""Search ShopEasy's knowledge base for policies, FAQs, and product info.
Use this when customer asks about policies, procedures, or general info."""
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vs = Chroma(persist_directory="./memory/chroma_db",
collection_name="support_kb", embedding_function=embeddings)
docs = vs.similarity_search(query, k=3)
return "\n\n".join(f"[Source {i+1}]: {d.page_content}" for i, d in enumerate(docs))
@tool
def lookup_order(order_id: str) -> str:
"""Look up order by ID. Returns status, items, total, and delivery info."""
orders = {
"7845": {"order_id": "7845", "status": "shipped", "eta": "2026-04-30",
"items": ["MacBook Air M3"], "total": 89999, "customer": "Rajesh Kumar"},
"7846": {"order_id": "7846", "status": "delivered", "delivered_on": "2026-04-25",
"items": ["iPhone 16"], "total": 79999, "customer": "Priya Singh"},
}
order = orders.get(order_id.strip())
return json.dumps(order) if order else f"Order #{order_id} not found."
@tool
def escalate_to_human(reason: str) -> str:
"""Escalate the conversation to a human agent. Use when you cannot resolve the issue."""
return f"Ticket created. Reason: {reason}. A specialist will respond within 2 hours."
# --- Agent Setup ---
llm = ChatOllama(model="qwen2.5:3b", temperature=0.2)
tools = [search_knowledge_base, lookup_order, escalate_to_human]
memory = ConversationBufferWindowMemory(memory_key="chat_history", return_messages=True, k=5)
prompt = ChatPromptTemplate.from_messages([
("system", """You are a customer support agent for ShopEasy, India's trusted e-commerce platform.
Tools available:
- search_knowledge_base: Search policies, FAQs, product info
- lookup_order: Check order status by order ID
- escalate_to_human: Escalate to human agent
Rules:
1. ALWAYS search the knowledge base before answering policy questions
2. ALWAYS look up orders before making claims about order status
3. Be empathetic, specific, and concise (under 100 words)
4. If unsure, escalate β never guess or hallucinate
5. Reference exact policy details (days, amounts) from search results
"""),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent, tools=tools, memory=memory,
verbose=True, max_iterations=5, handle_parsing_errors=True
)
# --- Interactive Loop ---
print("π§ ShopEasy Support Agent (type 'quit' to exit)\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
break
result = executor.invoke({"input": user_input})
print(f"\nπ€ Agent: {result['output']}\n")
search_knowledge_base("damaged item refund policy")policies.txt with at least 5 policy sectionsSimple agents (AgentExecutor) work for straightforward tasks. But real support scenarios need complex workflows:
| Limitation of AgentExecutor | LangGraph Solution |
|---|---|
| No branching: can't route to different handlers | Conditional edges β route by intent |
| No human approval: auto-executes everything | Interrupt nodes β pause for human |
| No parallel paths: sequential only | Parallel nodes β execute simultaneously |
| Hard to retry specific steps | Checkpointing β save & replay state |
| One agent does everything | Multi-node β different LLMs per step |
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
llm = ChatOllama(model="qwen2.5:3b", temperature=0.2)
# --- State Definition ---
class SupportState(TypedDict):
customer_message: str
intent: str
context: str # Retrieved info from tools
sentiment: str
draft_response: str
final_response: str
# --- Node Functions ---
def classify_intent(state: SupportState) -> SupportState:
"""Classify the customer's intent."""
msg = state["customer_message"]
response = llm.invoke(
f"Classify this customer message into exactly one category: "
f"billing, shipping, product, account, escalate.\n"
f"Message: '{msg}'\nCategory:"
)
intent = response.content.strip().lower()
# Normalize
for valid in ["billing", "shipping", "product", "account"]:
if valid in intent:
return {**state, "intent": valid}
return {**state, "intent": "escalate"}
def handle_billing(state: SupportState) -> SupportState:
"""Handle billing-related queries."""
# In production: call billing tools, check payment status
context = "Billing context: Full refund within 7 days. EMI available above βΉ3,000."
return {**state, "context": context}
def handle_shipping(state: SupportState) -> SupportState:
"""Handle shipping-related queries."""
context = "Shipping context: Standard 5-7 days. Express 1-2 days. Delay >2 days = βΉ200 credit."
return {**state, "context": context}
def handle_product(state: SupportState) -> SupportState:
"""Handle product-related queries."""
context = "Product context: 1-year warranty on electronics. Damaged = full refund + βΉ500 credit."
return {**state, "context": context}
def handle_escalation(state: SupportState) -> SupportState:
"""Escalate to human agent."""
return {**state, "context": "ESCALATED: Ticket created for human review.",
"final_response": "I understand this needs special attention. "
"I've escalated your case to a specialist who will "
"respond within 2 hours. Ticket ID: ESC-2026-0428."}
def generate_response(state: SupportState) -> SupportState:
"""Generate the customer-facing response."""
if state.get("final_response"):
return state # Already set by escalation
response = llm.invoke(
f"You are a ShopEasy support agent. Generate a helpful reply.\n\n"
f"Customer: {state['customer_message']}\n"
f"Category: {state['intent']}\n"
f"Policy Info: {state['context']}\n\n"
f"Reply (empathetic, specific, under 100 words):"
)
return {**state, "final_response": response.content}
# --- Router Function ---
def route_by_intent(state: SupportState) -> Literal["billing", "shipping", "product", "escalate"]:
"""Route to the appropriate handler based on intent."""
intent = state.get("intent", "escalate")
if intent in ("billing", "shipping", "product"):
return intent
return "escalate"
# --- Build Graph ---
workflow = StateGraph(SupportState)
# Add nodes
workflow.add_node("classify", classify_intent)
workflow.add_node("billing", handle_billing)
workflow.add_node("shipping", handle_shipping)
workflow.add_node("product", handle_product)
workflow.add_node("escalate", handle_escalation)
workflow.add_node("respond", generate_response)
# Add edges
workflow.add_edge(START, "classify")
workflow.add_conditional_edges("classify", route_by_intent, {
"billing": "billing",
"shipping": "shipping",
"product": "product",
"escalate": "escalate",
})
workflow.add_edge("billing", "respond")
workflow.add_edge("shipping", "respond")
workflow.add_edge("product", "respond")
workflow.add_edge("escalate", "respond")
workflow.add_edge("respond", END)
# Compile
app = workflow.compile()
# --- Run ---
result = app.invoke({
"customer_message": "My laptop screen is cracked after just 2 weeks!",
"intent": "", "context": "", "sentiment": "",
"draft_response": "", "final_response": ""
})
print(f"Intent: {result['intent']}")
print(f"Response: {result['final_response']}")
For sensitive actions (refunds > βΉ5,000, account deletion), pause the graph and wait for human approval.
from langgraph.checkpoint.memory import MemorySaver
# Add a checkpoint saver
checkpointer = MemorySaver()
# Compile with interrupt_before
app = workflow.compile(
checkpointer=checkpointer,
interrupt_before=["escalate"] # Pause before escalation
)
# Run β will pause at "escalate" node
config = {"configurable": {"thread_id": "ticket-001"}}
result = app.invoke({
"customer_message": "I want to sue your company!",
"intent": "", "context": "", "sentiment": "",
"draft_response": "", "final_response": ""
}, config)
# At this point, graph is PAUSED
# Human reviews and approves...
print("βΈοΈ Paused for human review")
# Resume after approval
result = app.invoke(None, config) # Continue from checkpoint
print(f"Response: {result['final_response']}")
| Scenario | Why LangGraph? |
|---|---|
| Different handlers per intent | Conditional routing β billing team vs shipping team |
| Refund > βΉ5,000 | Human-in-the-loop β manager approval required |
| Complex multi-issue ticket | Parallel nodes β handle billing + shipping simultaneously |
| Quality assurance | Post-processing node β check response before sending |
| Retry on failure | Checkpointing β save state and retry from last good point |
A single agent trying to do everything is like one employee handling triage, investigation, response drafting, AND quality review. Specialization beats generalization for complex workflows.
from crewai import Agent, Task, Crew, Process
from crewai_tools import tool
from langchain_ollama import ChatOllama
import json
llm = ChatOllama(model="qwen2.5:3b", temperature=0.2)
# --- Tools ---
@tool("Order Lookup")
def lookup_order(order_id: str) -> str:
"""Look up order details by order ID."""
orders = {
"7845": {"order_id": "7845", "status": "shipped", "eta": "2026-04-30",
"items": ["MacBook Air M3"], "total": 89999},
"7846": {"order_id": "7846", "status": "delivered",
"items": ["iPhone 16"], "total": 79999},
}
return json.dumps(orders.get(order_id.strip(), {"error": "Order not found"}))
@tool("Knowledge Base Search")
def search_kb(query: str) -> str:
"""Search ShopEasy knowledge base for policies and FAQs."""
policies = {
"refund": "Full refund within 7 days. After 7 days: store credit. Electronics: 15 days.",
"shipping": "Standard 5-7 days. Express 1-2 days. Delay >2 days = βΉ200 credit.",
"damaged": "Damaged: full refund + βΉ500 credit. Photo required.",
"warranty": "Electronics: 1-year warranty. Extended warranty βΉ999/year."
}
results = [v for k, v in policies.items() if any(w in query.lower() for w in k.split())]
return "\n".join(results) if results else "No relevant policy found."
# --- Agent 1: Triage ---
triage_agent = Agent(
role="Customer Triage Specialist",
goal="Quickly classify customer issues, extract key entities, and assess urgency",
backstory="""You are the first line of support at ShopEasy. You read customer
messages and immediately identify: the intent (billing/shipping/product/account),
any order IDs or product names, the customer's sentiment, and urgency level.""",
llm=llm,
verbose=True
)
# --- Agent 2: Research ---
research_agent = Agent(
role="Support Research Analyst",
goal="Gather all relevant information needed to resolve the customer's issue",
backstory="""You are a thorough researcher. Given a triaged ticket, you look up
order details, search the knowledge base for relevant policies, and compile
all facts needed for a resolution.""",
tools=[lookup_order, search_kb],
llm=llm,
verbose=True
)
# --- Agent 3: Response ---
response_agent = Agent(
role="Customer Response Writer",
goal="Write empathetic, accurate, professional responses that resolve customer issues",
backstory="""You are an expert at writing customer-facing messages. You take
research findings and craft the perfect response β empathetic, specific (with
order numbers and amounts), and actionable. You never make up information.""",
llm=llm,
verbose=True
)
# --- Tasks ---
def create_support_tasks(customer_message: str):
triage_task = Task(
description=f"""Analyze this customer message:
"{customer_message}"
Provide: intent, entities (order IDs, products), sentiment, urgency (low/med/high/critical)""",
expected_output="JSON with intent, entities, sentiment, urgency",
agent=triage_agent
)
research_task = Task(
description="""Based on the triage analysis, gather all information needed:
- Look up any order IDs mentioned
- Search knowledge base for relevant policies
- Compile complete context for response""",
expected_output="Complete research findings with order details and applicable policies",
agent=research_agent,
context=[triage_task] # Depends on triage output
)
response_task = Task(
description="""Write the customer response using the research findings.
Rules: empathetic, specific (include order numbers/amounts), under 100 words,
actionable next steps. Never fabricate information.""",
expected_output="A professional customer-facing response",
agent=response_agent,
context=[triage_task, research_task] # Has all context
)
return [triage_task, research_task, response_task]
# --- Create Crew ---
tasks = create_support_tasks("I ordered a MacBook (order #7845) and it still hasn't arrived. Very frustrated!")
crew = Crew(
agents=[triage_agent, research_agent, response_agent],
tasks=tasks,
process=Process.sequential, # One after another
verbose=True
)
# --- Run ---
result = crew.kickoff()
print(f"\n{'='*60}")
print(f"Final Response:\n{result}")
| Pattern | How It Works | Best For |
|---|---|---|
| Sequential | Agent A β Agent B β Agent C (pipeline) | Triage β Research β Respond |
| Hierarchical | Manager agent delegates to specialists | Manager routes to billing/shipping/product team |
| Parallel | Multiple agents work simultaneously | Search KB + lookup order at the same time |
| Consensus | Multiple agents vote on the best answer | QA: 3 agents draft response, pick the best |
# Manager agent decides which specialist to use
crew = Crew(
agents=[triage_agent, research_agent, response_agent],
tasks=tasks,
process=Process.hierarchical, # Manager delegates
manager_llm=ChatOllama(model="qwen2.5:3b"),
verbose=True
)
| Single Agent β | Multi-Agent β |
|---|---|
| Simple order status check | Complex complaint (multiple issues) |
| FAQ / policy questions | Tickets needing research + response + QA |
| Low-volume support | High-volume: route to specialized teams |
| Prototype / MVP | Production system with quality gates |
Upgrade your Week 2 agent with RAG, LangGraph workflows, and a Streamlit UI.
| # | Feature | Details |
|---|---|---|
| 1 | RAG Knowledge Base | Ingest policies.txt + faqs.txt into ChromaDB. At least 20 chunks. |
| 2 | LangGraph Workflow | classify β route (billing/shipping/product/escalate) β handle β respond |
| 3 | Tools (min 4) | search_kb, lookup_order, get_refund_policy, escalate_to_human |
| 4 | Memory | ConversationBufferWindowMemory (k=5) for multi-turn |
| 5 | Streamlit UI | Chat interface with message history display |
| 6 | Verbose Logging | Print agent reasoning to terminal for debugging |
# 1. Policy Query (RAG)
"What's your return policy for electronics?"
β Should search KB and cite: "15-day return window for electronics"
# 2. Order + Policy Combo
"My order #7845 arrived damaged. What can I do?"
β lookup_order + search_kb("damaged") β Offer refund + βΉ500 credit
# 3. Multi-Turn Memory
Turn 1: "I'm Rajesh, order #7845"
Turn 2: "Is it eligible for a refund?"
Turn 3: "Ok, process it"
β Agent remembers Rajesh and order across turns
# 4. Correct Routing
"I was charged twice" β billing handler
"Package not delivered" β shipping handler
"Phone screen cracked" β product handler
# 5. Escalation
"I want to speak to a manager"
β Should escalate, not try to handle
Final week: Week 4 β Integration & Production β Semantic Kernel (.NET), Angular + .NET + Agent, Testing, Deployment