ciyidogan commited on
Commit
c7293c2
Β·
verified Β·
1 Parent(s): ed22a9d

Update chat_handler.py

Browse files
Files changed (1) hide show
  1. chat_handler.py +294 -167
chat_handler.py CHANGED
@@ -1,18 +1,19 @@
1
  """
2
- Flare – Chat Handler (v1.4 Β· tutarsΔ±zlΔ±klar giderildi)
3
- =======================================================
4
  """
5
 
6
- import re, json, uuid, sys, httpx, commentjson
7
  from datetime import datetime
8
  from typing import Dict, List, Optional
9
  from fastapi import APIRouter, HTTPException, Header
10
  from pydantic import BaseModel
11
- from commentjson import JSONLibraryException
12
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
13
  from api_executor import call_api as execute_api
14
  from config_provider import ConfigProvider
15
  from validation_engine import validate
 
16
 
17
  # ───────────────────────── HELPERS ───────────────────────── #
18
  def _trim_response(raw: str) -> str:
@@ -29,7 +30,7 @@ def _trim_response(raw: str) -> str:
29
  raw = re.sub(r"Hoş[\s-]?geldin(iz)?", "Hoş geldiniz", raw, flags=re.IGNORECASE)
30
  return raw.strip()
31
 
32
- def _safe_intent_parse(raw: str) -> (str, str):
33
  """Extract intent name and extra tail."""
34
  m = re.search(r"#DETECTED_INTENT:\s*([A-Za-z0-9_-]+)", raw)
35
  if not m:
@@ -40,192 +41,318 @@ def _safe_intent_parse(raw: str) -> (str, str):
40
 
41
  # ───────────────────────── CONFIG ───────────────────────── #
42
  cfg = ConfigProvider.get()
43
- PROJECTS = {p.name: p for p in cfg.projects}
44
  SPARK_URL = str(cfg.global_config.spark_endpoint).rstrip("/")
45
  ALLOWED_INTENTS = {"flight-booking", "flight-info", "booking-cancel"}
46
 
47
- # ───────────────────────── SESSION ───────────────────────── #
48
- class Session:
49
- def __init__(self, project_name: str):
50
- self.id = str(uuid.uuid4())
51
- self.project = PROJECTS[project_name]
52
- self.history: List[Dict[str, str]] = []
53
- self.vars: Dict[str, str] = {}
54
- self.awaiting: Optional[Dict] = None
55
- log(f"πŸ†• Session {self.id}")
56
-
57
- SESSIONS: Dict[str, Session] = {}
58
-
59
  # ───────────────────────── SPARK ───────────────────────── #
60
  async def spark_generate(s: Session, prompt: str, user_msg: str) -> str:
61
- # Get the first published version
62
- version = next((v for v in s.project.versions if v.published), None)
63
- if not version:
64
- raise HTTPException(500, "No published version found")
65
-
66
- payload = {
67
- "project_name": s.project.name,
68
- "user_input": user_msg,
69
- "context": s.history[-10:],
70
- "system_prompt": prompt
71
- }
72
- async with httpx.AsyncClient(timeout=60) as c:
73
- r = await c.post(SPARK_URL + "/generate", json=payload)
74
- r.raise_for_status()
75
- d = r.json()
76
- raw = (d.get("assistant") or d.get("model_answer") or d.get("text", "")).strip()
77
- log(f"πŸͺ„ Spark raw: {raw[:120]!r}")
78
- return raw
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  # ───────────────────────── FASTAPI ───────────────────────── #
81
  router = APIRouter()
