Step 1: The Support Tools
Building the shared tool layer
All three agent patterns will use the same tools — this is critical for a fair comparison. The tools represent real SaaS support operations:
The Acme SaaS scenario
Acme SaaS sells a project management platform. Their support agent needs to:
- Look up customer accounts by email (subscription tier, seat usage, billing)
- Search the knowledge base for product info (pricing, features, policies)
- Create support tickets when issues need human follow-up
These tools simulate a real support stack. In production, they'd call your CRM API, search a vector database, and POST to your ticketing system.
Why the same tools matter
The whole point of this lab is comparing reasoning strategies, not tool implementations. By keeping the tools identical across all three agents, we isolate the variable: how the LLM decides when and in what order to call tools.
Your task
Create tools.py with three tool functions decorated with @tool:
check_account(email: str) -> str
- Simulates a CRM lookup. Takes an email, returns account details.
- Use the provided
ACCOUNTSdictionary for mock data. - Return "No account found" for unknown emails.
search_kb(query: str) -> str
- Performs semantic search over the knowledge base using Milvus (the vector DB you used in Lab 2).
- Embed the query with
embedder.embed_query(query), then search Milvus for the top 2 results. - Return the matched article texts joined with newlines. Return "No results found" if nothing matches.
- This is real RAG-style retrieval — it understands meaning, not just keywords. "Is my data safe?" will match the security article even without the word "security".
create_ticket(subject: str, priority: str, description: str) -> str
- Simulates creating a support ticket. Takes subject, priority (low/medium/high), and description.
- Returns a confirmation with a mock ticket ID.
Test all three tools with the provided test cases.
NCP-AAI exam note: Tool design is tested in the Development domain (15%). Key concepts: tools must have clear docstrings (the LLM reads them to decide when to call), input types should be simple (strings, not complex objects), and tools should return structured information the LLM can reason about. Notice how
search_kbwraps a full RAG pipeline (embed → vector search → return) inside a single tool — this is the standard production pattern.
tools.py, the file you edit102 lines
# Shared tools for all three agent patterns
from langchain_core.tools import tool
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
from pymilvus import MilvusClient
# Mock CRM data — simulates a real customer database
ACCOUNTS = {
"[email protected]": {
"name": "Alice Chen",
"company": "StartupIO",
"plan": "Pro",
"seats_used": 3,
"seats_total": 5,
"mrr": 49,
"billing_cycle": "monthly",
"next_billing": "2026-05-01",
"status": "active",
},
"[email protected]": {
"name": "Bob Martinez",
"company": "EnterpriseCo",
"plan": "Business",
"seats_used": 18,
"seats_total": 25,
"mrr": 149,
"billing_cycle": "annual",
"next_billing": "2027-01-15",
"status": "active",
},
"[email protected]": {
"name": "Carol Kim",
"company": "BigCorp",
"plan": "Enterprise",
"seats_used": 142,
"seats_total": 200,
"mrr": 899,
"billing_cycle": "annual",
"next_billing": "2026-09-01",
"status": "active",
},
}
# Knowledge base articles — indexed in Milvus for semantic search
KB_ARTICLES = [
"Plans: Starter $19/mo (2 users), Pro $49/mo (5 users), Business $149/mo (25 users), Enterprise custom. All include SSO. Annual billing saves 20%.",
"30-day money-back guarantee on all plans. Pro-rated refunds for annual subscriptions. Process takes 5-7 business days.",
"Native integrations: Slack, Jira, GitHub, Salesforce, HubSpot. REST API on Business+. Webhooks on all plans. OAuth2 for SSO.",
"SOC 2 Type II certified. AES-256 encryption at rest, TLS 1.3 in transit. GDPR compliant. EU data residency available on Enterprise.",
"Upgrades are instant. You pay the prorated difference for the current billing cycle. Downgrades take effect at next billing date.",
"REST API available on Business and Enterprise plans. Rate limit: 1000 req/min. API keys managed in Settings > Integrations > API.",
]
# --- Set up Milvus vector store for the KB ---
embedder = NVIDIAEmbeddings(
base_url="http://nim-proxy.labs.svc:8080/v1",
model="nvidia/llama-nemotron-embed-vl-1b-v2",
dimensions=1024,
api_key="not-needed",
)
milvus = MilvusClient("/tmp/support_kb.db")
# Index KB articles (idempotent — drops and recreates)
if milvus.has_collection("kb"):
milvus.drop_collection("kb")
vectors = embedder.embed_documents(KB_ARTICLES)
milvus.create_collection(collection_name="kb", dimension=len(vectors[0]), auto_id=True)
milvus.insert(collection_name="kb", data=[
{"vector": vec, "text": text} for vec, text in zip(vectors, KB_ARTICLES)
])
print(f"KB indexed: {len(KB_ARTICLES)} articles in Milvus")
# TODO: Implement the three tools
@tool
def check_account(email: str) -> str:
"""Look up a customer account by email. Returns subscription details including plan, seats, billing, and status."""
______
@tool
def search_kb(query: str) -> str:
"""Search the knowledge base for product information, pricing, policies, and how-to articles. Uses semantic search — understands meaning, not just keywords."""
______
@tool
def create_ticket(subject: str, priority: str, description: str) -> str:
"""Create a support ticket for issues requiring human follow-up. Priority: low, medium, or high."""
______
if __name__ == "__main__":
# Test
print("\n=== Account Lookup ===")
print(check_account.invoke({"email": "[email protected]"}))
print(check_account.invoke({"email": "[email protected]"}))
print("\n=== KB Search (semantic) ===")
print(search_kb.invoke({"query": "what is the pricing for Business plan?"}))
print(search_kb.invoke({"query": "do you support Slack integration?"}))
print(search_kb.invoke({"query": "is my data safe and GDPR compliant?"}))
print("\n=== Ticket Creation ===")
print(create_ticket.invoke({"subject": "SSO not working", "priority": "high", "description": "Customer reports SAML SSO login fails after IdP update"}))