Pulastya0 commited on
Commit
fc4553e
·
verified ·
1 Parent(s): af2b232

Update agent_langchain.py

Browse files
Files changed (1) hide show
  1. agent_langchain.py +254 -262
agent_langchain.py CHANGED
@@ -1,325 +1,317 @@
1
  import os
2
- from typing import Optional, Dict, Any
3
- from datetime import datetime
 
 
 
 
 
 
 
 
 
 
4
  import chromadb
5
  from chromadb.config import Settings
6
- from sentence_transformers import SentenceTransformer
7
- from langchain_groq import ChatGroq
8
- from langchain_core.prompts import PromptTemplate
 
 
 
9
 
10
- # Environment setup
11
- os.environ["TOKENIZERS_PARALLELISM"] = "false"
 
 
12
 
13
- # Initialize models
14
- encoder = SentenceTransformer('all-MiniLM-L6-v2')
15
- llm = ChatGroq(
16
- model="llama-3.3-70b-versatile",
17
- temperature=0.3,
18
- api_key=os.getenv("GROQ_API_KEY")
19
- )
20
 
21
- # Global storage
22
- conversations = {}
23
- kb_collection = None
 
24
 
25
- # Chroma settings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  CHROMA_PATH = "/tmp/chroma"
27
  COLLECTION_NAME = "knowledge_base"
28
-
29
- # ===========================
30
- # Knowledge Base Functions
31
- # ===========================
32
 
33
  def get_kb_collection():
34
- """Get or initialize KB collection."""
35
  global kb_collection
36
  if kb_collection is None:
37
- try:
38
- chroma_client = chromadb.PersistentClient(
39
- path=CHROMA_PATH,
40
- settings=Settings(anonymized_telemetry=False, allow_reset=True)
41
- )
42
- kb_collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
43
- except Exception as e:
44
- print(f"Warning: Could not initialize KB collection: {e}")
 
 
45
  return kb_collection
46
 
47
- def query_kb(query: str, n_results: int = 1) -> Dict[str, Any]:
48
- """Query knowledge base for relevant information."""
49
  collection = get_kb_collection()
50
-
51
  if not collection or collection.count() == 0:
52
  return {"answer": None, "confidence": 0.0}
53
 
54
  try:
55
- query_embedding = encoder.encode([query])[0].tolist()
56
- result = collection.query(
57
  query_embeddings=[query_embedding],
58
- n_results=n_results,
59
  include=["documents", "distances", "metadatas"]
60
  )
61
 
62
- if not result or not result.get('documents') or len(result['documents'][0]) == 0:
63
  return {"answer": None, "confidence": 0.0}
64
 
65
- best_doc = result['documents'][0][0]
66
- best_distance = result['distances'][0][0] if result.get('distances') else 1.0
67
- confidence = max(0.0, 1.0 - (best_distance / 2.0))
68
 
69
- return {
70
- "answer": best_doc,
71
- "confidence": float(confidence),
72
- "metadata": result['metadatas'][0][0] if result.get('metadatas') else {}
73
- }
74
  except Exception as e:
75
- print(f"KB query error: {e}")
76
  return {"answer": None, "confidence": 0.0}
77
 
78
- # ===========================
79
- # Classification & Routing
80
- # ===========================
 
 
 
81
 
82
- def classify_ticket(ticket_text: str) -> str:
83
- """Classify ticket into priority/category."""
84
- prompt = PromptTemplate(
85
- input_variables=["ticket"],
86
- template="""Classify this IT support ticket into ONE of these categories:
87
- - password_reset
88
- - software_issue
89
- - hardware_problem
90
- - network_issue
91
- - access_request
92
- - general_inquiry
93
 
94
- Ticket: {ticket}
 
 
 
 
95
 
96
- Category (one word only):"""
97
- )
98
-
99
- try:
100
- response = llm.invoke(prompt.format(ticket=ticket_text))
101
- classification = response.content.strip().lower()
102
-
103
- valid_categories = ["password_reset", "software_issue", "hardware_problem",
104
- "network_issue", "access_request", "general_inquiry"]
105
-
106
- return classification if classification in valid_categories else "general_inquiry"
107
- except Exception as e:
108
- print(f"Classification error: {e}")
109
- return "general_inquiry"
110
 