82
- @router.get("/") # health
83
- def health(): return {"ok": True}
84
-
85
- class Start(BaseModel): project_name: str
86
- class Body(BaseModel): user_input: str
87
- class Resp(BaseModel): session_id: str; answer: str
88
-
89
- @router.post("/start_session", response_model=Resp)
90
- async def start_session(req: Start):
91
- if req.project_name not in PROJECTS:
92
- raise HTTPException(404, "project")
93
- s = Session(req.project_name)
94
- SESSIONS[s.id] = s
95
- greet = "Hoş geldiniz! Size nasıl yardımcı olabilirim?"
96
- return Resp(session_id=s.id, answer=greet)
97
-
98
- @router.post("/chat", response_model=Resp)
99
- async def chat(body: Body, x_session_id: str = Header(...)):
100
- if x_session_id not in SESSIONS:
101
- raise HTTPException(404, "session")
102
- s = SESSIONS[x_session_id]
103
- user = body.user_input.strip()
104
- s.history.append({"role": "user", "content": user})
105
-
106
- # Get the first published version
107
- version = next((v for v in s.project.versions if v.published), None)
108
- if not version:
109
- raise HTTPException(500, "No published version found")
110
-
111
- # ---------- follow-up ----------
112
- if s.awaiting:
113
- ans = await _followup(s, user)
114
- s.history.append({"role": "assistant", "content": ans})
115
- return Resp(session_id=s.id, answer=ans)
116
-
117
- # ---------- intent detect ----------
118
- p = build_intent_prompt(
119
- version.general_prompt,
120
- s.history, user, version.intents)
121
- raw = await spark_generate(s, p, user)
122
 
