ciyidogan commited on
Commit
df458ea
·
verified ·
1 Parent(s): 25b58a2

Update chat_handler.py

Browse files
Files changed (1) hide show
  1. chat_handler.py +37 -58
chat_handler.py CHANGED
@@ -1,14 +1,11 @@
1
  """
2
- Flare – Chat Handler (Spark /generate + safe-intent + config-validate)
3
- ======================================================================
4
- • X-Session-ID header
5
- • Config JSONC parse hatası -> log + graceful exit
6
  """
7
 
8
  import re, json, uuid, sys, httpx, commentjson
9
  from datetime import datetime
10
  from typing import Dict, List, Optional
11
-
12
  from fastapi import APIRouter, HTTPException, Header
13
  from pydantic import BaseModel
14
  from commentjson import JSONLibraryException
@@ -16,7 +13,7 @@ from commentjson import JSONLibraryException
16
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
17
 
18
  # --------------------------------------------------------------------------- #
19
- # CONFIG LOADING + VALIDATION
20
  # --------------------------------------------------------------------------- #
21
  def load_config(path: str = "service_config.jsonc") -> dict:
22
  try:
@@ -24,16 +21,11 @@ def load_config(path: str = "service_config.jsonc") -> dict:
24
  cfg = commentjson.load(f)
25
  log("✅ service_config.jsonc parsed successfully.")
26
  return cfg
27
- except JSONLibraryException as e:
28
- log("❌ CONFIG PARSE ERROR:")
29
- log(str(e))
30
- sys.exit(1)
31
- except FileNotFoundError:
32
- log(f"❌ Config file '{path}' not found.")
33
  sys.exit(1)
34
 
35
  CFG = load_config()
36
-
37
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
38
  APIS = {a["name"]: a for a in CFG["apis"]}
39
  SPARK_URL = CFG["config"]["spark_endpoint"].rstrip("/") + "/generate"
