Spaces:
Sleeping
Sleeping
Update agent_langchain.py
Browse files- agent_langchain.py +153 -28
agent_langchain.py
CHANGED
|
@@ -5,7 +5,6 @@ os.environ["HF_HOME"] = "/tmp/huggingface"
|
|
| 5 |
os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/sentence_transformers"
|
| 6 |
os.environ["TORCH_HOME"] = "/tmp/torch"
|
| 7 |
|
| 8 |
-
import json
|
| 9 |
import requests
|
| 10 |
import torch
|
| 11 |
import time
|
|
@@ -29,25 +28,18 @@ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
|
| 29 |
ROUTING_URL = os.environ.get("ROUTING_URL")
|
| 30 |
SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
|
| 31 |
FIREBASE_CREDS_PATH = os.environ.get("FIREBASE_CREDS_PATH")
|
| 32 |
-
firebase_creds_json = os.getenv("FIREBASE_CREDS_JSON")
|
| 33 |
|
| 34 |
# Initialize Firebase
|
| 35 |
db = None
|
| 36 |
-
if
|
| 37 |
try:
|
| 38 |
-
creds_dict = json.loads(firebase_creds_json)
|
| 39 |
-
cred = credentials.Certificate(creds_dict)
|
| 40 |
-
|
| 41 |
if not firebase_admin._apps:
|
|
|
|
| 42 |
firebase_admin.initialize_app(cred)
|
| 43 |
db = firestore.client()
|
| 44 |
-
print("β
Firebase initialized
|
| 45 |
except Exception as e:
|
| 46 |
-
import traceback
|
| 47 |
print(f"β οΈ Firebase init failed: {e}")
|
| 48 |
-
traceback.print_exc()
|
| 49 |
-
else:
|
| 50 |
-
print("β οΈ FIREBASE_CREDS_JSON not found in environment variables")
|
| 51 |
|
| 52 |
# Label Dictionary
|
| 53 |
LABEL_DICTIONARY = {
|
|
@@ -189,6 +181,45 @@ def escalation_tool(reason: str) -> str:
|
|
| 189 |
ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
| 190 |
return f"ESCALATED: Ticket {ticket_id} created. Reason: {reason}. Human agent will respond in 2-4 hours."
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
# Define Tools with better descriptions
|
| 193 |
tools = [
|
| 194 |
Tool(
|
|
@@ -206,10 +237,20 @@ tools = [
|
|
| 206 |
func=kb_tool,
|
| 207 |
description="Searches internal knowledge base for solutions. Returns answer with confidence score (0-1). Use this when you need to find technical solutions or troubleshooting steps."
|
| 208 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
Tool(
|
| 210 |
name="EscalateToHuman",
|
| 211 |
func=escalation_tool,
|
| 212 |
-
description="Creates an escalation ticket for human agent review. Use this ONLY when: 1) KB confidence score is below 0.75, 2) Issue is highly complex or unusual, 3) User confirms solution didn't work."
|
| 213 |
)
|
| 214 |
]
|
| 215 |
|
|
@@ -224,32 +265,70 @@ TOOL NAMES: {tool_names}
|
|
| 224 |
GUIDING PRINCIPLES:
|
| 225 |
1. **Think autonomously** - Decide which tools you need based on the situation, not a fixed sequence
|
| 226 |
2. **Be efficient** - Only use tools when they add value to solving the user's problem
|
| 227 |
-
3. **Trust high-confidence solutions** - If KB returns confidence >= 0.75, provide that solution
|
| 228 |
-
4. **
|
| 229 |
-
5. **
|
| 230 |
-
6. **
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
FORMAT:
|
| 239 |
Question: the user's input
|
| 240 |
Thought: your reasoning about what to do next
|
| 241 |
Action: the tool to use (must be one of [{tool_names}])
|
| 242 |
Action Input: the input for that tool
|
| 243 |
-
Observation: the
|
| 244 |
... (repeat Thought/Action/Observation as needed)
|
| 245 |
Thought: I now have enough information to respond
|
| 246 |
Final Answer: your complete response to the user
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
IMPORTANT:
|
| 249 |
- Don't mention tool names or technical process to users
|
| 250 |
- Provide clear, step-by-step instructions
|
| 251 |
-
-
|
|
|
|
| 252 |
- Be conversational and helpful
|
|
|
|
| 253 |
|
| 254 |
Begin!
|
| 255 |
|
|
@@ -322,16 +401,25 @@ def process_with_agent(
|
|
| 322 |
status = "in_progress"
|
| 323 |
should_save = False
|
| 324 |
|
| 325 |
-
# Check for
|
| 326 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
status = "resolved"
|
| 328 |
should_save = True
|
| 329 |
-
|
|
|
|
| 330 |
status = "escalated"
|
| 331 |
-
should_save = True
|
| 332 |
|
| 333 |
# Extract ticket info from tools
|
| 334 |
ticket_info = conv.get("ticket_info", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
for action, observation in intermediate_steps:
|
| 336 |
if action.tool == "ClassifyTicket":
|
| 337 |
# Parse classification
|
|
@@ -343,10 +431,47 @@ def process_with_agent(
|
|
| 343 |
ticket_info["urgency"] = part.split(": ")[1]
|
| 344 |
elif "Type:" in part:
|
| 345 |
ticket_info["type"] = part.split(": ")[1]
|
|
|
|
| 346 |
elif action.tool == "RouteTicket":
|
| 347 |
ticket_info["department"] = str(observation).replace("Department: ", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
conv["ticket_info"] = ticket_info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
conv["status"] = status
|
| 351 |
|
| 352 |
reasoning_trace = []
|
|
|
|
| 5 |
os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/sentence_transformers"
|
| 6 |
os.environ["TORCH_HOME"] = "/tmp/torch"
|
| 7 |
|
|
|
|
| 8 |
import requests
|
| 9 |
import torch
|
| 10 |
import time
|
|
|
|
| 28 |
ROUTING_URL = os.environ.get("ROUTING_URL")
|
| 29 |
SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
|
| 30 |
FIREBASE_CREDS_PATH = os.environ.get("FIREBASE_CREDS_PATH")
|
|
|
|
| 31 |
|
| 32 |
# Initialize Firebase
|
| 33 |
db = None
|
| 34 |
+
if FIREBASE_CREDS_PATH and os.path.exists(FIREBASE_CREDS_PATH):
|
| 35 |
try:
|
|
|
|
|
|
|
|
|
|
| 36 |
if not firebase_admin._apps:
|
| 37 |
+
cred = credentials.Certificate(FIREBASE_CREDS_PATH)
|
| 38 |
firebase_admin.initialize_app(cred)
|
| 39 |
db = firestore.client()
|
| 40 |
+
print("β
Firebase initialized")
|
| 41 |
except Exception as e:
|
|
|
|
| 42 |
print(f"β οΈ Firebase init failed: {e}")
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
# Label Dictionary
|
| 45 |
LABEL_DICTIONARY = {
|
|
|
|
| 181 |
ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
| 182 |
return f"ESCALATED: Ticket {ticket_id} created. Reason: {reason}. Human agent will respond in 2-4 hours."
|
| 183 |
|
| 184 |
+
def clarifying_questions_tool(issue_summary: str) -> str:
|
| 185 |
+
"""Generates clarifying questions when more information needed. Use when: User description is vague, Need to diagnose issue better, Before searching KB."""
|
| 186 |
+
prompt = f"""You are an IT helpdesk expert. Based on this issue, generate 2-3 specific clarifying questions to better diagnose the problem.
|
| 187 |
+
|
| 188 |
+
Issue: {issue_summary}
|
| 189 |
+
|
| 190 |
+
Generate focused questions that would help identify:
|
| 191 |
+
- Specific symptoms or error messages
|
| 192 |
+
- When the issue started
|
| 193 |
+
- What the user has already tried
|
| 194 |
+
- Environment details (OS, browser, device)
|
| 195 |
+
|
| 196 |
+
Questions (numbered list):"""
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
response = llm.invoke(prompt)
|
| 200 |
+
return f"CLARIFYING QUESTIONS:\n{response.content.strip()}"
|
| 201 |
+
except Exception as e:
|
| 202 |
+
return "Please provide more details: 1) What specific error do you see? 2) When did this start? 3) Have you tried any solutions yet?"
|
| 203 |
+
|
| 204 |
+
def check_solution_tool(user_feedback: str) -> str:
|
| 205 |
+
"""Analyzes user feedback to determine if provided solution worked. Use after: Giving a solution, User responds with feedback."""
|
| 206 |
+
feedback_lower = user_feedback.lower()
|
| 207 |
+
|
| 208 |
+
# Success indicators
|
| 209 |
+
success_keywords = ["worked", "fixed", "solved", "resolved", "success", "thank", "thanks", "great", "perfect", "awesome"]
|
| 210 |
+
# Failure indicators
|
| 211 |
+
failure_keywords = ["didn't work", "not working", "still", "failed", "broken", "issue persists", "same problem", "no luck", "doesn't work"]
|
| 212 |
+
|
| 213 |
+
success_count = sum(1 for kw in success_keywords if kw in feedback_lower)
|
| 214 |
+
failure_count = sum(1 for kw in failure_keywords if kw in feedback_lower)
|
| 215 |
+
|
| 216 |
+
if success_count > failure_count and success_count > 0:
|
| 217 |
+
return "SOLUTION_WORKED: User confirmed the solution resolved their issue. You can close this ticket positively."
|
| 218 |
+
elif failure_count > success_count and failure_count > 0:
|
| 219 |
+
return "SOLUTION_FAILED: User confirmed the solution did NOT work. You should try alternative solutions or escalate."
|
| 220 |
+
else:
|
| 221 |
+
return "UNCLEAR: Cannot determine if solution worked. Ask the user directly: 'Did that solution work for you?'"
|
| 222 |
+
|
| 223 |
# Define Tools with better descriptions
|
| 224 |
tools = [
|
| 225 |
Tool(
|
|
|
|
| 237 |
func=kb_tool,
|
| 238 |
description="Searches internal knowledge base for solutions. Returns answer with confidence score (0-1). Use this when you need to find technical solutions or troubleshooting steps."
|
| 239 |
),
|
| 240 |
+
Tool(
|
| 241 |
+
name="AskClarifyingQuestions",
|
| 242 |
+
func=clarifying_questions_tool,
|
| 243 |
+
description="Generates 2-3 specific questions to better understand the user's problem. Use when: user's description is vague, you need more details to diagnose, or before searching KB with unclear symptoms."
|
| 244 |
+
),
|
| 245 |
+
Tool(
|
| 246 |
+
name="CheckSolutionEffectiveness",
|
| 247 |
+
func=check_solution_tool,
|
| 248 |
+
description="Analyzes user's feedback to determine if the provided solution worked. Use AFTER giving a solution and user responds. Returns SOLUTION_WORKED, SOLUTION_FAILED, or UNCLEAR."
|
| 249 |
+
),
|
| 250 |
Tool(
|
| 251 |
name="EscalateToHuman",
|
| 252 |
func=escalation_tool,
|
| 253 |
+
description="Creates an escalation ticket for human agent review. Use this ONLY when: 1) KB confidence score is below 0.75, 2) Issue is highly complex or unusual, 3) User confirms solution didn't work, 4) User explicitly requests human help."
|
| 254 |
)
|
| 255 |
]
|
| 256 |
|
|
|
|
| 265 |
GUIDING PRINCIPLES:
|
| 266 |
1. **Think autonomously** - Decide which tools you need based on the situation, not a fixed sequence
|
| 267 |
2. **Be efficient** - Only use tools when they add value to solving the user's problem
|
| 268 |
+
3. **Trust high-confidence solutions** - If KB returns confidence >= 0.75, provide that solution directly
|
| 269 |
+
4. **Exhaust KB options first** - Try searching KB before escalating. Only escalate if KB truly cannot help
|
| 270 |
+
5. **Escalate wisely** - Only escalate when truly necessary (low KB confidence, complex issues, or failed solutions)
|
| 271 |
+
6. **Maintain context** - Remember previous conversation history when handling follow-ups
|
| 272 |
+
7. **Be empathetic** - Users are frustrated when things break; be professional and supportive
|
| 273 |
+
8. **Be Useful** - If low KB confidence, try answering using general knowledge and escalate
|
| 274 |
+
|
| 275 |
+
CRITICAL DECISION FRAMEWORK FOR KB CONFIDENCE:
|
| 276 |
+
|
| 277 |
+
**KB Confidence >= 0.75 (HIGH):**
|
| 278 |
+
- β
PROVIDE THE SOLUTION IMMEDIATELY
|
| 279 |
+
- β DO NOT escalate
|
| 280 |
+
- β DO NOT use EscalateToHuman tool
|
| 281 |
+
- This is a good solution - trust it!
|
| 282 |
+
|
| 283 |
+
**KB Confidence 0.5-0.74 (MEDIUM):**
|
| 284 |
+
- Consider the issue complexity
|
| 285 |
+
- For common issues (password, printer, etc): Provide the solution
|
| 286 |
+
- For critical issues: Provide solution + mention you can escalate if it doesn't work
|
| 287 |
+
- DO NOT immediately escalate - give the solution a chance first
|
| 288 |
+
|
| 289 |
+
**KB Confidence < 0.5 (LOW) OR No KB Match:**
|
| 290 |
+
- Try to help with general IT knowledge if possible
|
| 291 |
+
- If you cannot help OR issue is highly complex β Then escalate
|
| 292 |
+
- Use EscalateToHuman tool only as last resort
|
| 293 |
+
|
| 294 |
+
WORKFLOW FOR NEW TICKETS:
|
| 295 |
+
1. Understand the issue (classify if needed)
|
| 296 |
+
2. Search KB for solutions
|
| 297 |
+
3. Evaluate KB confidence:
|
| 298 |
+
- High (β₯0.75): Provide solution, DON'T escalate
|
| 299 |
+
- Medium (0.5-0.74): Provide solution, offer escalation as backup
|
| 300 |
+
- Low (<0.5): Try general knowledge OR escalate
|
| 301 |
+
4. Only use EscalateToHuman if KB has no solution AND you can't help
|
| 302 |
+
|
| 303 |
+
WORKFLOW FOR FOLLOW-UPS:
|
| 304 |
+
- If user says solution worked β Close positively, NO escalation needed
|
| 305 |
+
- If user says solution failed β Try searching KB with different query OR escalate
|
| 306 |
+
- For clarification questions β Answer directly
|
| 307 |
|
| 308 |
FORMAT:
|
| 309 |
Question: the user's input
|
| 310 |
Thought: your reasoning about what to do next
|
| 311 |
Action: the tool to use (must be one of [{tool_names}])
|
| 312 |
Action Input: the input for that tool
|
| 313 |
+
Observation: the result of the action
|
| 314 |
... (repeat Thought/Action/Observation as needed)
|
| 315 |
Thought: I now have enough information to respond
|
| 316 |
Final Answer: your complete response to the user
|
| 317 |
|
| 318 |
+
CRITICAL: When you have enough information to answer the user, you MUST use this EXACT format:
|
| 319 |
+
|
| 320 |
+
Thought: I now have enough information to respond
|
| 321 |
+
Final Answer: [your complete response to the user]
|
| 322 |
+
|
| 323 |
+
Do NOT add any additional "Action:" or "Thought:" after "Final Answer:". The conversation ends with "Final Answer:".
|
| 324 |
+
|
| 325 |
IMPORTANT:
|
| 326 |
- Don't mention tool names or technical process to users
|
| 327 |
- Provide clear, step-by-step instructions
|
| 328 |
+
- Don't escalate if you have a working solution (confidence β₯ 0.75)
|
| 329 |
+
- Trust high-confidence KB solutions - they are tested and verified
|
| 330 |
- Be conversational and helpful
|
| 331 |
+
- STOP after "Final Answer:" - do not continue the loop
|
| 332 |
|
| 333 |
Begin!
|
| 334 |
|
|
|
|
| 401 |
status = "in_progress"
|
| 402 |
should_save = False
|
| 403 |
|
| 404 |
+
# Check for escalation FIRST (higher priority)
|
| 405 |
+
if "ESCALATED" in agent_response or "TKT-" in agent_response:
|
| 406 |
+
status = "escalated"
|
| 407 |
+
should_save = True # β
FIX: Save escalated tickets too!
|
| 408 |
+
# Then check for resolution indicators
|
| 409 |
+
elif any(phrase in agent_response.lower() for phrase in ["resolved", "you're all set", "should work now", "problem solved", "glad that worked"]):
|
| 410 |
status = "resolved"
|
| 411 |
should_save = True
|
| 412 |
+
# If agent explicitly mentions escalation/forwarding to team
|
| 413 |
+
elif any(phrase in agent_response.lower() for phrase in ["sent your request to", "forwarded to", "escalated to", "will be in touch", "team will contact"]):
|
| 414 |
status = "escalated"
|
| 415 |
+
should_save = True # β
FIX: Save when forwarded to team
|
| 416 |
|
| 417 |
# Extract ticket info from tools
|
| 418 |
ticket_info = conv.get("ticket_info", {})
|
| 419 |
+
used_escalation_tool = False # Track if escalation tool was used
|
| 420 |
+
kb_confidence = 0.0 # Track KB confidence
|
| 421 |
+
kb_was_searched = False # Track if KB was searched
|
| 422 |
+
|
| 423 |
for action, observation in intermediate_steps:
|
| 424 |
if action.tool == "ClassifyTicket":
|
| 425 |
# Parse classification
|
|
|
|
| 431 |
ticket_info["urgency"] = part.split(": ")[1]
|
| 432 |
elif "Type:" in part:
|
| 433 |
ticket_info["type"] = part.split(": ")[1]
|
| 434 |
+
|
| 435 |
elif action.tool == "RouteTicket":
|
| 436 |
ticket_info["department"] = str(observation).replace("Department: ", "")
|
| 437 |
+
|
| 438 |
+
elif action.tool == "SearchKnowledgeBase":
|
| 439 |
+
# β
Track KB search
|
| 440 |
+
kb_was_searched = True
|
| 441 |
+
obs_str = str(observation)
|
| 442 |
+
if "[KB Confidence:" in obs_str:
|
| 443 |
+
try:
|
| 444 |
+
# Extract confidence score
|
| 445 |
+
import re
|
| 446 |
+
match = re.search(r'\[KB Confidence: ([\d.]+)\]', obs_str)
|
| 447 |
+
if match:
|
| 448 |
+
kb_confidence = float(match.group(1))
|
| 449 |
+
ticket_info["kb_confidence"] = kb_confidence
|
| 450 |
+
except:
|
| 451 |
+
pass
|
| 452 |
+
|
| 453 |
+
elif action.tool == "EscalateToHuman":
|
| 454 |
+
# β
SAFEGUARD: Check if escalation was premature
|
| 455 |
+
if kb_was_searched and kb_confidence >= 0.75:
|
| 456 |
+
print(f"β οΈ WARNING: Agent escalated despite high KB confidence ({kb_confidence})")
|
| 457 |
+
# Don't set used_escalation_tool = True
|
| 458 |
+
# This prevents premature escalation from being treated as valid
|
| 459 |
+
else:
|
| 460 |
+
used_escalation_tool = True
|
| 461 |
+
# Extract ticket ID from observation
|
| 462 |
+
if "TKT-" in str(observation):
|
| 463 |
+
import re
|
| 464 |
+
ticket_match = re.search(r'TKT-\d{8}-\d{6}', str(observation))
|
| 465 |
+
if ticket_match:
|
| 466 |
+
ticket_info["escalation_ticket_id"] = ticket_match.group(0)
|
| 467 |
|
| 468 |
conv["ticket_info"] = ticket_info
|
| 469 |
+
|
| 470 |
+
# β
FIX: If escalation tool was used, force status to escalated
|
| 471 |
+
if used_escalation_tool:
|
| 472 |
+
status = "escalated"
|
| 473 |
+
should_save = True
|
| 474 |
+
|
| 475 |
conv["status"] = status
|
| 476 |
|
| 477 |
reasoning_trace = []
|