destinyebuka commited on
Commit
debee07
Β·
1 Parent(s): b7d3dbc
app/ai/agent/nodes/respond.py CHANGED
@@ -1,11 +1,11 @@
1
  # app/ai/agent/nodes/respond.py
2
  """
3
  Node: Generate and return response to user.
4
- Final node before returning to client.
5
  """
6
 
7
  from structlog import get_logger
8
- from typing import Dict, Any, Optional
9
 
10
  from app.ai.agent.state import AgentState, FlowState
11
  from app.ai.agent.schemas import AgentResponse
@@ -13,32 +13,65 @@ from app.ai.agent.schemas import AgentResponse
13
  logger = get_logger(__name__)
14
 
15
 
16
- async def respond_to_user(state: AgentState) -> tuple[AgentState, AgentResponse]:
17
  """
18
  Generate final response to send to user.
19
 
 
 
 
 
 
20
  Args:
21
  state: Current agent state
22
 
23
  Returns:
24
- Tuple of (updated_state, AgentResponse)
25
  """
26
 
27
- logger.info("Generating response", user_id=state.user_id, flow=state.current_flow.value)
 
 
 
 
 
28
 
29
  try:
30
- # Get response data from state
 
 
 
31
  response_text = state.temp_data.get("response_text", "")
32
  draft = state.temp_data.get("draft")
33
  draft_ui = state.temp_data.get("draft_ui")
34
- tool_result = state.temp_data.get("tool_result")
35
  action = state.temp_data.get("action", state.current_flow.value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- # Ensure we have response text
38
  if not response_text:
39
  response_text = _get_fallback_response(state)
 
 
 
 
 
 
 
 
 
40
 
41
- # Build response
42
  response = AgentResponse(
43
  success=state.last_error is None,
44
  text=response_text,
@@ -47,6 +80,7 @@ async def respond_to_user(state: AgentState) -> tuple[AgentState, AgentResponse]
47
  "flow": state.current_flow.value,
48
  "steps": state.steps_taken,
49
  "errors": state.error_count,
 
50
  },
51
  draft=draft,
52
  draft_ui=draft_ui,
@@ -57,57 +91,157 @@ async def respond_to_user(state: AgentState) -> tuple[AgentState, AgentResponse]
57
  "intent_confidence": state.intent_confidence,
58
  "language": state.language_detected,
59
  "messages_in_session": len(state.conversation_history),
 
 
60
  }
61
  )
62
 
63
- # Add message to history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  state.add_message("assistant", response_text)
65
 
66
- # Determine next state for UI
 
 
 
 
 
 
 
 
67
  if state.current_flow == FlowState.COMPLETE:
68
- # Reset for next conversation
69
- state.transition_to(FlowState.IDLE, reason="Conversation complete")
 
 
 
 
 
 
 
 
 
 
 
 
70
  elif state.current_flow == FlowState.ERROR:
71
- # Keep in error state for user to see
72
- pass
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  logger.info(
75
- "Response generated",
76
  user_id=state.user_id,
77
- response_len=len(response_text),
78
- has_draft=draft is not None,
79
  )
80
 
81
- return state, response
 
 
 
 
82
 
83
  except Exception as e:
84
- logger.error("Response generation error", exc_info=e)
 
 
 
 
85
 
86
- # Fallback response
87
- fallback_text = "I encountered an error processing your request. Please try again."
88
- fallback_response = AgentResponse(
89
  success=False,
90
- text=fallback_text,
91
  action="error",
92
- state={"flow": state.current_flow.value},
 
 
 
 
93
  error=str(e),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  )
95
 
96
- return state, fallback_response
 
97
 
98
 
99
  def _get_fallback_response(state: AgentState) -> str:
100
  """
101
  Generate fallback response based on current flow.
102
 
103
- Used when no response was generated or error occurred.
 
 
 
 
 
 
104
  """
 
105
  fallback_responses = {
106
- FlowState.GREETING: "Hello! How can I help you with real estate today?",
107
- FlowState.LISTING_COLLECT: "Tell me more about the property you want to list.",
 
 
 
 
108
  FlowState.SEARCH_QUERY: "What properties are you looking for?",
109
- FlowState.CASUAL_CHAT: "I'm here to help! What would you like to know?",
110
- FlowState.ERROR: f"Sorry, I encountered an error: {state.last_error or 'Unknown error'}",
 
 
 
111
  }