@@ -62,16 +54,14 @@ async def spark_generate(session: Session,
62
  payload = {
63
  "project_name": session.project["name"],
64
  "user_input": user_input,
65
- "context": session.history[-10:], # last 10 turns
66
  "system_prompt": system_prompt
67
  }
68
  async with httpx.AsyncClient(timeout=60) as c:
69
  r = await c.post(SPARK_URL, json=payload)
70
  r.raise_for_status()
71
  data = r.json()
72
- return (data.get("assistant") or
73
- data.get("model_answer") or
74
- data.get("text", "")).strip()
75
 
76
  # --------------------------------------------------------------------------- #
77
  # FASTAPI ROUTER
@@ -84,10 +74,8 @@ def health():
84
 
85
  class StartSessionRequest(BaseModel):
86
  project_name: str
87
-
88
  class ChatBody(BaseModel):
89
  user_input: str
90
-
91
  class ChatResponse(BaseModel):
92
  session_id: str
93
  answer: str
@@ -104,8 +92,7 @@ async def start_session(req: StartSessionRequest):
104
  return ChatResponse(session_id=s.id, answer="Nasıl yardımcı olabilirim?")
105
 
106
  @router.post("/chat", response_model=ChatResponse)
107
- async def chat(body: ChatBody,
108
- x_session_id: str = Header(...)):
109
  if x_session_id not in SESSIONS:
110
  raise HTTPException(404, "Invalid session")
111
 
@@ -113,21 +100,30 @@ async def chat(body: ChatBody,
113
  user_msg = body.user_input.strip()
114
  s.history.append({"role": "user", "content": user_msg})
115
 
116
- # ---------------- Follow-up?
117
  if s.awaiting:
118
  answer = await _followup(s, user_msg)
119
  s.history.append({"role": "assistant", "content": answer})
120
  return ChatResponse(session_id=s.id, answer=answer)
121
 
122
- # ---------------- Intent detect
123
  gen_prompt = s.project["versions"][0]["general_prompt"]
124
  intent_raw = await spark_generate(s, gen_prompt, user_msg)
125
 
 
126
  if not intent_raw.startswith("#DETECTED_INTENT:"):
127
  s.history.append({"role": "assistant", "content": intent_raw})
128
  return ChatResponse(session_id=s.id, answer=intent_raw)
129
 
130
  intent_name = intent_raw.split(":", 1)[1].strip()
 
 
 
 
 
 
 
 
131
  if intent_name not in ALLOWED_INTENTS:
132
  clean = intent_raw.split("#DETECTED_INTENT")[0].split("\nassistant")[0].strip()
133
  s.history.append({"role": "assistant", "content": clean})
@@ -144,30 +140,25 @@ async def chat(body: ChatBody,
144
  return ChatResponse(session_id=s.id, answer=answer)
145
 
146
  # --------------------------------------------------------------------------- #
147
- # HELPER FUNCS
148
  # --------------------------------------------------------------------------- #
149
  def _find_intent(project, name_):
150
- return next((i for i in project["versions"][0]["intents"]
151
- if i["name"] == name_), None)
152
 
153
  def _missing(s, intent_cfg):
154
- return [p["name"] for p in intent_cfg["parameters"]
155
- if p["variable_name"] not in s.variables]
156
 
157
  async def _handle_intent(s, intent_cfg, user_msg):
158
  missing = _missing(s, intent_cfg)
159
  if missing:
160
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
161
- p_raw = await spark_generate(s, p_prompt, user_msg)
162
- if p_raw.startswith("#PARAMETERS:"):
163
- if bad := _process_params(s, intent_cfg, p_raw):
164
- return bad
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"]
170
- if p["name"] == missing[0])["caption"]
171
  return f"{cap} nedir?"
172
 
173
  s.awaiting = None
@@ -175,36 +166,30 @@ async def _handle_intent(s, intent_cfg, user_msg):
175
 
176
  async def _followup(s, user_msg):
177
  intent_cfg = s.awaiting["intent"]
178
- missing = s.awaiting["missing"]
179
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
180
- p_raw = await spark_generate(s, p_prompt, user_msg)
181
- if not p_raw.startswith("#PARAMETERS:"):
182
  return "Üzgünüm, anlayamadım."
183
- if bad := _process_params(s, intent_cfg, p_raw):
184
- return bad
185
-
186
  missing = _missing(s, intent_cfg)
187
  if missing:
188
  s.awaiting["missing"] = missing
189
- cap = next(p for p in intent_cfg["parameters"]
190
- if p["name"] == missing[0])["caption"]
191
  return f"{cap} nedir?"
192
-
193
  s.awaiting = None
194
  return await _call_api(s, intent_cfg)
195
 
196
- def _process_params(s, intent_cfg, p_raw):
197
  try:
198
- data = json.loads(p_raw[len("#PARAMETERS:"):])
199
  except json.JSONDecodeError:
200
- return "Parametreleri çözemedim."
201
  for pair in data.get("extracted", []):
202
- p_cfg = next(p for p in intent_cfg["parameters"]
203
- if p["name"] == pair["name"])
204
  if not _valid(p_cfg, pair["value"]):
205
- return p_cfg.get("invalid_prompt", "Geçersiz değer.")
206
  s.variables[p_cfg["variable_name"]] = pair["value"]
207
- return None
208
 
209
  def _valid(p_cfg, val):
210
  rx = p_cfg.get("validation_regex")
@@ -214,22 +199,16 @@ async def _call_api(s, intent_cfg):
214
  api = APIS[intent_cfg["action"]]
215
  token = "testtoken"
216
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
217
-
218
  body = json.loads(json.dumps(api["body_template"]))
219
  for k, v in body.items():
220
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
221
  body[k] = s.variables.get(v[2:-2], "")
222
-
223
  try:
224
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
225
- r = await c.request(api["method"], api["url"],
226
- headers=headers, json=body)
227
  r.raise_for_status()
228
  api_json = r.json()
229
  except Exception:
230
  return intent_cfg["fallback_error_prompt"]
231
-
232
- summary_prompt = api["response_prompt"].replace(
233
- "{{api_response}}", json.dumps(api_json, ensure_ascii=False)
234
- )
235
  return await spark_generate(s, summary_prompt, "")
 
1
  """
2
+ Flare – Chat Handler (Spark /generate + safe-intent + config-validate + short-msg guard)
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
 
13
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
14
 
15
  # --------------------------------------------------------------------------- #
16
+ # CONFIG LOAD
17
  # --------------------------------------------------------------------------- #
18
  def load_config(path: str = "service_config.jsonc") -> dict:
19
  try:
 
21
  cfg = commentjson.load(f)
22
  log("✅ service_config.jsonc parsed successfully.")
23
  return cfg
24
+ except (JSONLibraryException, FileNotFoundError) as e:
25
+ log(f"❌ CONFIG ERROR: {e}")
 
 
 
 
26
  sys.exit(1)
27
 
28
  CFG = load_config()
 
29
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
30
  APIS = {a["name"]: a for a in CFG["apis"]}
31
  SPARK_URL = CFG["config"]["spark_endpoint"].rstrip("/") + "/generate"
 
54
  payload = {
55
  "project_name": session.project["name"],
56
  "user_input": user_input,
57
+ "context": session.history[-10:],
58
  "system_prompt": system_prompt
59
  }
60
  async with httpx.AsyncClient(timeout=60) as c:
61
  r = await c.post(SPARK_URL, json=payload)
62
  r.raise_for_status()
63
  data = r.json()
64
+ return (data.get("assistant") or data.get("model_answer") or data.get("text", "")).strip()
 
 
65
 
66
  # --------------------------------------------------------------------------- #
67
  # FASTAPI ROUTER
 
74
 
75
  class StartSessionRequest(BaseModel):
76
  project_name: str
 
77
  class ChatBody(BaseModel):
78
  user_input: str
 
79
  class ChatResponse(BaseModel):
80
  session_id: str
81
  answer: str
 
92
  return ChatResponse(session_id=s.id, answer="Nasıl yardımcı olabilirim?")
93
 
94
  @router.post("/chat", response_model=ChatResponse)
95
+ async def chat(body: ChatBody, x_session_id: str = Header(...)):
 
96
  if x_session_id not in SESSIONS:
97
  raise HTTPException(404, "Invalid session")
98
 
 
100
  user_msg = body.user_input.strip()
101
  s.history.append({"role": "user", "content": user_msg})
102
 
103
+ # ---------------- follow-up?
104
  if s.awaiting:
105
  answer = await _followup(s, user_msg)
106
  s.history.append({"role": "assistant", "content": answer})
107
  return ChatResponse(session_id=s.id, answer=answer)
108
 
109
+ # ---------------- intent detect
110
  gen_prompt = s.project["versions"][0]["general_prompt"]
111
  intent_raw = await spark_generate(s, gen_prompt, user_msg)
112
 
113
+ # small-talk?
114
  if not intent_raw.startswith("#DETECTED_INTENT:"):
115
  s.history.append({"role": "assistant", "content": intent_raw})
116
  return ChatResponse(session_id=s.id, answer=intent_raw)
117
 
118
  intent_name = intent_raw.split(":", 1)[1].strip()
119
+
120
+ # short-message guard: tek/iki kelime selamlaşma + intent bastıysa yok say
121
+ if len(user_msg.split()) < 3:
122
+ clean = intent_raw.split("#DETECTED_INTENT")[0].split("\nassistant")[0].strip()
123
+ s.history.append({"role": "assistant", "content": clean})
124
+ return ChatResponse(session_id=s.id, answer=clean)
125
+
126
+ # allowed-set kontrolü
127
  if intent_name not in ALLOWED_INTENTS:
128
  clean = intent_raw.split("#DETECTED_INTENT")[0].split("\nassistant")[0].strip()
129
  s.history.append({"role": "assistant", "content": clean})
 
140
  return ChatResponse(session_id=s.id, answer=answer)
141
 
142
  # --------------------------------------------------------------------------- #
143
+ # HELPER FUNCS (değişmeyen kısımlar)
144
  # --------------------------------------------------------------------------- #
145
  def _find_intent(project, name_):
146
+ return next((i for i in project["versions"][0]["intents"] if i["name"] == name_), None)
 
147
 
148
  def _missing(s, intent_cfg):
149
+ return [p["name"] for p in intent_cfg["parameters"] if p["variable_name"] not in s.variables]
 
150
 
151
  async def _handle_intent(s, intent_cfg, user_msg):
152
  missing = _missing(s, intent_cfg)
153
  if missing:
154
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
155
+ p_raw = await spark_generate(s, p_prompt, user_msg)
156
+ if p_raw.startswith("#PARAMETERS:") and not _process_params(s, intent_cfg, p_raw):
 
 
157
  missing = _missing(s, intent_cfg)
158
 
159
  if missing:
160
  s.awaiting = {"intent": intent_cfg, "missing": missing}
161
+ cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
 
162
  return f"{cap} nedir?"
163
 
164
  s.awaiting = None
 
166
 
167
  async def _followup(s, user_msg):
168
  intent_cfg = s.awaiting["intent"]
169
+ missing = s.awaiting["missing"]
170
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
171
+ p_raw = await spark_generate(s, p_prompt, user_msg)
172
+ if not p_raw.startswith("#PARAMETERS:") or _process_params(s, intent_cfg, p_raw):
173
  return "Üzgünüm, anlayamadım."
 
 
 
174
  missing = _missing(s, intent_cfg)
175
  if missing:
176
  s.awaiting["missing"] = missing
177
+ cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
 
178
  return f"{cap} nedir?"
 
179
  s.awaiting = None
180
  return await _call_api(s, intent_cfg)
181
 
182
+ def _process_params(s, intent_cfg, raw):
183
  try:
184
+ data = json.loads(raw[len("#PARAMETERS:"):])
185
  except json.JSONDecodeError:
186
+ return True
187
  for pair in data.get("extracted", []):
188
+ p_cfg = next(p for p in intent_cfg["parameters"] if p["name"] == pair["name"])
 
189
  if not _valid(p_cfg, pair["value"]):
190
+ return True
191
  s.variables[p_cfg["variable_name"]] = pair["value"]
192
+ return False
193
 
194
  def _valid(p_cfg, val):
195
  rx = p_cfg.get("validation_regex")
 
199
  api = APIS[intent_cfg["action"]]
200
  token = "testtoken"
201
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
 
202
  body = json.loads(json.dumps(api["body_template"]))
203
  for k, v in body.items():
204
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
205
  body[k] = s.variables.get(v[2:-2], "")
 
206
  try:
207
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
208
+ r = await c.request(api["method"], api["url"], headers=headers, json=body)
 
209
  r.raise_for_status()
210
  api_json = r.json()
211
  except Exception:
212
  return intent_cfg["fallback_error_prompt"]
213
+ summary_prompt = api["response_prompt"].replace("{{api_response}}", json.dumps(api_json, ensure_ascii=False))
 
 
 
214
  return await spark_generate(s, summary_prompt, "")