111
- def call_routing(ticket_text: str) -> str:
112
- """Route ticket to appropriate department."""
113
- prompt = PromptTemplate(
114
- input_variables=["ticket"],
115
- template="""Route this IT ticket to the correct department. Choose ONE:
116
- - IT Support
117
- - Network Team
118
- - Security Team
119
- - Hardware Team
120
- - Access Management
121
 
122
- Ticket: {ticket}
 
 
 
123
 
124
- Department (exact name only):"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  )
126
-
127
- try:
128
- response = llm.invoke(prompt.format(ticket=ticket_text))
129
- department = response.content.strip()
130
-
131
- valid_depts = ["IT Support", "Network Team", "Security Team", "Hardware Team", "Access Management"]
132
-
133
- return department if department in valid_depts else "IT Support"
134
- except Exception as e:
135
- print(f"Routing error: {e}")
136
- return "IT Support"
137
 
138
- # ===========================
139
- # Main Processing Functions
140
- # ===========================
141
 
142
- def process_ticket_langchain(ticket_text: str) -> Dict[str, Any]:
143
- """
144
- Original single-turn ticket processing.
145
- Used for initial ticket intake.
146
- """
147
- # Step 1: Classify
148
- classification = classify_ticket(ticket_text)
149
-
150
- # Step 2: Route
151
- department = call_routing(ticket_text)
152
-
153
- # Step 3: Query KB
154
- kb_result = query_kb(ticket_text)
155
-
156
- # Step 4: Generate response
157
- if kb_result["answer"] and kb_result["confidence"] >= 0.7:
158
- answer = kb_result["answer"]
159
- status = "resolved"
160
- else:
161
- # Generate ticket ID and escalate
162
- ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
163
- answer = f"""I couldn't find a confident answer in our knowledge base.
164
 
165
- **Ticket Created:** {ticket_id}
166
- **Department:** {department}
167
- **Classification:** {classification}
 
 
 
 
168
 
169
- A specialist will review your ticket and respond within 2-4 business hours."""
170
- status = "escalated"
171
-
172
- return {
173
- "classification": classification,
174
- "department": department,
175
- "answer": answer,
176
- "confidence": kb_result["confidence"],
177
- "status": status
178
- }
 
179
 
180
- def process_with_agent(user_message: str, conversation_id: Optional[str] = None) -> Dict[str, Any]:
181
- """
182
- Full agentic conversation handler with memory and escalation logic.
183
- This is the main function used by the /orchestrate endpoint.
184
- """
185
- # Generate conversation ID if not provided
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  if not conversation_id:
187
  conversation_id = f"conv_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hash(user_message) % 10000}"
188
 
189
- # Initialize conversation if new
190
  if conversation_id not in conversations:
191
  conversations[conversation_id] = {
192
  "messages": [],
193
- "ticket_info": None,
194
- "created_at": datetime.now().isoformat(),
195
- "escalated": False
196
  }
197
 
198
  conv = conversations[conversation_id]
199
 
200
- # Don't continue if already escalated
201
- if conv.get("escalated", False):
202
- return {
203
- "conversation_id": conversation_id,
204
- "response": "This ticket has been escalated to a human agent. They will contact you soon.",
205
- "status": "escalated",
206
- "message_count": len(conv["messages"]),
207
- "can_continue": False
208
- }
209
-
210
- # Add user message
211
  conv["messages"].append({
212
  "role": "user",
213
  "content": user_message,
214
  "timestamp": datetime.now().isoformat()
215
  })
216
 
217
- # First message - full orchestration
218
- if len(conv["messages"]) == 1:
219
- result = process_ticket_langchain(user_message)
220
- conv["ticket_info"] = {
221
- "classification": result["classification"],
222
- "department": result["department"],
223
- "initial_query": user_message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  }
225
 
226
- response_text = result["answer"]
227
- status = result["status"]
 
 
228
 
229
- if status == "escalated":
230
- conv["escalated"] = True
231
-
232
- # Follow-up messages
233
- else:
234
- # Check for escalation keywords
235
- escalation_keywords = ["not working", "didn't work", "still broken", "still not",
236
- "escalate", "human", "agent", "supervisor", "still having"]
237
- wants_escalation = any(kw in user_message.lower() for kw in escalation_keywords)
238
 
239
- if wants_escalation:
240
- # Try KB one more time with refined query
241
- kb_result = query_kb(user_message)
242
-
243
- if kb_result["answer"] and kb_result["confidence"] >= 0.75:
244
- response_text = f"Let me try a different solution:\n\n{kb_result['answer']}\n\nPlease try this and let me know if it works."
245
- status = "in_progress"
246
- else:
247
- # Escalate to human
248
- ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
249
- conv["escalated"] = True
250
- response_text = f"""I understand the previous solutions haven't resolved your issue. I'm escalating this to a human specialist.
251
-
252
- **Escalation Ticket:** {ticket_id}
253
- **Department:** {conv['ticket_info']['department']}
254
- **Priority:** High
255
- **Issue:** {conv['ticket_info']['classification']}
256
-
257
- A {conv['ticket_info']['department']} specialist will contact you within 2-4 business hours. They will have full access to our conversation history.
258
-
259
- Your ticket reference: {ticket_id}"""
260
- status = "escalated"
261
- else:
262
- # Continue conversation with full context
263
- context = f"""You are a helpful IT helpdesk AI agent. Provide clear, concise troubleshooting help.
264
-
265
- **Conversation Context:**
266
- - Initial Issue: {conv['ticket_info']['initial_query']}
267
- - Classification: {conv['ticket_info']['classification']}
268
- - Department: {conv['ticket_info']['department']}
269
-
270
- **Recent Conversation:**
271
- """
272
- # Include last 6 messages for context
273
- for msg in conv["messages"][-6:]:
274
- context += f"{msg['role'].upper()}: {msg['content']}\n"
275
-
276
- context += f"""\n**Current User Message:** {user_message}
277
-
278
- Instructions:
279
- - Provide helpful, specific guidance
280
- - If user confirms something worked, congratulate them
281
- - If unclear, ask clarifying questions
282
- - Keep responses concise (2-3 paragraphs max)
283
- - Don't repeat previous solutions
284
-
285
- Your response:"""
286
-
287
- try:
288
- response_text = llm.invoke(context).content
289
- status = "in_progress"
290
- except Exception as e:
291
- print(f"LLM error: {e}")
292
- response_text = "I'm having trouble processing that. Could you rephrase your question?"
293
- status = "in_progress"
294
-
295
- # Add assistant response
296
- conv["messages"].append({
297
- "role": "assistant",
298
- "content": response_text,
299
- "status": status,
300
- "timestamp": datetime.now().isoformat()
301
- })
302
-
303
- return {
304
- "conversation_id": conversation_id,
305
- "response": response_text,
306
- "status": status,
307
- "message_count": len(conv["messages"]),
308
- "can_continue": not conv.get("escalated", False)
309
- }
310
-
311
- def get_conversation_history(conversation_id: str) -> Optional[Dict[str, Any]]:
312
- """Retrieve conversation history by ID."""
313
- return conversations.get(conversation_id)
314
-
315
- # ===========================
316
- # Initialization
317
- # ===========================
318
-
319
- # Initialize KB collection on module load
320
- get_kb_collection()
321
 
322
- print("✅ Agent LangChain module loaded successfully")
323
- print(f"📊 KB Collection: {'initialized' if kb_collection else 'not initialized'}")
324
- if kb_collection:
325
- print(f"📚 KB Records: {kb_collection.count()}")
 
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 requests
9
+ import torch
10
+ import time
11
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
12
+ import numpy as np
13
+ from sentence_transformers import SentenceTransformer
14
  import chromadb
15
  from chromadb.config import Settings
16
+ from langchain_google_genai import ChatGoogleGenerativeAI
17
+ from langchain.agents import AgentExecutor, create_react_agent
18
+ from langchain.tools import Tool
19
+ from langchain.prompts import PromptTemplate
20
+ import threading
21
+ from datetime import datetime
22
 
23
+ # Environment
24
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
25
+ ROUTING_URL = os.environ.get("ROUTING_URL")
26
+ SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
27
 
28
+ # Label Dictionary
29
+ LABEL_DICTIONARY = {
30
+ "I1": "Low Impact", "I2": "Medium Impact", "I3": "High Impact", "I4": "Critical Impact",
31
+ "U1": "Low Urgency", "U2": "Medium Urgency", "U3": "High Urgency", "U4": "Critical Urgency",
32
+ "T1": "Information", "T2": "Incident", "T3": "Problem", "T4": "Request", "T5": "Question"
33
+ }
 
34
 
35
+ # Classification Model
36
+ clf_model_name = "DavinciTech/BERT_Categorizer"
37
+ clf_tokenizer = AutoTokenizer.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
38
+ clf_model = AutoModelForSequenceClassification.from_pretrained(clf_model_name, cache_dir="/tmp/transformers")
39
 
40
+ def classify_ticket(text):
41
+ """Classify ticket into Impact, Urgency, and Type."""
42
+ inputs = clf_tokenizer(text, return_tensors="pt", truncation=True)
43
+ outputs = clf_model(**inputs)
44
+ logits = outputs.logits[0]
45
+ impact_idx = torch.argmax(logits[:4]).item() + 1
46
+ urgency_idx = torch.argmax(logits[4:8]).item() + 1
47
+ type_idx = torch.argmax(logits[8:]).item() + 1
48
+ return {
49
+ "impact": LABEL_DICTIONARY[f"I{impact_idx}"],
50
+ "urgency": LABEL_DICTIONARY[f"U{urgency_idx}"],
51
+ "type": LABEL_DICTIONARY[f"T{type_idx}"]
52
+ }
53
+
54
+ # Routing Function
55
+ def call_routing(text, retries=3, delay=5):
56
+ """Route ticket to appropriate department."""
57
+ url = ROUTING_URL if ROUTING_URL else f"{SPACE_URL}/route"
58
+ for attempt in range(retries):
59
+ try:
60
+ resp = requests.post(url, json={"text": text}, timeout=30)
61
+ resp.raise_for_status()
62
+ return resp.json().get("department", "General IT")
63
+ except Exception as e:
64
+ print(f"Routing attempt {attempt+1} failed: {e}")
65
+ if attempt < retries - 1:
66
+ time.sleep(delay)
67
+ return "General IT"
68
+
69
+ # Knowledge Base
70
  CHROMA_PATH = "/tmp/chroma"
71
  COLLECTION_NAME = "knowledge_base"
72
+ kb_collection = None
73
+ kb_lock = threading.Lock()
74
+ encoder = SentenceTransformer("all-MiniLM-L6-v2", cache_folder="/tmp/sentence_transformers")
 
75
 
76
  def get_kb_collection():
 
77
  global kb_collection
78
  if kb_collection is None:
79
+ with kb_lock:
80
+ if kb_collection is None:
81
+ try:
82
+ chroma_client = chromadb.PersistentClient(
83
+ path=CHROMA_PATH,
84
+ settings=Settings(anonymized_telemetry=False, allow_reset=True)
85
+ )
86
+ kb_collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
87
+ except Exception as e:
88
+ print(f"Could not get KB collection: {e}")
89
  return kb_collection
90
 
91
+ def query_kb(text: str, top_k: int = 1):
92
+ """Query KB and return answer with confidence."""
93
  collection = get_kb_collection()
 
94
  if not collection or collection.count() == 0:
95
  return {"answer": None, "confidence": 0.0}
96
 
97
  try:
98
+ query_embedding = encoder.encode([text])[0].tolist()
99
+ results = collection.query(
100
  query_embeddings=[query_embedding],
101
+ n_results=top_k,
102
  include=["documents", "distances", "metadatas"]
103
  )
104
 
105
+ if not results or not results.get("documents") or len(results["documents"][0]) == 0:
106
  return {"answer": None, "confidence": 0.0}
107
 
108
+ answer = results["documents"][0][0]
109
+ distance = results["distances"][0][0] if results.get("distances") else 1.0
110
+ confidence = max(0.0, 1.0 - (distance / 2.0))
111
 
112
+ return {"answer": answer, "confidence": round(float(confidence), 3)}
 
 
 
 
113
  except Exception as e:
114
+ print(f"KB query failed: {e}")
115
  return {"answer": None, "confidence": 0.0}
116
 
117
+ # Gemini LLM
118
+ llm = ChatGoogleGenerativeAI(
119
+ model="gemini-2.0-flash-exp",
120
+ temperature=0.3,
121
+ google_api_key=GEMINI_API_KEY
122
+ )
123
 
124
+ # Global conversation storage
125
+ conversations = {}
 
 
 
 
 
 
 
 
 
126
 
127
+ # Tool Functions for Agent
128
+ def classify_tool(query: str) -> str:
129
+ """Classifies IT ticket into impact, urgency, and type. Use this FIRST."""
130
+ result = classify_ticket(query)
131
+ return f"Impact: {result['impact']}, Urgency: {result['urgency']}, Type: {result['type']}"
132
 
133
+ def routing_tool(query: str) -> str:
134
+ """Determines which IT department should handle this ticket. Use this SECOND."""
135
+ dept = call_routing(query)
136
+ return f"Department: {dept}"
 
 
 
 
 
 
 
 
 
 
137
 
138
+ def kb_tool(query: str) -> str:
139
+ """Searches knowledge base for solutions. Returns answer and confidence score. Use this THIRD."""
140
+ result = query_kb(query)
141
+ if result["answer"] and result["confidence"] > 0.5:
142
+ return f"[KB Confidence: {result['confidence']}]\n{result['answer']}"
143
+ return f"[KB Confidence: {result['confidence']}] No relevant solution found in knowledge base."
 
 
 
 
144
 
145
+ def escalation_tool(reason: str) -> str:
146
+ """Escalates ticket to human agent. Use ONLY when KB confidence < 0.75 OR user says solution didn't work."""
147
+ ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
148
+ return f"ESCALATED: Ticket {ticket_id} created. Reason: {reason}. Human agent will respond in 2-4 hours."
149
 
150
+ # Define Tools
151
+ tools = [
152
+ Tool(
153
+ name="ClassifyTicket",
154
+ func=classify_tool,
155
+ description="Classifies IT ticket severity. Input: user's issue description. Use this FIRST for every new ticket."
156
+ ),
157
+ Tool(
158
+ name="RouteTicket",
159
+ func=routing_tool,
160
+ description="Determines responsible department. Input: user's issue description. Use this SECOND after classification."
161
+ ),
162
+ Tool(
163
+ name="SearchKnowledgeBase",
164
+ func=kb_tool,
165
+ description="Searches for solutions with confidence score. Input: user's issue description. Use this THIRD to find solutions."
166
+ ),
167
+ Tool(
168
+ name="EscalateToHuman",
169
+ func=escalation_tool,
170
+ description="Creates escalation ticket. Input: brief reason. Use ONLY when: 1) KB confidence < 0.75, 2) User reports solution failed, 3) Complex/unusual issue."
171
  )
172
+ ]
 
 
 
 
 
 
 
 
 
 
173
 
174
+ # Agent Prompt Template
175
+ AGENT_PROMPT = """You are an intelligent IT Helpdesk AI Agent. Resolve tickets efficiently using available tools.
 
176
 
177
+ TOOLS:
178
+ {tools}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ WORKFLOW FOR NEW TICKETS:
181
+ 1. Use ClassifyTicket to understand severity
182
+ 2. Use RouteTicket to determine responsible team
183
+ 3. Use SearchKnowledgeBase to find solutions
184
+ 4. Evaluate KB confidence score:
185
+ - If confidence >= 0.75: Provide the solution to user
186
+ - If confidence < 0.75: Use EscalateToHuman with clear reason
187
 
188
+ WORKFLOW FOR FOLLOW-UPS:
189
+ - If user confirms solution worked: Thank them and close positively
190
+ - If user says solution didn't work: Use SearchKnowledgeBase again OR EscalateToHuman
191
+ - For clarification questions: Answer directly
192
+
193
+ RULES:
194
+ - Be professional, empathetic, and clear
195
+ - Provide step-by-step instructions
196
+ - Trust high-confidence KB solutions (>= 0.75)
197
+ - Don't escalate prematurely
198
+ - Remember conversation context
199
 
200
+ Use this format:
201
+
202
+ Question: the input question
203
+ Thought: think about what to do
204
+ Action: the action to take, must be one of [{tool_names}]
205
+ Action Input: the input to the action
206
+ Observation: the result of the action
207
+ ... (repeat as needed)
208
+ Thought: I now know the final answer
209
+ Final Answer: the final answer
210
+
211
+ Begin!
212
+
213
+ Question: {input}
214
+ Thought: {agent_scratchpad}"""
215
+
216
+ prompt = PromptTemplate.from_template(AGENT_PROMPT)
217
+
218
+ # Create Agent
219
+ agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
220
+ agent_executor = AgentExecutor(
221
+ agent=agent,
222
+ tools=tools,
223
+ verbose=True,
224
+ max_iterations=6,
225
+ handle_parsing_errors=True,
226
+ return_intermediate_steps=True
227
+ )
228
+
229
+ # Main Processing Function
230
+ def process_with_agent(user_message: str, conversation_id: str = None):
231
+ """Process user message through autonomous AI agent."""
232
+
233
  if not conversation_id:
234
  conversation_id = f"conv_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hash(user_message) % 10000}"
235
 
 
236
  if conversation_id not in conversations:
237
  conversations[conversation_id] = {
238
  "messages": [],
239
+ "ticket_info": {},
240
+ "created_at": datetime.now().isoformat()
 
241
  }
242
 
243
  conv = conversations[conversation_id]
244
 
 
 
 
 
 
 
 
 
 
 
 
245
  conv["messages"].append({
246
  "role": "user",
247
  "content": user_message,
248
  "timestamp": datetime.now().isoformat()
249
  })
250
 
251
+ # Build context for follow-ups
252
+ if len(conv["messages"]) > 1:
253
+ context = f"PREVIOUS CONVERSATION:\n"
254
+ for msg in conv["messages"][-5:-1]:
255
+ context += f"{msg['role'].upper()}: {msg['content']}\n"
256
+ context += f"\nCURRENT USER MESSAGE: {user_message}"
257
+ agent_input = context
258
+ else:
259
+ agent_input = user_message
260
+
261
+ try:
262
+ result = agent_executor.invoke({"input": agent_input})
263
+
264
+ agent_response = result.get("output", "I apologize, I encountered an error.")
265
+ intermediate_steps = result.get("intermediate_steps", [])
266
+
267
+ status = "resolved"
268
+ if "ESCALATED" in agent_response or "TKT-" in agent_response:
269
+ status = "escalated"
270
+ elif len(conv["messages"]) > 1:
271
+ status = "in_progress"
272
+
273
+ reasoning_trace = []
274
+ for action, observation in intermediate_steps:
275
+ reasoning_trace.append({
276
+ "tool": action.tool,
277
+ "input": action.tool_input,
278
+ "output": str(observation)[:200]
279
+ })
280
+
281
+ conv["messages"].append({
282
+ "role": "assistant",
283
+ "content": agent_response,
284
+ "timestamp": datetime.now().isoformat(),
285
+ "reasoning": reasoning_trace
286
+ })
287
+
288
+ return {
289
+ "conversation_id": conversation_id,
290
+ "response": agent_response,
291
+ "status": status,
292
+ "message_count": len(conv["messages"]),
293
+ "reasoning_trace": reasoning_trace
294
  }
295
 
296
+ except Exception as e:
297
+ print(f"Agent error: {e}")
298
+ import traceback
299
+ traceback.print_exc()
300
 
301
+ error_response = "I apologize, I encountered an error. Please try again."
302
+ conv["messages"].append({
303
+ "role": "assistant",
304
+ "content": error_response,
305
+ "timestamp": datetime.now().isoformat()
306
+ })
 
 
 
307
 
308
+ return {
309
+ "conversation_id": conversation_id,
310
+ "response": error_response,
311
+ "status": "error",
312
+ "error": str(e)
313
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
+ def get_conversation_history(conversation_id: str):
316
+ """Get conversation history."""
317
+ return conversations.get(conversation_id)