112
 
113
- return fallback_responses.get(state.current_flow, "How can I assist you?")
 
 
 
 
 
 
1
  # app/ai/agent/nodes/respond.py
2
  """
3
  Node: Generate and return response to user.
4
+ FINAL FIX: Only returns AgentState, response stored in temp_data
5
  """
6
 
7
  from structlog import get_logger
8
+ from datetime import datetime
9
 
10
  from app.ai.agent.state import AgentState, FlowState
11
  from app.ai.agent.schemas import AgentResponse
 
13
  logger = get_logger(__name__)
14
 
15
 
16
+ async def respond_to_user(state: AgentState) -> AgentState:
17
  """
18
  Generate final response to send to user.
19
 
20
+ CRITICAL RULES:
21
+ Òœ… Only return AgentState (NEVER tuple!)
22
+ Òœ… Store AgentResponse in state.temp_data["final_response"]
23
+ Òœ… Route handler extracts from temp_data
24
+
25
  Args:
26
  state: Current agent state
27
 
28
  Returns:
29
+ Updated AgentState (response in temp_data)
30
  """
31
 
32
+ logger.info(
33
+ "πŸ“ Generating final response",
34
+ user_id=state.user_id,
35
+ flow=state.current_flow.value,
36
+ steps=state.steps_taken
37
+ )
38
 
39
  try:
40
+ # ============================================================
41
+ # Extract response components from temp_data
42
+ # ============================================================
43
+
44
  response_text = state.temp_data.get("response_text", "")
45
  draft = state.temp_data.get("draft")
46
  draft_ui = state.temp_data.get("draft_ui")
 
47
  action = state.temp_data.get("action", state.current_flow.value)
48
+ listing_id = state.temp_data.get("listing_id")
49
+ tool_result = state.temp_data.get("tool_result")
50
+
51
+ logger.info(
52
+ "πŸ“¦ Response components extracted",
53
+ has_text=bool(response_text),
54
+ has_draft=draft is not None,
55
+ has_draft_ui=draft_ui is not None,
56
+ action=action,
57
+ )
58
+
59
+ # ============================================================
60
+ # Use fallback if no response text
61
+ # ============================================================
62
 
 
63
  if not response_text:
64
  response_text = _get_fallback_response(state)
65
+ logger.warning(
66
+ "⚠️ Using fallback response",
67
+ user_id=state.user_id,
68
+ fallback=response_text[:50]
69
+ )
70
+
71
+ # ============================================================
72
+ # Build AgentResponse
73
+ # ============================================================
74
 
 
75
  response = AgentResponse(
76
  success=state.last_error is None,
77
  text=response_text,
 
80
  "flow": state.current_flow.value,
81
  "steps": state.steps_taken,
82
  "errors": state.error_count,
83
+ "last_error": state.last_error,
84
  },
85
  draft=draft,
86
  draft_ui=draft_ui,
 
91
  "intent_confidence": state.intent_confidence,
92
  "language": state.language_detected,
93
  "messages_in_session": len(state.conversation_history),
94
+ "listing_id": listing_id,
95
+ "timestamp": datetime.utcnow().isoformat(),
96
  }
97
  )
98
 
99
+ logger.info("βœ… AgentResponse built", success=response.success)
100
+
101
+ # ============================================================
102
+ # Òœ… CRITICAL: Store response in temp_data
103
+ # ============================================================
104
+
105
+ state.temp_data["final_response"] = response
106
+
107
+ logger.info(
108
+ "πŸ’Ύ Response stored in state.temp_data[final_response]",
109
+ user_id=state.user_id
110
+ )
111
+
112
+ # ============================================================
113
+ # Add message to conversation history
114
+ # ============================================================
115
+
116
  state.add_message("assistant", response_text)
117
 
