Spaces:
Sleeping
Sleeping
File size: 11,345 Bytes
aa4ac8d fc4553e aa4ac8d fc4553e aa4ac8d fc4553e aa4ac8d fc4553e aa4ac8d fc4553e aa4ac8d fc4553e 5206349 1dc1a72 fc4553e 03e9d51 5206349 1dc1a72 5206349 fc4553e 5206349 c958d02 fc4553e 5206349 e9a3932 5206349 fc4553e 5206349 fc4553e e9a3932 5206349 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e aa4ac8d fc4553e aa4ac8d fc4553e aa4ac8d fc4553e b7cdb59 fc4553e aa4ac8d fc4553e 5bf6a85 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e e9a3932 fc4553e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers"
os.environ["HF_HOME"] = "/tmp/huggingface"
os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/sentence_transformers"
os.environ["TORCH_HOME"] = "/tmp/torch"
import requests
import torch
import time
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import numpy as np
from sentence_transformers import SentenceTransformer
import chromadb
from chromadb.config import Settings
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import Tool
from langchain.prompts import PromptTemplate
import threading
from datetime import datetime
# Environment
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
ROUTING_URL = os.environ.get("ROUTING_URL")
SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
# Label Dictionary
LABEL_DICTIONARY = {
"I1": "Low Impact", "I2": "Medium Impact", "I3": "High Impact", "I4": "Critical Impact",
"U1": "Low Urgency", "U2": "Medium Urgency", "U3": "High Urgency", "U4": "Critical Urgency",
"T1": "Information", "T2": "Incident", "T3": "Problem", "T4": "Request", "T5": "Question"
}
# Classification Model
clf_model_name = "DavinciTech/BERT_Categorizer"
clf_tokenizer = AutoTokenizer.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
clf_model = AutoModelForSequenceClassification.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
def classify_ticket(text):
"""Classify ticket into Impact, Urgency, and Type."""
inputs = clf_tokenizer(text, return_tensors="pt", truncation=True)
outputs = clf_model(**inputs)
logits = outputs.logits[0]
impact_idx = torch.argmax(logits[:4]).item() + 1
urgency_idx = torch.argmax(logits[4:8]).item() + 1
type_idx = torch.argmax(logits[8:]).item() + 1
return {
"impact": LABEL_DICTIONARY[f"I{impact_idx}"],
"urgency": LABEL_DICTIONARY[f"U{urgency_idx}"],
"type": LABEL_DICTIONARY[f"T{type_idx}"]
}
# Routing Function
def call_routing(text, retries=3, delay=5):
"""Route ticket to appropriate department."""
url = ROUTING_URL if ROUTING_URL else f"{SPACE_URL}/route"
for attempt in range(retries):
try:
resp = requests.post(url, json={"text": text}, timeout=30)
resp.raise_for_status()
return resp.json().get("department", "General IT")
except Exception as e:
print(f"Routing attempt {attempt+1} failed: {e}")
if attempt < retries - 1:
time.sleep(delay)
return "General IT"
# Knowledge Base
CHROMA_PATH = "/tmp/chroma"
COLLECTION_NAME = "knowledge_base"
kb_collection = None
kb_lock = threading.Lock()
encoder = SentenceTransformer("all-MiniLM-L6-v2", cache_folder="/tmp/sentence_transformers")
def get_kb_collection():
global kb_collection
if kb_collection is None:
with kb_lock:
if kb_collection is None:
try:
chroma_client = chromadb.PersistentClient(
path=CHROMA_PATH,
settings=Settings(anonymized_telemetry=False, allow_reset=True)
)
kb_collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
except Exception as e:
print(f"Could not get KB collection: {e}")
return kb_collection
def query_kb(text: str, top_k: int = 1):
"""Query KB and return answer with confidence."""
collection = get_kb_collection()
if not collection or collection.count() == 0:
return {"answer": None, "confidence": 0.0}
try:
query_embedding = encoder.encode([text])[0].tolist()
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k,
include=["documents", "distances", "metadatas"]
)
if not results or not results.get("documents") or len(results["documents"][0]) == 0:
return {"answer": None, "confidence": 0.0}
answer = results["documents"][0][0]
distance = results["distances"][0][0] if results.get("distances") else 1.0
confidence = max(0.0, 1.0 - (distance / 2.0))
return {"answer": answer, "confidence": round(float(confidence), 3)}
except Exception as e:
print(f"KB query failed: {e}")
return {"answer": None, "confidence": 0.0}
# Gemini LLM
llm = ChatGoogleGenerativeAI(
model="gemini-2.0-flash-exp",
temperature=0.3,
google_api_key=GEMINI_API_KEY
)
# Global conversation storage
conversations = {}
# Tool Functions for Agent
def classify_tool(query: str) -> str:
"""Classifies IT ticket into impact, urgency, and type. Use this FIRST."""
result = classify_ticket(query)
return f"Impact: {result['impact']}, Urgency: {result['urgency']}, Type: {result['type']}"
def routing_tool(query: str) -> str:
"""Determines which IT department should handle this ticket. Use this SECOND."""
dept = call_routing(query)
return f"Department: {dept}"
def kb_tool(query: str) -> str:
"""Searches knowledge base for solutions. Returns answer and confidence score. Use this THIRD."""
result = query_kb(query)
if result["answer"] and result["confidence"] > 0.5:
return f"[KB Confidence: {result['confidence']}]\n{result['answer']}"
return f"[KB Confidence: {result['confidence']}] No relevant solution found in knowledge base."
def escalation_tool(reason: str) -> str:
"""Escalates ticket to human agent. Use ONLY when KB confidence < 0.75 OR user says solution didn't work."""
ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
return f"ESCALATED: Ticket {ticket_id} created. Reason: {reason}. Human agent will respond in 2-4 hours."
# Define Tools
tools = [
Tool(
name="ClassifyTicket",
func=classify_tool,
description="Classifies IT ticket severity. Input: user's issue description. Use this FIRST for every new ticket."
),
Tool(
name="RouteTicket",
func=routing_tool,
description="Determines responsible department. Input: user's issue description. Use this SECOND after classification."
),
Tool(
name="SearchKnowledgeBase",
func=kb_tool,
description="Searches for solutions with confidence score. Input: user's issue description. Use this THIRD to find solutions."
),
Tool(
name="EscalateToHuman",
func=escalation_tool,
description="Creates escalation ticket. Input: brief reason. Use ONLY when: 1) KB confidence < 0.75, 2) User reports solution failed, 3) Complex/unusual issue."
)
]
# Agent Prompt Template
AGENT_PROMPT = """You are an intelligent IT Helpdesk AI Agent. Resolve tickets efficiently using available tools.
TOOLS:
{tools}
WORKFLOW FOR NEW TICKETS:
1. Use ClassifyTicket to understand severity
2. Use RouteTicket to determine responsible team
3. Use SearchKnowledgeBase to find solutions
4. Evaluate KB confidence score:
- If confidence >= 0.75: Provide the solution to user
- If confidence < 0.75: Use EscalateToHuman with clear reason
WORKFLOW FOR FOLLOW-UPS:
- If user confirms solution worked: Thank them and close positively
- If user says solution didn't work: Use SearchKnowledgeBase again OR EscalateToHuman
- For clarification questions: Answer directly
RULES:
- Be professional, empathetic, and clear
- Provide step-by-step instructions
- Trust high-confidence KB solutions (>= 0.45)
- Don't escalate prematurely
- Remember conversation context
Use this format:
Question: the input question
Thought: think about what to do
Action: the action to take, must be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (repeat as needed)
Thought: I now know the final answer
Final Answer: the final answer
Begin!
Question: {input}
Thought: {agent_scratchpad}"""
prompt = PromptTemplate.from_template(AGENT_PROMPT)
# Create Agent
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=6,
handle_parsing_errors=True,
return_intermediate_steps=True
)
# Main Processing Function
def process_with_agent(user_message: str, conversation_id: str = None):
"""Process user message through autonomous AI agent."""
if not conversation_id:
conversation_id = f"conv_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hash(user_message) % 10000}"
if conversation_id not in conversations:
conversations[conversation_id] = {
"messages": [],
"ticket_info": {},
"created_at": datetime.now().isoformat()
}
conv = conversations[conversation_id]
conv["messages"].append({
"role": "user",
"content": user_message,
"timestamp": datetime.now().isoformat()
})
# Build context for follow-ups
if len(conv["messages"]) > 1:
context = f"PREVIOUS CONVERSATION:\n"
for msg in conv["messages"][-5:-1]:
context += f"{msg['role'].upper()}: {msg['content']}\n"
context += f"\nCURRENT USER MESSAGE: {user_message}"
agent_input = context
else:
agent_input = user_message
try:
result = agent_executor.invoke({"input": agent_input})
agent_response = result.get("output", "I apologize, I encountered an error.")
intermediate_steps = result.get("intermediate_steps", [])
status = "resolved"
if "ESCALATED" in agent_response or "TKT-" in agent_response:
status = "escalated"
elif len(conv["messages"]) > 1:
status = "in_progress"
reasoning_trace = []
for action, observation in intermediate_steps:
reasoning_trace.append({
"tool": action.tool,
"input": action.tool_input,
"output": str(observation)[:200]
})
conv["messages"].append({
"role": "assistant",
"content": agent_response,
"timestamp": datetime.now().isoformat(),
"reasoning": reasoning_trace
})
return {
"conversation_id": conversation_id,
"response": agent_response,
"status": status,
"message_count": len(conv["messages"]),
"reasoning_trace": reasoning_trace
}
except Exception as e:
print(f"Agent error: {e}")
import traceback
traceback.print_exc()
error_response = "I apologize, I encountered an error. Please try again."
conv["messages"].append({
"role": "assistant",
"content": error_response,
"timestamp": datetime.now().isoformat()
})
return {
"conversation_id": conversation_id,
"response": error_response,
"status": "error",
"error": str(e)
}
def get_conversation_history(conversation_id: str):
"""Get conversation history."""
return conversations.get(conversation_id) |