Pipalskill commited on
Commit
29a622a
·
verified ·
1 Parent(s): c53ba71

Update agent_langchain.py

Browse files
Files changed (1) hide show
  1. agent_langchain.py +70 -170
agent_langchain.py CHANGED
@@ -5,9 +5,9 @@ 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 requests
9
  import torch
10
- import json
11
  import time
12
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
13
  import numpy as np
@@ -39,7 +39,6 @@ if firebase_creds_json:
39
  cred = credentials.Certificate(creds_dict)
40
 
41
  if not firebase_admin._apps:
42
-
43
  firebase_admin.initialize_app(cred)
44
  db = firestore.client()
45
  print("✅ Firebase initialized from FIREBASE_CREDS_JSON")
@@ -167,7 +166,7 @@ llm = ChatGoogleGenerativeAI(
167
  # Global conversation storage
168
  conversations = {}
169
 
170
- # Tool Functions for Agent (TRULY AUTONOMOUS NOW)
171
  def classify_tool(query: str) -> str:
172
  """Analyzes ticket severity, impact, urgency, and type. Use when you need to understand ticket priority."""
173
  result = classify_ticket(query)
@@ -186,50 +185,11 @@ def kb_tool(query: str) -> str:
186
  return f"[KB Confidence: {result['confidence']}] No relevant solution found in knowledge base."
187
 
188
  def escalation_tool(reason: str) -> str:
189
- """Creates escalation ticket for human agent. Use when: KB confidence is low, issue is complex, or user reports solution failed."""
190
  ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
191
  return f"ESCALATED: Ticket {ticket_id} created. Reason: {reason}. Human agent will respond in 2-4 hours."
192
 
193
- def clarifying_questions_tool(issue_summary: str) -> str:
194
- """Generates clarifying questions when more information needed. Use when: User description is vague, Need to diagnose issue better, Before searching KB."""
195
- prompt = f"""You are an IT helpdesk expert. Based on this issue, generate 2-3 specific clarifying questions to better diagnose the problem.
196
-
197
- Issue: {issue_summary}
198
-
199
- Generate focused questions that would help identify:
200
- - Specific symptoms or error messages
201
- - When the issue started
202
- - What the user has already tried
203
- - Environment details (OS, browser, device)
204
-
205
- Questions (numbered list):"""
206
-
207
- try:
208
- response = llm.invoke(prompt)
209
- return f"CLARIFYING QUESTIONS:\n{response.content.strip()}"
210
- except Exception as e:
211
- return "Please provide more details: 1) What specific error do you see? 2) When did this start? 3) Have you tried any solutions yet?"
212
-
213
- def check_solution_tool(user_feedback: str) -> str:
214
- """Analyzes user feedback to determine if provided solution worked. Use after: Giving a solution, User responds with feedback."""
215
- feedback_lower = user_feedback.lower()
216
-
217
- # Success indicators
218
- success_keywords = ["worked", "fixed", "solved", "resolved", "success", "thank", "thanks", "great", "perfect", "awesome"]
219
- # Failure indicators
220
- failure_keywords = ["didn't work", "not working", "still", "failed", "broken", "issue persists", "same problem", "no luck", "doesn't work"]
221
-
222
- success_count = sum(1 for kw in success_keywords if kw in feedback_lower)
223
- failure_count = sum(1 for kw in failure_keywords if kw in feedback_lower)
224
-
225
- if success_count > failure_count and success_count > 0:
226
- return "SOLUTION_WORKED: User confirmed the solution resolved their issue. You can close this ticket positively."
227
- elif failure_count > success_count and failure_count > 0:
228
- return "SOLUTION_FAILED: User confirmed the solution did NOT work. You should try alternative solutions or escalate."
229
- else:
230
- return "UNCLEAR: Cannot determine if solution worked. Ask the user directly: 'Did that solution work for you?'"
231
-
232
- # Define Tools with better descriptions
233
  tools = [
234
  Tool(
235
  name="ClassifyTicket",
@@ -244,101 +204,58 @@ tools = [
244
  Tool(
245
  name="SearchKnowledgeBase",
246
  func=kb_tool,
247
- 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."
248
- ),
249
- Tool(
250
- name="AskClarifyingQuestions",
251
- func=clarifying_questions_tool,
252
- 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."
253
- ),
254
- Tool(
255
- name="CheckSolutionEffectiveness",
256
- func=check_solution_tool,
257
- 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."
258
  ),
259
  Tool(
260
  name="EscalateToHuman",
261
  func=escalation_tool,
262
- 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."
263
  )
264
  ]
265
 
266
- # IMPROVED Agent Prompt - More Autonomous
267
- AGENT_PROMPT = """You are an intelligent IT Helpdesk AI Agent. Your goal is to efficiently resolve IT support tickets using the tools available to you.
268
 
269
  AVAILABLE TOOLS:
270
  {tools}
271
 
272
  TOOL NAMES: {tool_names}
273
 
274
- GUIDING PRINCIPLES:
275
- 1. **Think autonomously** - Decide which tools you need based on the situation, not a fixed sequence
276
- 2. **Be efficient** - Only use tools when they add value to solving the user's problem
277
- 3. **Trust high-confidence solutions** - If KB returns confidence >= 0.75, provide that solution directly
278
- 4. **Exhaust KB options first** - Try searching KB before escalating. Only escalate if KB truly cannot help
279
- 5. **Escalate wisely** - Only escalate when truly necessary (low KB confidence, complex issues, or failed solutions)
280
- 6. **Maintain context** - Remember previous conversation history when handling follow-ups
281
- 7. **Be empathetic** - Users are frustrated when things break; be professional and supportive
282
- 8. **Be Useful** - If low KB confidence, try answering using general knowledge and escalate
283
-
284
- CRITICAL DECISION FRAMEWORK FOR KB CONFIDENCE:
285
-
286
- **KB Confidence >= 0.75 (HIGH):**
287
- - PROVIDE THE SOLUTION IMMEDIATELY
288
- - ❌ DO NOT escalate
289
- - DO NOT use EscalateToHuman tool
290
- - This is a good solution - trust it!
291
-
292
- **KB Confidence 0.5-0.74 (MEDIUM):**
293
- - Consider the issue complexity
294
- - For common issues (password, printer, etc): Provide the solution
295
- - For critical issues: Provide solution + mention you can escalate if it doesn't work
296
- - DO NOT immediately escalate - give the solution a chance first
297
-
298
- **KB Confidence < 0.5 (LOW) OR No KB Match:**
299
- - Try to help with general IT knowledge if possible
300
- - If you cannot help OR issue is highly complex → Then escalate
301
- - Use EscalateToHuman tool only as last resort
302
-
303
- WORKFLOW FOR NEW TICKETS:
304
- 1. Understand the issue (classify if needed)
305
- 2. Search KB for solutions
306
- 3. Evaluate KB confidence:
307
- - High (≥0.75): Provide solution, DON'T escalate
308
- - Medium (0.5-0.74): Provide solution, offer escalation as backup
309
- - Low (<0.5): Try general knowledge OR escalate
310
- 4. Only use EscalateToHuman if KB has no solution AND you can't help
311
-
312
- WORKFLOW FOR FOLLOW-UPS:
313
- - If user says solution worked → Close positively, NO escalation needed
314
- - If user says solution failed → Try searching KB with different query OR escalate
315
- - For clarification questions → Answer directly
316
 
317
  FORMAT:
318
  Question: the user's input
319
  Thought: your reasoning about what to do next
320
  Action: the tool to use (must be one of [{tool_names}])
321
  Action Input: the input for that tool
322
- Observation: the result of the action
323
  ... (repeat Thought/Action/Observation as needed)
324
  Thought: I now have enough information to respond
325
  Final Answer: your complete response to the user
326
 
327
- CRITICAL: When you have enough information to answer the user, you MUST use this EXACT format:
328
-
329
- Thought: I now have enough information to respond
330
- Final Answer: [your complete response to the user]
331
-
332
- Do NOT add any additional "Action:" or "Thought:" after "Final Answer:". The conversation ends with "Final Answer:".
333
-
334
- IMPORTANT:
335
- - Don't mention tool names or technical process to users
336
- - Provide clear, step-by-step instructions
337
- - Don't escalate if you have a working solution (confidence ≥ 0.75)
338
- - Trust high-confidence KB solutions - they are tested and verified
339
- - Be conversational and helpful
340
- - STOP after "Final Answer:" - do not continue the loop
341
-
342
  Begin!
343
 
344
  Question: {input}
@@ -346,16 +263,16 @@ Thought: {agent_scratchpad}"""
346
 
347
  prompt = PromptTemplate.from_template(AGENT_PROMPT)
348
 
349
- # Create Agent with more flexibility
350
  agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
351
  agent_executor = AgentExecutor(
352
  agent=agent,
353
  tools=tools,
354
  verbose=True,
355
- max_iterations=6, # Reduced to prevent loops
356
  handle_parsing_errors="Check your output and make sure it conforms to the format instructions!",
357
  return_intermediate_steps=True,
358
- early_stopping_method="force" # Force stop if max iterations reached
359
  )
360
 
361
  # Main Processing Function
@@ -363,7 +280,7 @@ def process_with_agent(
363
  user_message: str,
364
  conversation_id: str = None,
365
  user_email: str = None,
366
- callback=None # For streaming updates via WebSocket
367
  ):
368
  """Process user message through autonomous AI agent."""
369
 
@@ -393,7 +310,7 @@ def process_with_agent(
393
  # Build context for follow-ups
394
  if len(conv["messages"]) > 1:
395
  context = f"CONVERSATION HISTORY:\n"
396
- for msg in conv["messages"][-6:-1]: # Last 5 messages for context
397
  context += f"{msg['role'].upper()}: {msg['content']}\n"
398
  context += f"\nCURRENT MESSAGE: {user_message}"
399
  agent_input = context
@@ -406,32 +323,29 @@ def process_with_agent(
406
  agent_response = result.get("output", "I apologize, I encountered an error.")
407
  intermediate_steps = result.get("intermediate_steps", [])
408
 
409
- # Determine status and handle Firestore
410
  status = "in_progress"
411
  should_save = False
 
412
 
413
- # Check for escalation FIRST (higher priority)
414
  if "ESCALATED" in agent_response or "TKT-" in agent_response:
415
  status = "escalated"
416
- should_save = True # ✅ FIX: Save escalated tickets too!
417
- # Then check for resolution indicators
418
- elif any(phrase in agent_response.lower() for phrase in ["resolved", "you're all set", "should work now", "problem solved", "glad that worked"]):
 
 
419
  status = "resolved"
420
  should_save = True
421
- # If agent explicitly mentions escalation/forwarding to team
422
- 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"]):
423
- status = "escalated"
424
- should_save = True # ✅ FIX: Save when forwarded to team
425
 
426
  # Extract ticket info from tools
427
  ticket_info = conv.get("ticket_info", {})
428
- used_escalation_tool = False # Track if escalation tool was used
429
- kb_confidence = 0.0 # Track KB confidence
430
- kb_was_searched = False # Track if KB was searched
431
 
432
  for action, observation in intermediate_steps:
433
  if action.tool == "ClassifyTicket":
434
- # Parse classification
435
  parts = str(observation).split(", ")
436
  for part in parts:
437
  if "Impact:" in part:
@@ -440,47 +354,19 @@ def process_with_agent(
440
  ticket_info["urgency"] = part.split(": ")[1]
441
  elif "Type:" in part:
442
  ticket_info["type"] = part.split(": ")[1]
443
-
444
  elif action.tool == "RouteTicket":
445
  ticket_info["department"] = str(observation).replace("Department: ", "")
446
-
447
  elif action.tool == "SearchKnowledgeBase":
448
- # Track KB search
449
- kb_was_searched = True
450
- obs_str = str(observation)
451
- if "[KB Confidence:" in obs_str:
452
  try:
453
- # Extract confidence score
454
- import re
455
- match = re.search(r'\[KB Confidence: ([\d.]+)\]', obs_str)
456
- if match:
457
- kb_confidence = float(match.group(1))
458
- ticket_info["kb_confidence"] = kb_confidence
459
  except:
460
  pass
461
-
462
- elif action.tool == "EscalateToHuman":
463
- # ✅ SAFEGUARD: Check if escalation was premature
464
- if kb_was_searched and kb_confidence >= 0.75:
465
- print(f"⚠️ WARNING: Agent escalated despite high KB confidence ({kb_confidence})")
466
- # Don't set used_escalation_tool = True
467
- # This prevents premature escalation from being treated as valid
468
- else:
469
- used_escalation_tool = True
470
- # Extract ticket ID from observation
471
- if "TKT-" in str(observation):
472
- import re
473
- ticket_match = re.search(r'TKT-\d{8}-\d{6}', str(observation))
474
- if ticket_match:
475
- ticket_info["escalation_ticket_id"] = ticket_match.group(0)
476
 
477
  conv["ticket_info"] = ticket_info
478
-
479
- # ✅ FIX: If escalation tool was used, force status to escalated
480
- if used_escalation_tool:
481
- status = "escalated"
482
- should_save = True
483
-
484
  conv["status"] = status
485
 
486
  reasoning_trace = []
@@ -505,7 +391,7 @@ def process_with_agent(
505
  "reasoning": reasoning_trace
506
  })
507
 
508
- # Save to Firestore if resolved/escalated
509
  firestore_id = None
510
  if should_save:
511
  firestore_data = {
@@ -515,14 +401,28 @@ def process_with_agent(
515
  "ticket_info": ticket_info,
516
  "messages": conv["messages"],
517
  "resolution": agent_response,
518
- "created_at_iso": conv["created_at"]
 
519
  }
 
 
 
 
 
 
 
520
  firestore_id = save_ticket_to_firestore(firestore_data)
521
 
 
 
 
 
 
522
  if callback:
523
  callback({
524
  "type": "saved",
525
- "firestore_id": firestore_id
 
526
  })
527
 
528
  return {
 
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
12
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
13
  import numpy as np
 
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 from FIREBASE_CREDS_JSON")
 
166
  # Global conversation storage
167
  conversations = {}
168
 
169
+ # Tool Functions for Agent
170
  def classify_tool(query: str) -> str:
171
  """Analyzes ticket severity, impact, urgency, and type. Use when you need to understand ticket priority."""
172
  result = classify_ticket(query)
 
185
  return f"[KB Confidence: {result['confidence']}] No relevant solution found in knowledge base."
186
 
187
  def escalation_tool(reason: str) -> str:
188
+ """Creates escalation ticket for human agent. Use ONLY when KB confidence is below 0.6 AND issue is truly complex. Always try KB first!"""
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  tools = [
194
  Tool(
195
  name="ClassifyTicket",
 
204
  Tool(
205
  name="SearchKnowledgeBase",
206
  func=kb_tool,
207
+ description="Searches internal knowledge base for solutions. Returns answer with confidence score (0-1). ALWAYS USE THIS FIRST before escalating. 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. CRITICAL: Use this ONLY as a LAST RESORT when: 1) KB confidence score is below 0.6 AND you've already tried KB, 2) Issue is extremely complex and unusual, 3) User explicitly confirms the KB solution failed after trying it. DO NOT escalate if KB has a reasonable solution (confidence > 0.6)."
213
  )
214
  ]
215
 
216
+ # IMPROVED Agent Prompt
217
+ AGENT_PROMPT = """You are an intelligent IT Helpdesk AI Agent. Your PRIMARY goal is to resolve tickets using the Knowledge Base. Escalation is a LAST RESORT.
218
 
219
  AVAILABLE TOOLS:
220
  {tools}
221
 
222
  TOOL NAMES: {tool_names}
223
 
224
+ CRITICAL RULES:
225
+ 1. **ALWAYS search Knowledge Base FIRST** - This is your primary tool for resolution
226
+ 2. **Trust KB solutions with confidence >= 0.6** - These are reliable solutions, provide them to users
227
+ 3. **ONLY escalate when ABSOLUTELY necessary**:
228
+ - KB confidence is below 0.6 AND no solution found
229
+ - Issue is extremely unusual or complex beyond KB scope
230
+ - User explicitly tried your KB solution and reports it failed
231
+ 4. **Be thorough with KB** - If first search doesn't work, try rephrasing the query
232
+ 5. **Maintain context** - Remember conversation history for follow-ups
233
+
234
+ DECISION WORKFLOW:
235
+ NEW TICKET → Search KB → If confidence >= 0.6 → Provide solution → Mark RESOLVED
236
+
237
+ If confidence < 0.6 Try rephrasing search → Still low? → Classify & Route → THEN escalate
238
+
239
+ FOLLOW-UP Check if user tried solution → Worked? → Mark RESOLVED
240
+
241
+ Failed? → Search KB again with different query → Still failing? → THEN escalate
242
+
243
+ IMPORTANT:
244
+ - Don't mention tool names or confidence scores to users
245
+ - Provide clear, step-by-step instructions from KB
246
+ - Be conversational and helpful
247
+ - Escalation means you couldn't solve it - avoid this outcome!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
  FORMAT:
250
  Question: the user's input
251
  Thought: your reasoning about what to do next
252
  Action: the tool to use (must be one of [{tool_names}])
253
  Action Input: the input for that tool
254
+ Observation: the tool's output
255
  ... (repeat Thought/Action/Observation as needed)
256
  Thought: I now have enough information to respond
257
  Final Answer: your complete response to the user
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  Begin!
260
 
261
  Question: {input}
 
263
 
264
  prompt = PromptTemplate.from_template(AGENT_PROMPT)
265
 
266
+ # Create Agent
267
  agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
268
  agent_executor = AgentExecutor(
269
  agent=agent,
270
  tools=tools,
271
  verbose=True,
272
+ max_iterations=6,
273
  handle_parsing_errors="Check your output and make sure it conforms to the format instructions!",
274
  return_intermediate_steps=True,
275
+ early_stopping_method="force"
276
  )
277
 
278
  # Main Processing Function
 
280
  user_message: str,
281
  conversation_id: str = None,
282
  user_email: str = None,
283
+ callback=None
284
  ):
285
  """Process user message through autonomous AI agent."""
286
 
 
310
  # Build context for follow-ups
311
  if len(conv["messages"]) > 1:
312
  context = f"CONVERSATION HISTORY:\n"
313
+ for msg in conv["messages"][-6:-1]:
314
  context += f"{msg['role'].upper()}: {msg['content']}\n"
315
  context += f"\nCURRENT MESSAGE: {user_message}"
316
  agent_input = context
 
323
  agent_response = result.get("output", "I apologize, I encountered an error.")
324
  intermediate_steps = result.get("intermediate_steps", [])
325
 
326
+ # Determine status based on agent's response
327
  status = "in_progress"
328
  should_save = False
329
+ escalated = False
330
 
331
+ # Check for escalation FIRST (most important)
332
  if "ESCALATED" in agent_response or "TKT-" in agent_response:
333
  status = "escalated"
334
+ should_save = True
335
+ escalated = True
336
+ print("🔴 Ticket ESCALATED - Saving to Firebase")
337
+ # Then check for resolution
338
+ elif any(phrase in agent_response.lower() for phrase in ["resolved", "you're all set", "should work now", "problem solved", "this should fix", "try these steps"]):
339
  status = "resolved"
340
  should_save = True
341
+ print("✅ Ticket RESOLVED - Saving to Firebase")
 
 
 
342
 
343
  # Extract ticket info from tools
344
  ticket_info = conv.get("ticket_info", {})
345
+ kb_confidence = None
 
 
346
 
347
  for action, observation in intermediate_steps:
348
  if action.tool == "ClassifyTicket":
 
349
  parts = str(observation).split(", ")
350
  for part in parts:
351
  if "Impact:" in part:
 
354
  ticket_info["urgency"] = part.split(": ")[1]
355
  elif "Type:" in part:
356
  ticket_info["type"] = part.split(": ")[1]
 
357
  elif action.tool == "RouteTicket":
358
  ticket_info["department"] = str(observation).replace("Department: ", "")
 
359
  elif action.tool == "SearchKnowledgeBase":
360
+ # Extract confidence from KB response
361
+ if "[KB Confidence:" in str(observation):
 
 
362
  try:
363
+ conf_str = str(observation).split("[KB Confidence: ")[1].split("]")[0]
364
+ kb_confidence = float(conf_str)
365
+ ticket_info["kb_confidence"] = kb_confidence
 
 
 
366
  except:
367
  pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
  conv["ticket_info"] = ticket_info
 
 
 
 
 
 
370
  conv["status"] = status
371
 
372
  reasoning_trace = []
 
391
  "reasoning": reasoning_trace
392
  })
393
 
394
+ # Save to Firestore if resolved OR escalated
395
  firestore_id = None
396
  if should_save:
397
  firestore_data = {
 
401
  "ticket_info": ticket_info,
402
  "messages": conv["messages"],
403
  "resolution": agent_response,
404
+ "created_at_iso": conv["created_at"],
405
+ "escalated": escalated
406
  }
407
+
408
+ # Add escalation reason if escalated
409
+ if escalated:
410
+ escalation_reason = "Low KB confidence" if kb_confidence and kb_confidence < 0.6 else "Complex issue requiring human intervention"
411
+ firestore_data["escalation_reason"] = escalation_reason
412
+ print(f"📤 Saving escalated ticket to Firebase - Reason: {escalation_reason}")
413
+
414
  firestore_id = save_ticket_to_firestore(firestore_data)
415
 
416
+ if firestore_id:
417
+ print(f"✅ Successfully saved to Firestore with ID: {firestore_id}")
418
+ else:
419
+ print("❌ Failed to save to Firestore")
420
+
421
  if callback:
422
  callback({
423
  "type": "saved",
424
+ "firestore_id": firestore_id,
425
+ "status": status
426
  })
427
 
428
  return {