118
+ logger.info(
119
+ "πŸ“ Message added to history",
120
+ history_len=len(state.conversation_history)
121
+ )
122
+
123
+ # ============================================================
124
+ # Handle state transitions
125
+ # ============================================================
126
+
127
  if state.current_flow == FlowState.COMPLETE:
128
+ # After successful operation, reset to IDLE
129
+ success, error = state.transition_to(
130
+ FlowState.IDLE,
131
+ reason="Conversation complete, ready for next interaction"
132
+ )
133
+ if not success:
134
+ logger.warning(
135
+ "⚠️ Could not transition to IDLE",
136
+ error=error,
137
+ current_flow=state.current_flow.value
138
+ )
139
+ else:
140
+ logger.info("βœ… Transitioned to IDLE", user_id=state.user_id)
141
+
142
  elif state.current_flow == FlowState.ERROR:
143
+ # Stay in error state so client knows something went wrong
144
+ logger.warning(
145
+ "🚨 Response from ERROR state",
146
+ user_id=state.user_id,
147
+ error=state.last_error
148
+ )
149
+
150
+ else:
151
+ logger.info(
152
+ "ℹ️ Response from flow",
153
+ flow=state.current_flow.value,
154
+ user_id=state.user_id
155
+ )
156
 
157
  logger.info(
158
+ "βœ… Response generation complete",
159
  user_id=state.user_id,
160
+ steps_taken=state.steps_taken,
161
+ success=response.success,
162
  )
163
 
164
+ # ============================================================
165
+ # Òœ… RETURN ONLY STATE (NOT TUPLE!)
166
+ # ============================================================
167
+
168
+ return state
169
 
170
  except Exception as e:
171
+ logger.error("❌ Response generation exception", exc_info=e)
172
+
173
+ # ============================================================
174
+ # Create error response
175
+ # ============================================================
176
 
177
+ error_response = AgentResponse(
 
 
178
  success=False,
179
+ text="I encountered an error processing your request. Please try again.",
180
  action="error",
181
+ state={
182
+ "flow": state.current_flow.value,
183
+ "steps": state.steps_taken,
184
+ "errors": state.error_count,
185
+ },
186
  error=str(e),
187
+ metadata={
188
+ "timestamp": datetime.utcnow().isoformat(),
189
+ }
190
+ )
191
+
192
+ # Store error response
193
+ state.temp_data["final_response"] = error_response
194
+
195
+ logger.info("πŸ’Ύ Error response stored", error=str(e)[:50])
196
+
197
+ # Add to history
198
+ state.add_message("assistant", error_response.text)
199
+
200
+ # Transition to error if not already there
201
+ if state.current_flow != FlowState.ERROR:
202
+ state.transition_to(FlowState.ERROR, reason=f"Exception: {str(e)}")
203
+
204
+ logger.info(
205
+ "βœ… Error handling complete",
206
+ user_id=state.user_id,
207
+ error_type=type(e).__name__
208
  )
209
 
210
+ # Return state with error response
211
+ return state
212
 
213
 
214
  def _get_fallback_response(state: AgentState) -> str:
215
  """
216
  Generate fallback response based on current flow.
217
 
218
+ Used when node fails to generate proper response.
219
+
220
+ Args:
221
+ state: Current agent state
222
+
223
+ Returns:
224
+ Fallback response text
225
  """
226
+
227
  fallback_responses = {
228
+ FlowState.GREETING: "Hello! πŸ‘‹ I'm AIDA, your real estate assistant. How can I help you today?",
229
+ FlowState.AUTHENTICATE: "Setting up your session...",
230
+ FlowState.CLASSIFY_INTENT: "I'm analyzing your request... One moment.",
231
+ FlowState.LISTING_COLLECT: "Tell me more about the property you want to list. What city is it in?",
232
+ FlowState.LISTING_VALIDATE: "Let me prepare your listing preview...",
233
+ FlowState.LISTING_PUBLISH: "Publishing your listing now...",
234
  FlowState.SEARCH_QUERY: "What properties are you looking for?",
235
+ FlowState.SEARCH_RESULTS: "Here are the listings matching your search.",
236
+ FlowState.CASUAL_CHAT: "I'm here to help! What would you like to know about real estate?",
237
+ FlowState.ERROR: f"Sorry, I encountered an error. Please try again or contact support.",
238
+ FlowState.COMPLETE: "Thanks for using AIDA! How else can I help?",
239
+ FlowState.IDLE: "How can I assist you?",
240
  }
