Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions uc-0a/agents.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
# agents.md — UC-0A Complaint Classifier
# INSTRUCTIONS:
# 1. Open your AI tool
# 2. Paste the full contents of uc-0a/README.md
# 3. Use this prompt:
# "Read this UC README. Using the R.I.C.E framework, generate an
# agents.md YAML with four fields: role, intent, context, enforcement.
# Enforcement must include every rule listed under
# 'Enforcement Rules Your agents.md Must Include'.
# Output only valid YAML."
# 4. Paste the output below

role: >
[FILL IN]
An AI classifier for the City Operations team that reads municipal complaints and categorizes them. Its output directly feeds the Director's dashboard.

intent: >
[FILL IN]
To accurately classify each complaint into a predefined category, assign a priority level, provide a specific reason for the classification, and flag ambiguous complaints for review.

context: >
[FILL IN]
The agent must only use the provided complaint row (specifically the description and location fields) to make its determination. It must not use external knowledge or invent details not present in the complaint.

enforcement:
- "[FILL IN: category enum rule]"
- "[FILL IN: severity keyword rule — list the keywords]"
- "[FILL IN: reason field rule]"
- "[FILL IN: ambiguity refusal rule]"
- "[FILL IN: no invented categories rule]"
- "Category must be exactly one value from the allowed list: Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other. No variations."
- "Priority must be Urgent if description contains any severity keyword: injury, child, school, hospital, ambulance, fire, hazard, fell, collapse."
- "Every output row must include a reason field citing specific words from the description."
- "If category cannot be determined confidently — output `category: Other` and `flag: NEEDS_REVIEW`."
- "Never invent category names outside the allowed list."
137 changes: 124 additions & 13 deletions uc-0a/classifier.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,137 @@
"""
UC-0A — Complaint Classifier
classifier.py — Starter file

Build this using your AI coding tool:
1. Share agents.md, skills.md, and uc-0a/README.md
2. Ask the AI to implement this file
3. Run: python3 classifier.py --input ../data/city-test-files/test_pune.csv \
--output results_pune.csv
"""
import argparse
import csv
import os
import json
import time

def call_llm(prompt: str) -> str:
"""Uses Gemini API to classify the complaint based on the prompt."""
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
return json.dumps({
"category": "Other",
"priority": "Standard",
"reason": "[ERROR] GEMINI_API_KEY not set",
"flag": "NEEDS_REVIEW"
})

try:
import google.generativeai as genai
genai.configure(api_key=api_key)
# We request JSON response from Gemini
model = genai.GenerativeModel(
"gemini-1.5-flash",
generation_config={"response_mime_type": "application/json"}
)
response = model.generate_content(prompt)
return response.text
except Exception as e:
return json.dumps({
"category": "Other",
"priority": "Standard",
"reason": f"[ERROR] LLM Call failed: {str(e)}",
"flag": "NEEDS_REVIEW"
})

def classify_complaint(row: dict) -> dict:
def classify_complaint(row: dict, agents_text: str, skills_text: str) -> dict:
"""
Classify a single complaint row.
Returns dict with: complaint_id, category, priority, reason, flag
"""
raise NotImplementedError("Build this using your AI tool + agents.md")
description = row.get('description', '')
location = row.get('location', '')

# Error handling: vague/short descriptions -> Other + NEEDS_REVIEW
if not description or len(description.strip()) < 5:
return {
"complaint_id": row.get('complaint_id', ''),
"category": "Other",
"priority": "Standard",
"reason": "Vague or short description",
"flag": "NEEDS_REVIEW"
}

prompt = f"""You are the AI Complaint Classifier.

=== AGENTS DEFINITION ===
{agents_text}

=== SKILLS DEFINITION ===
{skills_text}

=== INPUT COMPLAINT ===
Location: {location}
Description: {description}

=== INSTRUCTIONS ===
Classify the complaint according to the schema and enforcement rules above.
Output strictly valid JSON with exactly the following keys:
"category", "priority", "reason", "flag"
"""

response_text = call_llm(prompt)

try:
result = json.loads(response_text)
result["complaint_id"] = row.get("complaint_id", "")
return result
except Exception as e:
return {
"complaint_id": row.get('complaint_id', ''),
"category": "Other",
"priority": "Standard",
"reason": f"Parsing failed: {str(e)}",
"flag": "NEEDS_REVIEW"
}

def batch_classify(input_path: str, output_path: str):
"""Read input CSV, classify each row, write results CSV."""
raise NotImplementedError("Build this using your AI tool + agents.md")

base_dir = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.join(base_dir, 'agents.md'), 'r', encoding='utf-8') as f:
agents_text = f.read()
with open(os.path.join(base_dir, 'skills.md'), 'r', encoding='utf-8') as f:
skills_text = f.read()
except FileNotFoundError:
print("Error: agents.md or skills.md not found. Ensure they are in the same directory.")
return

results = []