123
- if raw == "":
124
- fallback = "Üzgünüm, mesajınızı anlayamadım. Lütfen tekrar dener misiniz?"
125
- s.history.append({"role": "assistant", "content": fallback})
126
- return Resp(session_id=s.id, answer=fallback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- # small-talk yolu
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  if not raw.startswith("#DETECTED_INTENT"):
130
- clean = _trim_response(raw)
131
- s.history.append({"role": "assistant", "content": clean})
132
- return Resp(session_id=s.id, answer=clean)
133
-
134
- intent, tail = _safe_intent_parse(raw)
135
- if intent not in ALLOWED_INTENTS or len(user.split()) < 3:
136
- clean = _trim_response(tail)
137
- s.history.append({"role": "assistant", "content": clean})
138
- return Resp(session_id=s.id, answer=clean)
139
-
140
- intent_cfg = _find_intent(s, intent)
141
- if not intent_cfg:
142
- err = "Üzgünüm, anlayamadım."
143
- s.history.append({"role": "assistant", "content": err})
144
- return Resp(session_id=s.id, answer=err)
145
-
146
- answer = await _handle_intent(s, intent_cfg, user)
147
- s.history.append({"role": "assistant", "content": answer})
148
- return Resp(session_id=s.id, answer=answer)
149
-
150
- # ────────────────── INTENT / PARAM / API HELPERS ────────────────── #
151
- def _find_intent(s, name):
152
- version = next((v for v in s.project.versions if v.published), None)
153
- return next((i for i in version.intents if i.name == name), None) if version else None
154
-
155
- def _missing(s, intent_cfg):
156
- return [p.name for p in intent_cfg.parameters if p.required and p.variable_name not in s.vars]
157
-
158
- async def _handle_intent(s, intent_cfg, user):
159
- missing = _missing(s, intent_cfg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  if missing:
161
- prmpt = build_parameter_prompt(intent_cfg, missing, user, s.history)
162
- raw = await spark_generate(s, prmpt, user)
163
- if raw.startswith("#PARAMETERS:"):
164
- _proc_params(s, intent_cfg, raw)
165
- missing = _missing(s, intent_cfg)
 
 
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  if missing:
168
- s.awaiting = {"intent": intent_cfg, "missing": missing}
169
- cap = next(p for p in intent_cfg.parameters if p.name == missing[0]).caption
170
- return f"{cap} nedir?"
171
- s.awaiting = None
172
- return await _call_api(s, intent_cfg)
173
-
174
- async def _followup(s, user):
175
- intent_cfg = s.awaiting["intent"]
176
- miss = s.awaiting["missing"]
177
- prmpt = build_parameter_prompt(intent_cfg, miss, user, s.history)
178
- raw = await spark_generate(s, prmpt, user)
179
- if not raw.startswith("#PARAMETERS:"):
180
- return "Üzgünüm, anlayamadım."
181
-
182
- _proc_params(s, intent_cfg, raw)
183
- miss = _missing(s, intent_cfg)
184
- if miss:
185
- s.awaiting["missing"] = miss
186
- cap = next(p for p in intent_cfg.parameters if p.name == miss[0]).caption
187
- return f"{cap} nedir?"
188
- s.awaiting = None
189
- return await _call_api(s, intent_cfg)
190
-
191
- def _proc_params(s, intent_cfg, raw):
192
- try:
193
- d = json.loads(raw[len("#PARAMETERS:"):])
194
- except:
195
- return False
196
 
197
- for pr in d.get("extracted", []):
198
- pcfg = next((p for p in intent_cfg.parameters if p.name == pr["name"]), None)
199
- if not pcfg:
200
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- # Use validation engine
203
- if not validate(pr["value"], pcfg):
204
- continue
 
 
 
 
 
 
 
 
 
 
 
205
 
206
- s.vars[pcfg.variable_name] = pr["value"]
207
- return True
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- async def _call_api(s, intent_cfg):
210
- """Execute API call and humanize response"""
211
- api_name = intent_cfg.action
212
- api_cfg = cfg.get_api(api_name)
213
- if not api_cfg:
214
- return intent_cfg.get("fallback_error_prompt", "İşlem başarısız oldu.")
215
-
216
  try:
217
- # Call API using api_executor
218
- response = execute_api(api_cfg, s.vars)
 
 
 
 
 
 
 
 
 
 
219
  api_json = response.json()
220
 
221
  # Humanize response
222
- if api_cfg.response_prompt:
223
- prompt = api_cfg.response_prompt.replace("{{api_response}}", json.dumps(api_json, ensure_ascii=False))
224
- human_response = await spark_generate(s, prompt, json.dumps(api_json))
 
 
 
 
 
225
  return human_response if human_response else f"İşlem sonucu: {api_json}"
226
  else:
227
- return f"İşlem başarılı: {api_json}"
 
228
 
 
 
 
229
  except Exception as e:
230
  log(f"❌ API call error: {e}")
231
- return intent_cfg.get("fallback_error_prompt", "İşlem sırasında bir hata oluştu.")
 
 
1
  """
2
+ Flare – Chat Handler (v1.5 Β· modΓΌler yapΔ±)
3
+ ==========================================
4
  """
5
 
6
+ import re, json, sys, httpx
7
  from datetime import datetime
8
  from typing import Dict, List, Optional
9
  from fastapi import APIRouter, HTTPException, Header
10
  from pydantic import BaseModel
11
+
12
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
13
  from api_executor import call_api as execute_api
14
  from config_provider import ConfigProvider
15
  from validation_engine import validate
16
+ from session import session_store, Session
17
 
18
  # ───────────────────────── HELPERS ───────────────────────── #
19
  def _trim_response(raw: str) -> str:
 
30
  raw = re.sub(r"Hoş[\s-]?geldin(iz)?", "Hoş geldiniz", raw, flags=re.IGNORECASE)
31
  return raw.strip()
32
 
33
+ def _safe_intent_parse(raw: str) -> tuple[str, str]:
34
  """Extract intent name and extra tail."""
35
  m = re.search(r"#DETECTED_INTENT:\s*([A-Za-z0-9_-]+)", raw)
36
  if not m:
 
41
 
42
  # ───────────────────────── CONFIG ───────────────────────── #
43
  cfg = ConfigProvider.get()
 
44
  SPARK_URL = str(cfg.global_config.spark_endpoint).rstrip("/")
45
  ALLOWED_INTENTS = {"flight-booking", "flight-info", "booking-cancel"}
46
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  # ───────────────────────── SPARK ───────────────────────── #
48
  async def spark_generate(s: Session, prompt: str, user_msg: str) -> str:
49
+ """Call Spark with proper error handling"""
50
+ try:
51
+ project = next((p for p in cfg.projects if p.name == s.project_name), None)
52
+ if not project:
53
+ raise ValueError(f"Project not found: {s.project_name}")
54
+
55
+ version = next((v for v in project.versions if v.published), None)
56
+ if not version:
57
+ raise ValueError("No published version found")
58
+
59
+ payload = {
60
+ "project_name": s.project_name,
61
+ "user_input": user_msg,
62
+ "context": s.chat_history[-10:],
63
+ "system_prompt": prompt
64
+ }
65
+
66
+ log(f"πŸš€ Calling Spark for session {s.session_id[:8]}...")
67
+ async with httpx.AsyncClient(timeout=60) as client:
68
+ response = await client.post(SPARK_URL + "/generate", json=payload)
69
+ response.raise_for_status()
70
+ data = response.json()
71
+ raw = (data.get("assistant") or data.get("model_answer") or data.get("text", "")).strip()
72
+ log(f"πŸͺ„ Spark raw: {raw[:120]!r}")
73
+ return raw
74
+
75
+ except httpx.TimeoutException:
76
+ log(f"⏱️ Spark timeout for session {s.session_id[:8]}")
77
+ raise
78
+ except Exception as e:
79
+ log(f"❌ Spark error: {e}")
80
+ raise
81
 
82
  # ───────────────────────── FASTAPI ───────────────────────── #
83
  router = APIRouter()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ @router.get("/")
86
+ def health():
87
+ return {"status": "ok", "sessions": len(session_store._sessions)}
88
+
89
+ class StartRequest(BaseModel):
90
+ project_name: str
91
+
92
+ class ChatRequest(BaseModel):
93
+ user_input: str
94
+
95
+ class ChatResponse(BaseModel):
96
+ session_id: str
97
+ answer: str
98
+
99
+ @router.post("/start_session", response_model=ChatResponse)
100
+ async def start_session(req: StartRequest):
101
+ """Create new session"""
102
+ try:
103
+ # Validate project exists
104
+ project = next((p for p in cfg.projects if p.name == req.project_name and p.enabled), None)
105
+ if not project:
106
+ raise HTTPException(404, f"Project '{req.project_name}' not found or disabled")
107
+
108
+ # Create session
109
+ session = session_store.create_session(req.project_name)
110
+ greeting = "Hoş geldiniz! Size nasıl yardımcı olabilirim?"
111
+ session.add_turn("assistant", greeting)
112
+
113
+ return ChatResponse(session_id=session.session_id, answer=greeting)
114
+
115
+ except Exception as e:
116
+ log(f"❌ Error creating session: {e}")
117
+ raise HTTPException(500, str(e))
118
+
119
+ @router.post("/chat", response_model=ChatResponse)
120
+ async def chat(body: ChatRequest, x_session_id: str = Header(...)):
121
+ """Process chat message"""
122
+ try:
123
+ # Get session
124
+ session = session_store.get_session(x_session_id)
125
+ if not session:
126
+ raise HTTPException(404, "Session not found")
127
+
128
+ user_input = body.user_input.strip()
129
+ if not user_input:
130
+ raise HTTPException(400, "Empty message")
131
+
132
+ session.add_turn("user", user_input)
133
+
134
+ # Get project config
135
+ project = next((p for p in cfg.projects if p.name == session.project_name), None)
136
+ if not project:
137
+ raise HTTPException(500, "Project configuration lost")
138
+
139
+ version = next((v for v in project.versions if v.published), None)
140
+ if not version:
141
+ raise HTTPException(500, "No published version")
142
 
143
+ # Handle based on state
144
+ if session.state == "await_param":
145
+ answer = await _handle_parameter_followup(session, user_input, version)
146
+ else:
147
+ answer = await _handle_new_message(session, user_input, version)
148
+
149
+ session.add_turn("assistant", answer)
150
+ return ChatResponse(session_id=session.session_id, answer=answer)
151
+
152
+ except HTTPException:
153
+ raise
154
+ except Exception as e:
155
+ log(f"❌ Chat error: {e}")
156
+ session.reset_flow()
157
+ error_msg = "Bir hata oluştu. Lütfen tekrar deneyin."
158
+ session.add_turn("assistant", error_msg)
159
+ return ChatResponse(session_id=x_session_id, answer=error_msg)
160
+
161
+ # ───────────────────────── MESSAGE HANDLERS ───────────────────────── #
162
+ async def _handle_new_message(session: Session, user_input: str, version) -> str:
163
+ """Handle new message (not parameter followup)"""
164
+ # Build intent detection prompt
165
+ prompt = build_intent_prompt(
166
+ version.general_prompt,
167
+ session.chat_history,
168
+ user_input,
169
+ version.intents
170
+ )
171
+
172
+ # Get Spark response
173
+ raw = await spark_generate(session, prompt, user_input)
174
+
175
+ # Empty response fallback
176
+ if not raw:
177
+ return "Üzgünüm, mesajınızı anlayamadım. Lütfen tekrar dener misiniz?"
178
+
179
+ # Check for intent
180
  if not raw.startswith("#DETECTED_INTENT"):
181
+ # Small talk response
182
+ return _trim_response(raw)
183
+
184
+ # Parse intent
185
+ intent_name, tail = _safe_intent_parse(raw)
186
+
187
+ # Validate intent
188
+ if intent_name not in ALLOWED_INTENTS:
189
+ return _trim_response(tail) if tail else "Size nasΔ±l yardΔ±mcΔ± olabilirim?"
190
+
191
+ # Short message guard (less than 3 words usually means incomplete request)
192
+ if len(user_input.split()) < 3 and intent_name != "flight-info":
193
+ return _trim_response(tail) if tail else "LΓΌtfen talebinizi biraz daha detaylandΔ±rΔ±r mΔ±sΔ±nΔ±z?"
194
+
195
+ # Find intent config
196
+ intent_config = next((i for i in version.intents if i.name == intent_name), None)
197
+ if not intent_config:
198
+ return "Üzgünüm, bu işlemi gerçekleştiremiyorum."
199
+
200
+ # Set intent in session
201
+ session.last_intent = intent_name
202
+
203
+ # Extract parameters
204
+ return await _extract_parameters(session, intent_config, user_input)
205
+
206
+ async def _handle_parameter_followup(session: Session, user_input: str, version) -> str:
207
+ """Handle parameter collection followup"""
208
+ if not session.last_intent:
209
+ session.reset_flow()
210
+ return "Üzgünüm, hangi işlem için bilgi istediğimi unuttum. Baştan başlayalım."
211
+
212
+ # Get intent config
213
+ intent_config = next((i for i in version.intents if i.name == session.last_intent), None)
214
+ if not intent_config:
215
+ session.reset_flow()
216
+ return "Bir hata oluştu. Lütfen tekrar deneyin."
217
+
218
+ # Try to extract missing parameters
219
+ missing = session.awaiting_parameters
220
+ prompt = build_parameter_prompt(intent_config, missing, user_input, session.chat_history)
221
+ raw = await spark_generate(session, prompt, user_input)
222
+
223
+ if not raw.startswith("#PARAMETERS:"):
224
+ # Increment miss count
225
+ session.missing_ask_count += 1
226
+ if session.missing_ask_count >= 3:
227
+ session.reset_flow()
228
+ return "Üzgünüm, istediğiniz bilgileri anlayamadım. Başka bir konuda yardımcı olabilir miyim?"
229
+ return "Üzgünüm, anlayamadım. Lütfen tekrar sâyler misiniz?"
230
+
231
+ # Process parameters
232
+ success = _process_parameters(session, intent_config, raw)
233
+ if not success:
234
+ return "Girdiğiniz bilgilerde bir hata var. Lütfen kontrol edip tekrar deneyin."
235
+
236
+ # Check if we have all required parameters
237
+ missing = _get_missing_parameters(session, intent_config)
238
  if missing:
239
+ session.awaiting_parameters = missing
240
+ param = next(p for p in intent_config.parameters if p.name == missing[0])
241
+ return f"{param.caption} bilgisini alabilir miyim?"
242
+
243
+ # All parameters collected, call API
244
+ session.state = "call_api"
245
+ return await _execute_api_call(session, intent_config)
246
 
247
+ # ───────────────────────── PARAMETER HANDLING ───────────────────────── #
248
+ async def _extract_parameters(session: Session, intent_config, user_input: str) -> str:
249
+ """Extract parameters from user input"""
250
+ missing = _get_missing_parameters(session, intent_config)
251
+ if not missing:
252
+ # All parameters already available
253
+ return await _execute_api_call(session, intent_config)
254
+
255
+ # Build parameter extraction prompt
256
+ prompt = build_parameter_prompt(intent_config, missing, user_input, session.chat_history)
257
+ raw = await spark_generate(session, prompt, user_input)
258
+
259
+ if raw.startswith("#PARAMETERS:"):
260
+ success = _process_parameters(session, intent_config, raw)
261
+ if success:
262
+ missing = _get_missing_parameters(session, intent_config)
263
+
264
  if missing:
265
+ # Still missing parameters
266
+ session.state = "await_param"
267
+ session.awaiting_parameters = missing
268
+ session.missing_ask_count = 0
269
+ param = next(p for p in intent_config.parameters if p.name == missing[0])
270
+ return f"{param.caption} bilgisini alabilir miyim?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ # All parameters collected
273
+ return await _execute_api_call(session, intent_config)
274
+
275
+ def _get_missing_parameters(session: Session, intent_config) -> List[str]:
276
+ """Get list of missing required parameters"""
277
+ return [
278
+ p.name for p in intent_config.parameters
279
+ if p.required and p.variable_name not in session.variables
280
+ ]
281
+
282
+ def _process_parameters(session: Session, intent_config, raw: str) -> bool:
283
+ """Process parameter extraction response"""
284
+ try:
285
+ json_str = raw[len("#PARAMETERS:"):]
286
+ data = json.loads(json_str)
287
+
288
+ extracted = data.get("extracted", [])
289
+ any_valid = False
290
 
291
+ for param_data in extracted:
292
+ param_name = param_data.get("name")
293
+ param_value = param_data.get("value")
294
+
295
+ if not param_name or not param_value:
296
+ continue
297
+
298
+ # Find parameter config
299
+ param_config = next(
300
+ (p for p in intent_config.parameters if p.name == param_name),
301
+ None
302
+ )
303
+ if not param_config:
304
+ continue
305
 
306
+ # Validate parameter
307
+ if validate(str(param_value), param_config):
308
+ session.variables[param_config.variable_name] = str(param_value)
309
+ any_valid = True
310
+ log(f"βœ… Extracted {param_name}={param_value}")
311
+ else:
312
+ log(f"❌ Invalid {param_name}={param_value}")
313
+
314
+ return any_valid
315
+
316
+ except Exception as e:
317
+ log(f"❌ Parameter processing error: {e}")
318
+ return False
319
 
320
+ # ───────────────────────── API EXECUTION ───────────────────────── #
321
+ async def _execute_api_call(session: Session, intent_config) -> str:
322
+ """Execute API call and return humanized response"""
 
 
 
 
323
  try:
324
+ session.state = "call_api"
325
+ api_name = intent_config.action
326
+ api_config = cfg.get_api(api_name)
327
+
328
+ if not api_config:
329
+ session.reset_flow()
330
+ return intent_config.get("fallback_error_prompt", "İşlem başarısız oldu.")
331
+
332
+ log(f"πŸ“‘ Calling API: {api_name}")
333
+
334
+ # Execute API call
335
+ response = execute_api(api_config, session.variables)
336
  api_json = response.json()
337
 
338
  # Humanize response
339
+ session.state = "humanize"
340
+ if api_config.response_prompt:
341
+ prompt = api_config.response_prompt.replace(
342
+ "{{api_response}}",
343
+ json.dumps(api_json, ensure_ascii=False)
344
+ )
345
+ human_response = await spark_generate(session, prompt, json.dumps(api_json))
346
+ session.reset_flow()
347
  return human_response if human_response else f"İşlem sonucu: {api_json}"
348
  else:
349
+ session.reset_flow()
350
+ return f"İşlem tamamlandı: {api_json}"
351
 
352
+ except httpx.TimeoutException:
353
+ session.reset_flow()
354
+ return intent_config.get("fallback_timeout_prompt", "İşlem zaman aşımına uğradı.")
355
  except Exception as e:
356
  log(f"❌ API call error: {e}")
357
+ session.reset_flow()
358
+ return intent_config.get("fallback_error_prompt", "İşlem sırasında bir hata oluştu.")