241
 
242
+ response = fallback_responses.get(
243
+ state.current_flow,
244
+ "How can I assist you with real estate?"
245
+ )
246
+
247
+ return response
app/ai/routes/chat_refactored.py CHANGED
@@ -1,17 +1,17 @@
1
  # app/ai/routes/chat_refactored.py
2
  """
3
- AIDA Chat Endpoint - LangGraph Powered (NO AUTH)
4
- Fixed ainvoke for proper state passing
5
  """
6
 
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from typing import Optional
10
  from structlog import get_logger
11
- import uuid
 
12
 
13
  from app.ai.agent.graph import get_aida_graph
14
- from app.ai.agent.state import AgentState
15
  from app.ai.agent.schemas import AgentResponse
16
 
17
  logger = get_logger(__name__)
@@ -28,6 +28,8 @@ class AskBody(BaseModel):
28
  message: str
29
  session_id: Optional[str] = None
30
  user_id: Optional[str] = None
 
 
31
 
32
 
33
  # ============================================================
@@ -35,11 +37,21 @@ class AskBody(BaseModel):
35
  # ============================================================
36
 
37
  @router.post("/ask", response_model=AgentResponse)
38
- async def ask_ai(
39
- body: AskBody,
40
- ) -> AgentResponse:
41
  """
42
- Main chat endpoint using LangGraph - FIXED VERSION
 
 
 
 
 
 
 
 
 
 
 
 
43
  """
44
 
45
  logger.info("πŸš€ Chat request received", message_len=len(body.message))
