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

Update agent_langchain.py

Browse files
Files changed (1) hide show
  1. agent_langchain.py +58 -295
agent_langchain.py CHANGED
@@ -1,281 +1,8 @@
1
- import os
2
- os.environ["TOKENIZERS_PARALLELISM"] = "false"
3
- os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers"
4
- 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
12
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
13
- import numpy as np
14
- from sentence_transformers import SentenceTransformer
15
- import chromadb
16
- from chromadb.config import Settings
17
- from langchain_google_genai import ChatGoogleGenerativeAI
18
- from langchain.agents import AgentExecutor, create_react_agent
19
- from langchain.tools import Tool
20
- from langchain.prompts import PromptTemplate
21
- import threading
22
- from datetime import datetime
23
- import firebase_admin
24
- from firebase_admin import credentials, firestore
25
- from typing import Optional, Dict, Any
26
-
27
- # Environment
28
- 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 firebase_creds_json:
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 from FIREBASE_CREDS_JSON")
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 = {
54
- "I1": "Low Impact", "I2": "Medium Impact", "I3": "High Impact", "I4": "Critical Impact",
55
- "U1": "Low Urgency", "U2": "Medium Urgency", "U3": "High Urgency", "U4": "Critical Urgency",
56
- "T1": "Information", "T2": "Incident", "T3": "Problem", "T4": "Request", "T5": "Question"
57
- }
58
-
59
- # Classification Model
60
- clf_model_name = "DavinciTech/BERT_Categorizer"
61
- clf_tokenizer = AutoTokenizer.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
62
- clf_model = AutoModelForSequenceClassification.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
63
-
64
- def classify_ticket(text):
65
- """Classify ticket into Impact, Urgency, and Type."""
66
- inputs = clf_tokenizer(text, return_tensors="pt", truncation=True)
67
- outputs = clf_model(**inputs)
68
- logits = outputs.logits[0]
69
- impact_idx = torch.argmax(logits[:4]).item() + 1
70
- urgency_idx = torch.argmax(logits[4:8]).item() + 1
71
- type_idx = torch.argmax(logits[8:]).item() + 1
72
- return {
73
- "impact": LABEL_DICTIONARY[f"I{impact_idx}"],
74
- "urgency": LABEL_DICTIONARY[f"U{urgency_idx}"],
75
- "type": LABEL_DICTIONARY[f"T{type_idx}"]
76
- }
77
-
78
- # Routing Function
79
- def call_routing(text, retries=3, delay=5):
80
- """Route ticket to appropriate department."""
81
- url = ROUTING_URL if ROUTING_URL else f"{SPACE_URL}/route"
82
- for attempt in range(retries):
83
- try:
84
- resp = requests.post(url, json={"text": text}, timeout=30)
85
- resp.raise_for_status()
86
- return resp.json().get("department", "General IT")
87
- except Exception as e:
88
- print(f"Routing attempt {attempt+1} failed: {e}")
89
- if attempt < retries - 1:
90
- time.sleep(delay)
91
- return "General IT"
92
-
93
- # Knowledge Base
94
- CHROMA_PATH = "/tmp/chroma"
95
- COLLECTION_NAME = "knowledge_base"
96
- kb_collection = None
97
- kb_lock = threading.Lock()
98
- encoder = SentenceTransformer("all-MiniLM-L6-v2", cache_folder="/tmp/sentence_transformers")
99
-
100
- def get_kb_collection():
101
- global kb_collection
102
- if kb_collection is None:
103
- with kb_lock:
104
- if kb_collection is None:
105
- try:
106
- chroma_client = chromadb.PersistentClient(
107
- path=CHROMA_PATH,
108
- settings=Settings(anonymized_telemetry=False, allow_reset=True)
109
- )
110
- kb_collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
111
- except Exception as e:
112
- print(f"Could not get KB collection: {e}")
113
- return kb_collection
114
-
115
- def query_kb(text: str, top_k: int = 1):
116
- """Query KB and return answer with confidence."""
117
- collection = get_kb_collection()
118
- if not collection or collection.count() == 0:
119
- return {"answer": None, "confidence": 0.0}
120
-
121
- try:
122
- query_embedding = encoder.encode([text])[0].tolist()
123
- results = collection.query(
124
- query_embeddings=[query_embedding],
125
- n_results=top_k,
126
- include=["documents", "distances", "metadatas"]
127
- )
128
-
129
- if not results or not results.get("documents") or len(results["documents"][0]) == 0:
130
- return {"answer": None, "confidence": 0.0}
131
-
132
- answer = results["documents"][0][0]
133
- distance = results["distances"][0][0] if results.get("distances") else 1.0
134
- confidence = max(0.0, 1.0 - (distance / 2.0))
135
-
136
- return {"answer": answer, "confidence": round(float(confidence), 3)}
137
- except Exception as e:
138
- print(f"KB query failed: {e}")
139
- return {"answer": None, "confidence": 0.0}
140
-
141
- # Firestore Helper
142
- def save_ticket_to_firestore(ticket_data: Dict[str, Any]):
143
- """Save resolved/escalated ticket to Firestore."""
144
- if not db:
145
- print("⚠️ Firestore not initialized, skipping save")
146
- return None
147
-
148
- try:
149
- ticket_ref = db.collection('tickets').document()
150
- ticket_data['created_at'] = firestore.SERVER_TIMESTAMP
151
- ticket_data['updated_at'] = firestore.SERVER_TIMESTAMP
152
- ticket_ref.set(ticket_data)
153
- print(f"✅ Ticket saved to Firestore: {ticket_ref.id}")
154
- return ticket_ref.id
155
- except Exception as e:
156
- print(f"❌ Firestore save failed: {e}")
157
- return None
158
-
159
- # Gemini LLM
160
- llm = ChatGoogleGenerativeAI(
161
- model="gemini-2.0-flash-exp",
162
- temperature=0.3,
163
- google_api_key=GEMINI_API_KEY
164
- )
165
-
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)
173
- return f"Impact: {result['impact']}, Urgency: {result['urgency']}, Type: {result['type']}"
174
-
175
- def routing_tool(query: str) -> str:
176
- """Identifies which IT department should handle this issue. Use when you need to know responsible team."""
177
- dept = call_routing(query)
178
- return f"Department: {dept}"
179
-
180
- def kb_tool(query: str) -> str:
181
- """Searches knowledge base for solutions. Returns answer with confidence score. Use when you need technical solutions."""
182
- result = query_kb(query)
183
- if result["answer"] and result["confidence"] > 0.5:
184
- return f"[KB Confidence: {result['confidence']}]\n{result['answer']}"
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",
196
- func=classify_tool,
197
- description="Analyzes ticket to determine impact level, urgency, and type. Use this when you need to understand the severity or priority of an issue."
198
- ),
199
- Tool(
200
- name="RouteTicket",
201
- func=routing_tool,
202
- description="Determines which IT department should handle this ticket. Use this when you need to identify the responsible team."
203
- ),
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}
262
- Thought: {agent_scratchpad}"""
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
279
  def process_with_agent(
280
  user_message: str,
281
  conversation_id: str = None,
@@ -323,26 +50,48 @@ def process_with_agent(
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":
@@ -354,8 +103,10 @@ def process_with_agent(
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):
@@ -365,10 +116,15 @@ def process_with_agent(
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 = []
373
  for action, observation in intermediate_steps:
374
  reasoning_trace.append({
@@ -394,6 +150,8 @@ def process_with_agent(
394
  # Save to Firestore if resolved OR escalated
395
  firestore_id = None
396
  if should_save:
 
 
397
  firestore_data = {
398
  "conversation_id": conversation_id,
399
  "status": status,
@@ -402,21 +160,27 @@ def process_with_agent(
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({
@@ -424,6 +188,8 @@ def process_with_agent(
424
  "firestore_id": firestore_id,
425
  "status": status
426
  })
 
 
427
 
428
  return {
429
  "conversation_id": conversation_id,
@@ -432,11 +198,12 @@ def process_with_agent(
432
  "message_count": len(conv["messages"]),
433
  "reasoning_trace": reasoning_trace,
434
  "ticket_info": ticket_info,
435
- "firestore_id": firestore_id
 
436
  }
437
 
438
  except Exception as e:
439
- print(f"Agent error: {e}")
440
  import traceback
441
  traceback.print_exc()
442
 
@@ -455,8 +222,4 @@ def process_with_agent(
455
  "response": error_response,
456
  "status": "error",
457
  "error": str(e)
458
- }
459
-
460
- def get_conversation_history(conversation_id: str):
461
- """Get conversation history."""
462
- return conversations.get(conversation_id)
 
1
+ """
2
+ Complete fixed version of process_with_agent function in agent_langchain.py
3
+ Replace the existing function (around line 180-330) with this version.
4
+ """
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  def process_with_agent(
7
  user_message: str,
8
  conversation_id: str = None,
 
50
  agent_response = result.get("output", "I apologize, I encountered an error.")
51
  intermediate_steps = result.get("intermediate_steps", [])
52
 
53
+ # IMPROVED: Check tool usage to determine status
54
  status = "in_progress"
55
  should_save = False
56
  escalated = False
57
+ used_escalation_tool = False
58
 
59
+ # First, check if EscalateToHuman tool was actually used
60
+ for action, observation in intermediate_steps:
61
+ if action.tool == "EscalateToHuman":
62
+ used_escalation_tool = True
63
+ print("🔍 Detected EscalateToHuman tool usage")
64
+ break
65
+
66
+ # Determine final status
67
+ if used_escalation_tool:
68
+ # Tool was used - definitely escalated
69
+ status = "escalated"
70
+ should_save = True
71
+ escalated = True
72
+ print("🔴 Ticket ESCALATED (tool used) - Saving to Firebase")
73
+ elif "ESCALATED" in agent_response or "TKT-" in agent_response:
74
+ # Fallback: Check response text
75
  status = "escalated"
76
  should_save = True
77
  escalated = True
78
+ print("🔴 Ticket ESCALATED (text match) - Saving to Firebase")
79
+ elif any(phrase in agent_response.lower() for phrase in [
80
+ "resolved", "you're all set", "should work now", "problem solved",
81
+ "this should fix", "try these steps", "follow these steps"
82
+ ]):
83
+ # Resolution detected
84
  status = "resolved"
85
  should_save = True
86
  print("✅ Ticket RESOLVED - Saving to Firebase")
87
+ else:
88
+ # Still in progress
89
+ print("⏳ Ticket IN PROGRESS - Not saving yet")
90
 
91
  # Extract ticket info from tools
92
  ticket_info = conv.get("ticket_info", {})
93
  kb_confidence = None
94
+ escalation_reason = None
95
 
96
  for action, observation in intermediate_steps:
97
  if action.tool == "ClassifyTicket":
 
103
  ticket_info["urgency"] = part.split(": ")[1]
104
  elif "Type:" in part:
105
  ticket_info["type"] = part.split(": ")[1]
106
+
107
  elif action.tool == "RouteTicket":
108
  ticket_info["department"] = str(observation).replace("Department: ", "")
109
+
110
  elif action.tool == "SearchKnowledgeBase":
111
  # Extract confidence from KB response
112
  if "[KB Confidence:" in str(observation):
 
116
  ticket_info["kb_confidence"] = kb_confidence
117
  except:
118
  pass
119
+
120
+ elif action.tool == "EscalateToHuman":
121
+ # Capture escalation reason
122
+ escalation_reason = action.tool_input
123
 
124
  conv["ticket_info"] = ticket_info
125
  conv["status"] = status
126
 
127
+ # Build reasoning trace for debugging
128
  reasoning_trace = []
129
  for action, observation in intermediate_steps:
130
  reasoning_trace.append({
 
150
  # Save to Firestore if resolved OR escalated
151
  firestore_id = None
152
  if should_save:
153
+ print(f"💾 Preparing to save ticket - Status: {status}, Escalated: {escalated}")
154
+
155
  firestore_data = {
156
  "conversation_id": conversation_id,
157
  "status": status,
 
160
  "messages": conv["messages"],
161
  "resolution": agent_response,
162
  "created_at_iso": conv["created_at"],
163
+ "escalated": escalated,
164
+ "reasoning_trace": reasoning_trace # Include for debugging
165
  }
166
 
167
  # Add escalation reason if escalated
168
  if escalated:
169
+ if escalation_reason:
170
+ firestore_data["escalation_reason"] = escalation_reason
171
+ elif kb_confidence is not None and kb_confidence < 0.6:
172
+ firestore_data["escalation_reason"] = f"Low KB confidence: {kb_confidence}"
173
+ else:
174
+ firestore_data["escalation_reason"] = "Complex issue requiring human intervention"
175
+
176
+ print(f"🔼 Saving escalated ticket - Reason: {firestore_data['escalation_reason']}")
177
 
178
  firestore_id = save_ticket_to_firestore(firestore_data)
179
 
180
  if firestore_id:
181
  print(f"✅ Successfully saved to Firestore with ID: {firestore_id}")
182
  else:
183
+ print("❌ Failed to save to Firestore - check Firebase connection")
184
 
185
  if callback:
186
  callback({
 
188
  "firestore_id": firestore_id,
189
  "status": status
190
  })
191
+ else:
192
+ print(f"⏭️ Not saving - Status: {status}, Should save: {should_save}")
193
 
194
  return {
195
  "conversation_id": conversation_id,
 
198
  "message_count": len(conv["messages"]),
199
  "reasoning_trace": reasoning_trace,
200
  "ticket_info": ticket_info,
201
+ "firestore_id": firestore_id,
202
+ "escalated": escalated # Explicitly return this
203
  }
204
 
205
  except Exception as e:
206
+ print(f"Agent error: {e}")
207
  import traceback
208
  traceback.print_exc()
209
 
 
222
  "response": error_response,
223
  "status": "error",
224
  "error": str(e)
225
+ }