with open(input_path, mode='r', encoding='utf-8') as infile:
reader = csv.DictReader(infile)
for row in reader:
complaint_id = row.get('complaint_id')
# Error handling: malformed rows logged and skipped, processing continues
if not complaint_id:
print("Skipping malformed row missing complaint_id.")
continue

print(f"Classifying {complaint_id}...")
result_dict = classify_complaint(row, agents_text, skills_text)

clean_result = {
"complaint_id": result_dict.get("complaint_id", ""),
"category": result_dict.get("category", "Other"),
"priority": result_dict.get("priority", "Standard"),
"reason": result_dict.get("reason", "No reason provided"),
"flag": result_dict.get("flag", "")
}
results.append(clean_result)

# Minimal sleep to avoid Gemini rate limits on the free tier
time.sleep(1.0)

if not results:
print("No results to write.")
return

with open(output_path, mode='w', encoding='utf-8', newline='') as outfile:
fieldnames = ["complaint_id", "category", "priority", "reason", "flag"]
writer = csv.DictWriter(outfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="UC-0A Complaint Classifier")
Expand Down
16 changes: 16 additions & 0 deletions uc-0a/results_pune.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
complaint_id,category,priority,reason,flag
PM-202401,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202402,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202406,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202408,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202410,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202411,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202413,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202418,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202419,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202420,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202427,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202428,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202430,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202433,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
PM-202446,Other,Standard,[ERROR] GEMINI_API_KEY not set,NEEDS_REVIEW
19 changes: 8 additions & 11 deletions uc-0a/skills.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# skills.md — UC-0A Complaint Classifier
# INSTRUCTIONS: Same as agents.md — paste README into AI, ask for skills.md YAML

skills:
- name: classify_complaint
description: "[FILL IN]"
input: "[FILL IN]"
output: "[FILL IN]"
error_handling: "[FILL IN]"
description: "Reads a single complaint and classifies it according to the predefined schema and enforcement rules, preventing taxonomy drift, severity blindness, missing justification, and hallucinated sub-categories."
input: "One complaint row (dict with description, location fields)"
output: "Dict with category, priority, reason, flag"
error_handling: "If the description is vague/short, or if ambiguity causes false confidence, it must fall back to outputting `category: Other` and `flag: NEEDS_REVIEW`."

- name: batch_classify
description: "[FILL IN]"
input: "[FILL IN]"
output: "[FILL IN]"
error_handling: "[FILL IN]"
description: "Reads a CSV file containing multiple complaints, applies the classify_complaint skill to each row, and writes the results to a specified output CSV file."
input: "Path to test CSV file"
output: "Path to results CSV file"
error_handling: "If malformed rows are encountered, they must be logged and skipped, allowing processing of the remaining rows to continue."
33 changes: 8 additions & 25 deletions uc-mcp/agents.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
# agents.md — UC-MCP MCP Server
# INSTRUCTIONS:
# 1. Open your AI tool
# 2. Paste the full contents of uc-mcp/README.md
# 3. Use this prompt:
# "Read this UC README. Using the R.I.C.E framework, generate an
# agents.md YAML with four fields: role, intent, context, enforcement.
# The enforcement must include every rule listed under
# 'Enforcement Rules Your agents.md Must Include'.
# Output only valid YAML."
# 4. Paste the output below, replacing this placeholder
# 5. Pay special attention to enforcement rule 1 — the tool description
# must state exact document scope

role: >
[FILL IN: Who is this agent? What layer of the stack does it operate at?
Hint: an MCP server that exposes policy retrieval as a tool]
An MCP (Model Context Protocol) Server exposing a retrieval-augmented generation tool for CMC policy queries.

intent: >
[FILL IN: What does a correctly implemented MCP server produce?
Hint: JSON-RPC compliant responses, scoped tool description, correct refusals]
To strictly define the scope of the `query_policy_documents` tool to prevent agents from hallucinating or answering out-of-scope questions, and to serve standard JSON-RPC 2.0 requests over HTTP.

context: >
[FILL IN: What does this server have access to?
Hint: RAG server results only — no direct LLM calls, no outside knowledge]
The MCP server must interact exclusively through standard JSON-RPC requests for `tools/list` and `tools/call`, routing calls to the underlying RAG server.

enforcement:
- "[FILL IN: Tool description scope rule]"
- "[FILL IN: Refusal documentation rule]"
- "[FILL IN: inputSchema required field rule]"
- "[FILL IN: isError on failure rule]"
- "[FILL IN: HTTP 200 for all JSON-RPC responses rule]"
- "Tool description must state the exact document scope: CMC HR Leave Policy, IT Acceptable Use Policy, Finance Reimbursement Policy."
- "Tool description must state what it cannot answer: questions outside these three documents return the refusal template."
- "inputSchema must require `question` as a non-empty string."
- "Error responses must use `isError: true` — never return an empty content array on failure."
- "The server must return HTTP 200 for all JSON-RPC responses including errors — transport errors use HTTP 4xx/5xx, application errors use JSON-RPC error objects."
Loading