@@ -50,82 +62,133 @@ async def ask_ai(
50
  # ============================================================
51
 
52
  if not body.message or not body.message.strip():
53
- logger.warning("Empty message")
54
- raise HTTPException(status_code=400, detail="Message cannot be empty")
 
 
 
 
 
55
 
56
  message = body.message.strip()
57
- session_id = body.session_id or str(uuid.uuid4())
58
- user_id = body.user_id or f"anonymous_{uuid.uuid4()}"
59
- user_role = "renter"
60
 
61
  logger.info(
62
- "User session started",
63
  user_id=user_id,
64
  session_id=session_id,
 
 
65
  )
66
 
67
  # ============================================================
68
- # STEP 2: Create AgentState (NOT a dict!)
69
  # ============================================================
 
70
 
71
- state = AgentState(
72
- user_id=user_id,
73
- session_id=session_id,
74
- user_role=user_role,
75
- )
 
 
 
 
76
 
77
- # Add user message to history
78
- state.add_message("user", message)
79
- state.last_user_message = message
 
 
80
 
81
- logger.info("State initialized", user_id=user_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  # ============================================================
84
- # STEP 3: Get graph and execute with proper state
85
  # ============================================================
86
 
87
- graph = get_aida_graph()
88
 
89
- if graph is None:
90
- logger.error("❌ Graph is None!")
91
- raise HTTPException(status_code=500, detail="Graph initialization failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- logger.info("πŸ“ Executing LangGraph with state")
 
 
94
 
95
- # βœ… FIX: Pass the AgentState object directly, not a dict
96
- final_state = await graph.ainvoke(state)
97
 
98
- logger.info(
99
- "βœ… LangGraph execution completed",
100
- flow=final_state.current_flow.value,
101
- steps=final_state.steps_taken,
102
- )
 
 
103
 
104
  # ============================================================
105
- # STEP 4: Build response from final state
106
  # ============================================================
107
 
108
- response_text = final_state.temp_data.get("response_text", "")
109
- action = final_state.temp_data.get("action", "unknown")
110
- draft = final_state.temp_data.get("draft")
111
- draft_ui = final_state.temp_data.get("draft_ui")
112
 
 
113
  if not response_text:
114
  response_text = "I'm here to help! What would you like to do?"
115
 
116
  response = AgentResponse(
117
  success=final_state.last_error is None,
118
  text=response_text,
119
- action=action,
120
  state={
121
  "flow": final_state.current_flow.value,
122
  "steps": final_state.steps_taken,
123
  "errors": final_state.error_count,
124
- "user_id": user_id,
125
- "session_id": session_id,
126
  },
127
- draft=draft,
128
- draft_ui=draft_ui,
129
  error=final_state.last_error,
130
  metadata={
131
  "intent": final_state.intent_type,
@@ -137,22 +200,19 @@ async def ask_ai(
137
  }
138
  )
139
 
140
- logger.info(
141
- "βœ… Response generated",
142
- user_id=user_id,
143
- action=action,
144
- success=response.success,
145
- )
146
 
147
  return response
148
 
149
  except HTTPException:
150
  raise
151
  except Exception as e:
152
- logger.error("❌ Chat endpoint error", exc_info=e)
153
- raise HTTPException(
154
- status_code=500,
155
- detail=f"Internal server error: {str(e)}"
 
 
156
  )
157
 
158
 
@@ -161,8 +221,8 @@ async def ask_ai(
161
  # ============================================================
162
 
163
  @router.get("/health")
164
- async def health_check():
165
- """Health check for chat service"""
166
 
167
  try:
168
  graph = get_aida_graph()
@@ -172,12 +232,14 @@ async def health_check():
172
  "service": "AIDA Chat (LangGraph)",
173
  "version": "2.0.0",
174
  "graph_available": graph is not None,
 
175
  }
176
  except Exception as e:
177
- logger.error("Health check failed", exc_info=e)
178
  return {
179
  "status": "unhealthy",
180
  "error": str(e),
 
181
  }
182
 
183
 
@@ -186,21 +248,25 @@ async def health_check():
186
  # ============================================================
187
 
188
  @router.get("/history/{session_id}")
189
- async def get_history(session_id: str):
190
  """Get conversation history for a session"""
191
 
192
  try:
193
- logger.info("Retrieved history", session_id=session_id)
194
 
195
  return {
196
  "success": True,
197
  "session_id": session_id,
198
  "messages": [],
 
199
  }
200
 
201
  except Exception as e:
202
- logger.error("History retrieval error", exc_info=e)
203
- raise HTTPException(status_code=500, detail="Error retrieving history")
 
 
 
204
 
205
 
206
  # ============================================================
@@ -208,17 +274,44 @@ async def get_history(session_id: str):
208
  # ============================================================
209
 
210
  @router.post("/reset-session/{session_id}")
211
- async def reset_session(session_id: str):
212
  """Reset a session to fresh state"""
213
 
214
  try:
215
- logger.info("Session reset", session_id=session_id)
216
 
217
  return {
218
  "success": True,
219
  "message": "Session reset to fresh state",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
 
222
  except Exception as e:
223
- logger.error("Session reset error", exc_info=e)
224
- raise HTTPException(status_code=500, detail="Error resetting session")
 
 
 
 
1
  # app/ai/routes/chat_refactored.py
2
  """
3
+ AIDA Chat Endpoint - LangGraph Powered (PRIMARY - v1)
4
+ Properly invokes graph with dict input and extracts response from state
5
  """
6
 
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from typing import Optional, Dict, Any
10
  from structlog import get_logger
11
+ from uuid import uuid4
12
+ from datetime import datetime
13
 
14
  from app.ai.agent.graph import get_aida_graph
 
15
  from app.ai.agent.schemas import AgentResponse
16
 
17
  logger = get_logger(__name__)
 
28
  message: str
29
  session_id: Optional[str] = None
30
  user_id: Optional[str] = None
31
+ user_role: Optional[str] = "renter"
32
+ start_new_session: Optional[bool] = False
33
 
34
 
35
  # ============================================================
 
37
  # ============================================================
38
 
39
  @router.post("/ask", response_model=AgentResponse)
40
+ async def ask_ai(body: AskBody) -> AgentResponse:
 
 
41
  """
42
+ Main chat endpoint using LangGraph state machine.
43
+
44
+ CRITICAL FIX:
45
+ - Pass INPUT as dict (not AgentState object)
46
+ - LangGraph converts dict β†’ AgentState internally
47
+ - Extract response from final_state.temp_data["final_response"]
48
+
49
+ Flow:
50
+ 1. Validate input
51
+ 2. Build input dict
52
+ 3. Invoke graph with dict input
53
+ 4. Extract AgentResponse from final state
54
+ 5. Return to client
55
  """
56
 
57
  logger.info("πŸš€ Chat request received", message_len=len(body.message))
 
62
  # ============================================================
63
 
64
  if not body.message or not body.message.strip():
65
+ logger.warning("❌ Empty message received")
66
+ return AgentResponse(
67
+ success=False,
68
+ text="Please provide a message.",
69
+ action="error",
70
+ error="Empty message",
71
+ )
72
 
73
  message = body.message.strip()
74
+ session_id = body.session_id or str(uuid4())
75
+ user_id = body.user_id or f"anonymous_{uuid4()}"
76
+ user_role = body.user_role or "renter"
77
 
78
  logger.info(
79
+ "πŸ“‹ User session info",
80
  user_id=user_id,
81
  session_id=session_id,
82
+ user_role=user_role,
83
+ message_len=len(message),
84
  )
85
 
86
  # ============================================================
87
+ # STEP 2: Build input dict for graph
88
  # ============================================================
89
+ # Òœ… CRITICAL: LangGraph expects dict input, NOT AgentState
90
 
91
+ input_dict = {
92
+ "user_id": user_id,
93
+ "session_id": session_id,
94
+ "user_role": user_role,
95
+ "last_user_message": message,
96
+ "conversation_history": [],
97
+ "language_detected": "en",
98
+ "start_new_session": body.start_new_session or False,
99
+ }
100
 
101
+ logger.info("πŸ“¦ Input dict prepared", keys=list(input_dict.keys()))
102
+
103
+ # ============================================================
104
+ # STEP 3: Get graph and validate
105
+ # ============================================================
106
 
107
+ try:
108
+ graph = get_aida_graph()
109
+
110
+ if graph is None:
111
+ logger.error("❌ Graph is None!")
112
+ return AgentResponse(
113
+ success=False,
114
+ text="System error: Graph not initialized",
115
+ action="error",
116
+ error="Graph initialization failed",
117
+ )
118
+
119
+ logger.info("βœ… Graph retrieved successfully")
120
+
121
+ except Exception as e:
122
+ logger.error("❌ Graph retrieval failed", exc_info=e)
123
+ return AgentResponse(
124
+ success=False,
125
+ text=f"System error: {str(e)}",
126
+ action="error",
127
+ error=str(e),
128
+ )
129
 
130
  # ============================================================
131
+ # STEP 4: Invoke graph with dict input (NOT AgentState!)
132
  # ============================================================
133
 
134
+ logger.info("πŸ”„ Invoking LangGraph...", user_id=user_id)
135
 
136
+ try:
137
+ # Òœ… CRITICAL FIX: Pass dict to ainvoke, not AgentState
138
+ final_state = await graph.ainvoke(input_dict)
139
+
140
+ logger.info(
141
+ "βœ… LangGraph execution completed",
142
+ flow=final_state.current_flow.value,
143
+ steps=final_state.steps_taken,
144
+ has_error=final_state.last_error is not None,
145
+ )
146
+
147
+ except Exception as e:
148
+ logger.error("❌ Graph execution failed", exc_info=e)
149
+ return AgentResponse(
150
+ success=False,
151
+ text="Error processing your request. Please try again.",
152
+ action="error",
153
+ error=str(e),
154
+ )
155
 
156
+ # ============================================================
157
+ # STEP 5: Extract response from final state
158
+ # ============================================================
159
 
160
+ # Òœ… The respond node stores response in temp_data
161
+ final_response = final_state.temp_data.get("final_response")
162
 
163
+ if final_response:
164
+ logger.info(
165
+ "βœ… Response extracted from state",
166
+ action=final_response.action,
167
+ success=final_response.success,
168
+ )
169
+ return final_response
170
 
171
  # ============================================================
172
+ # FALLBACK: Build response manually if not in temp_data
173
  # ============================================================
174
 
175
+ logger.warning("⚠️ No final_response in temp_data, building manually")
 
 
 
176
 
177
+ response_text = final_state.temp_data.get("response_text", "")
178
  if not response_text:
179
  response_text = "I'm here to help! What would you like to do?"
180
 
181
  response = AgentResponse(
182
  success=final_state.last_error is None,
183
  text=response_text,
184
+ action=final_state.temp_data.get("action", final_state.current_flow.value),
185
  state={
186
  "flow": final_state.current_flow.value,
187
  "steps": final_state.steps_taken,
188
  "errors": final_state.error_count,
 
 
189
  },
190
+ draft=final_state.temp_data.get("draft"),
191
+ draft_ui=final_state.temp_data.get("draft_ui"),
192
  error=final_state.last_error,
193
  metadata={
194
  "intent": final_state.intent_type,
 
200
  }
201
  )
202
 
203
+ logger.info("βœ… Fallback response built", action=response.action)
 
 
 
 
 
204
 
205
  return response
206
 
207
  except HTTPException:
208
  raise
209
  except Exception as e:
210
+ logger.error("❌ Chat endpoint critical error", exc_info=e)
211
+ return AgentResponse(
212
+ success=False,
213
+ text="An unexpected error occurred. Please try again.",
214
+ action="error",
215
+ error=str(e),
216
  )
217
 
218
 
 
221
  # ============================================================
222
 
223
  @router.get("/health")
224
+ async def health_check() -> Dict[str, Any]:
225
+ """Health check for AIDA chat service"""
226
 
227
  try:
228
  graph = get_aida_graph()
 
232
  "service": "AIDA Chat (LangGraph)",
233
  "version": "2.0.0",
234
  "graph_available": graph is not None,
235
+ "timestamp": datetime.utcnow().isoformat(),
236
  }
237
  except Exception as e:
238
+ logger.error("❌ Health check failed", exc_info=e)
239
  return {
240
  "status": "unhealthy",
241
  "error": str(e),
242
+ "timestamp": datetime.utcnow().isoformat(),
243
  }
244
 
245
 
 
248
  # ============================================================
249
 
250
  @router.get("/history/{session_id}")
251
+ async def get_history(session_id: str) -> Dict[str, Any]:
252
  """Get conversation history for a session"""
253
 
254
  try:
255
+ logger.info("πŸ“– History requested", session_id=session_id)
256
 
257
  return {
258
  "success": True,
259
  "session_id": session_id,
260
  "messages": [],
261
+ "note": "History persistence not yet implemented"
262
  }
263
 
264
  except Exception as e:
265
+ logger.error("❌ History retrieval error", exc_info=e)
266
+ return {
267
+ "success": False,
268
+ "error": str(e),
269
+ }
270
 
271
 
272
  # ============================================================
 
274
  # ============================================================
275
 
276
  @router.post("/reset-session/{session_id}")
277
+ async def reset_session(session_id: str) -> Dict[str, Any]:
278
  """Reset a session to fresh state"""
279
 
280
  try:
281
+ logger.info("πŸ”„ Session reset requested", session_id=session_id)
282
 
283
  return {
284
  "success": True,
285
  "message": "Session reset to fresh state",
286
+ "session_id": session_id,
287
+ "timestamp": datetime.utcnow().isoformat(),
288
+ }
289
+
290
+ except Exception as e:
291
+ logger.error("❌ Session reset error", exc_info=e)
292
+ return {
293
+ "success": False,
294
+ "error": str(e),
295
+ }
296
+
297
+
298
+ @router.post("/close-session/{session_id}")
299
+ async def close_session(session_id: str) -> Dict[str, Any]:
300
+ """Close a session"""
301
+
302
+ try:
303
+ logger.info("❌ Session closed", session_id=session_id)
304
+
305
+ return {
306
+ "success": True,
307
+ "message": "Session closed",
308
+ "session_id": session_id,
309
+ "timestamp": datetime.utcnow().isoformat(),
310
  }
311
 
312
  except Exception as e:
313
+ logger.error("❌ Session close error", exc_info=e)
314
+ return {
315
+ "success": False,
316
+ "error": str(e),
317
+ }