ciyidogan commited on
Commit
cf59f92
·
verified ·
1 Parent(s): 9c2fd74

Update chat_handler.py

Browse files
Files changed (1) hide show
  1. chat_handler.py +52 -34
chat_handler.py CHANGED
@@ -1,9 +1,10 @@
1
  """
2
- Flare – Chat Handler (Spark /generate format)
3
- =============================================
4
  • X-Session-ID header
5
- /generate payload:
6
- {project_name, user_input, context, system_prompt}
 
7
  """
8
 
9
  import re, json, uuid, httpx, commentjson
@@ -22,6 +23,7 @@ CFG = commentjson.load(open("service_config.jsonc", encoding="utf-8"))
22
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
23
  APIS = {a["name"]: a for a in CFG["apis"]}
24
  SPARK_URL = CFG["config"]["spark_endpoint"].rstrip("/") + "/generate"
 
25
 
26
  # --------------------------------------------------------------------------- #
27
  # SESSION
@@ -30,7 +32,7 @@ class Session:
30
  def __init__(self, project_name: str):
31
  self.id = str(uuid.uuid4())
32
  self.project = PROJECTS[project_name]
33
- self.history: List[Dict[str, str]] = [] # {role, content}
34
  self.variables: Dict[str, str] = {}
35
  self.awaiting: Optional[Dict] = None
36
  log(f"🆕 Session {self.id} for {project_name}")
@@ -38,24 +40,24 @@ class Session:
38
  SESSIONS: Dict[str, Session] = {}
39
 
40
  # --------------------------------------------------------------------------- #
41
- # Spark client
42
  # --------------------------------------------------------------------------- #
43
  async def spark_generate(session: Session,
44
  system_prompt: str,
45
  user_input: str) -> str:
46
- """Send request to Spark /generate endpoint"""
47
  payload = {
48
  "project_name": session.project["name"],
49
  "user_input": user_input,
50
- "context": session.history[-10:], # only last 10 turns
51
  "system_prompt": system_prompt
52
  }
53
  async with httpx.AsyncClient(timeout=60) as c:
54
  r = await c.post(SPARK_URL, json=payload)
55
  r.raise_for_status()
56
- # Spark örneğinde cevap key'i "model_answer" olabilir.
57
  data = r.json()
58
- return data.get("assistant") or data.get("model_answer") or data.get("text", "")
 
 
59
 
60
  # --------------------------------------------------------------------------- #
61
  # FASTAPI ROUTER
@@ -97,22 +99,30 @@ async def chat(body: ChatBody,
97
  user_msg = body.user_input.strip()
98
  s.history.append({"role": "user", "content": user_msg})
99
 
100
- # ---------------- follow-up modunda mı?
101
  if s.awaiting:
102
  answer = await _followup(s, user_msg)
103
  s.history.append({"role": "assistant", "content": answer})
104
  return ChatResponse(session_id=s.id, answer=answer)
105
 
106
- # ---------------- intent detection ----------------
107
  gen_prompt = s.project["versions"][0]["general_prompt"]
108
- intent_out = await spark_generate(s, gen_prompt, user_msg)
109
-
110
- if not intent_out.startswith("#DETECTED_INTENT:"):
111
- s.history.append({"role": "assistant", "content": intent_out})
112
- return ChatResponse(session_id=s.id, answer=intent_out)
113
-
114
- intent_name = intent_out.split(":", 1)[1].strip()
115
- intent_cfg = _find_intent(s.project, intent_name)
 
 
 
 
 
 
 
 
116
  if not intent_cfg:
117
  err = "Üzgünüm, anlayamadım."
118
  s.history.append({"role": "assistant", "content": err})
@@ -123,10 +133,11 @@ async def chat(body: ChatBody,
123
  return ChatResponse(session_id=s.id, answer=answer)
124
 
125
  # --------------------------------------------------------------------------- #
126
- # Helper functions
127
  # --------------------------------------------------------------------------- #
128
  def _find_intent(project, name_):
129
- return next((i for i in project["versions"][0]["intents"] if i["name"] == name_), None)
 
130
 
131
  def _missing(s, intent_cfg):
132
  return [p["name"] for p in intent_cfg["parameters"]
@@ -134,17 +145,19 @@ def _missing(s, intent_cfg):
134
 
135
  async def _handle_intent(s, intent_cfg, user_msg):
136
  missing = _missing(s, intent_cfg)
 
137
  if missing:
138
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
139
- p_out = await spark_generate(s, p_prompt, user_msg)
140
- if p_out.startswith("#PARAMETERS:"):
141
- if bad := _process_params(s, intent_cfg, p_out):
142
  return bad
143
  missing = _missing(s, intent_cfg)
144
 
145
  if missing:
146
  s.awaiting = {"intent": intent_cfg, "missing": missing}
147
- cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
 
148
  return f"{cap} nedir?"
149
 
150
  s.awaiting = None
@@ -154,28 +167,30 @@ async def _followup(s, user_msg):
154
  intent_cfg = s.awaiting["intent"]
155
  missing = s.awaiting["missing"]
156
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
157
- p_out = await spark_generate(s, p_prompt, user_msg)
158
- if not p_out.startswith("#PARAMETERS:"):
159
  return "Üzgünüm, anlayamadım."
160
- if bad := _process_params(s, intent_cfg, p_out):
161
  return bad
162
 
163
  missing = _missing(s, intent_cfg)
164
  if missing:
165
  s.awaiting["missing"] = missing
166
- cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
 
167
  return f"{cap} nedir?"
168
 
169
  s.awaiting = None
170
  return await _call_api(s, intent_cfg)
171
 
172
- def _process_params(s, intent_cfg, p_out):
173
  try:
174
- data = json.loads(p_out[len("#PARAMETERS:"):])
175
  except json.JSONDecodeError:
176
  return "Parametreleri çözemedim."
177
  for pair in data.get("extracted", []):
178
- p_cfg = next(p for p in intent_cfg["parameters"] if p["name"] == pair["name"])
 
179
  if not _valid(p_cfg, pair["value"]):
180
  return p_cfg.get("invalid_prompt", "Geçersiz değer.")
181
  s.variables[p_cfg["variable_name"]] = pair["value"]
@@ -189,13 +204,16 @@ async def _call_api(s, intent_cfg):
189
  api = APIS[intent_cfg["action"]]
190
  token = "testtoken"
191
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
 
192
  body = json.loads(json.dumps(api["body_template"]))
193
  for k, v in body.items():
194
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
195
  body[k] = s.variables.get(v[2:-2], "")
 
196
  try:
197
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
198
- r = await c.request(api["method"], api["url"], headers=headers, json=body)
 
199
  r.raise_for_status()
200
  api_json = r.json()
201
  except Exception:
 
1
  """
2
+ Flare – Chat Handler (Spark /generate + safe-intent)
3
+ ====================================================
4
  • X-Session-ID header
5
+ Spark payload: {project_name, user_input, context, system_prompt}
6
+ Eğer model #DETECTED_INTENT:<label> döndürür ve <label> izinli liste
7
+ dışında ise, mesaj small-talk olarak kabul edilir.
8
  """
9
 
10
  import re, json, uuid, httpx, commentjson
 
23
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
24
  APIS = {a["name"]: a for a in CFG["apis"]}
25
  SPARK_URL = CFG["config"]["spark_endpoint"].rstrip("/") + "/generate"
26
+ ALLOWED_INTENTS = {"flight-booking", "flight-info", "booking-cancel"}
27
 
28
  # --------------------------------------------------------------------------- #
29
  # SESSION
 
32
  def __init__(self, project_name: str):
33
  self.id = str(uuid.uuid4())
34
  self.project = PROJECTS[project_name]
35
+ self.history: List[Dict[str, str]] = [] # {"role","content"}
36
  self.variables: Dict[str, str] = {}
37
  self.awaiting: Optional[Dict] = None
38
  log(f"🆕 Session {self.id} for {project_name}")
 
40
  SESSIONS: Dict[str, Session] = {}
41
 
42
  # --------------------------------------------------------------------------- #
43
+ # SPARK CLIENT
44
  # --------------------------------------------------------------------------- #
45
  async def spark_generate(session: Session,
46
  system_prompt: str,
47
  user_input: str) -> str:
 
48
  payload = {
49
  "project_name": session.project["name"],
50
  "user_input": user_input,
51
+ "context": session.history[-10:], # last 10 turns
52
  "system_prompt": system_prompt
53
  }
54
  async with httpx.AsyncClient(timeout=60) as c:
55
  r = await c.post(SPARK_URL, json=payload)
56
  r.raise_for_status()
 
57
  data = r.json()
58
+ return (data.get("assistant") or
59
+ data.get("model_answer") or
60
+ data.get("text", "")).strip()
61
 
62
  # --------------------------------------------------------------------------- #
63
  # FASTAPI ROUTER
 
99
  user_msg = body.user_input.strip()
100
  s.history.append({"role": "user", "content": user_msg})
101
 
102
+ # ---------------- Follow-up? ----------------
103
  if s.awaiting:
104
  answer = await _followup(s, user_msg)
105
  s.history.append({"role": "assistant", "content": answer})
106
  return ChatResponse(session_id=s.id, answer=answer)
107
 
108
+ # ---------------- Intent detection ----------------
109
  gen_prompt = s.project["versions"][0]["general_prompt"]
110
+ intent_raw = await spark_generate(s, gen_prompt, user_msg)
111
+
112
+ # --- Etiket yoksa → small talk cevabı olduğu gibi döndür
113
+ if not intent_raw.startswith("#DETECTED_INTENT:"):
114
+ s.history.append({"role": "assistant", "content": intent_raw})
115
+ return ChatResponse(session_id=s.id, answer=intent_raw)
116
+
117
+ # --- Etiket var → ayrıştır
118
+ intent_name = intent_raw.split(":", 1)[1].strip()
119
+ if intent_name not in ALLOWED_INTENTS:
120
+ # Etiket geçersiz → small talk olarak ilk kısmı döndür
121
+ clean = intent_raw.split("#DETECTED_INTENT")[0].split("\nassistant")[0].strip()
122
+ s.history.append({"role": "assistant", "content": clean})
123
+ return ChatResponse(session_id=s.id, answer=clean)
124
+
125
+ intent_cfg = _find_intent(s.project, intent_name)
126
  if not intent_cfg:
127
  err = "Üzgünüm, anlayamadım."
128
  s.history.append({"role": "assistant", "content": err})
 
133
  return ChatResponse(session_id=s.id, answer=answer)
134
 
135
  # --------------------------------------------------------------------------- #
136
+ # HELPER FUNCS
137
  # --------------------------------------------------------------------------- #
138
  def _find_intent(project, name_):
139
+ return next((i for i in project["versions"][0]["intents"]
140
+ if i["name"] == name_), None)
141
 
142
  def _missing(s, intent_cfg):
143
  return [p["name"] for p in intent_cfg["parameters"]
 
145
 
146
  async def _handle_intent(s, intent_cfg, user_msg):
147
  missing = _missing(s, intent_cfg)
148
+
149
  if missing:
150
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
151
+ p_raw = await spark_generate(s, p_prompt, user_msg)
152
+ if p_raw.startswith("#PARAMETERS:"):
153
+ if bad := _process_params(s, intent_cfg, p_raw):
154
  return bad
155
  missing = _missing(s, intent_cfg)
156
 
157
  if missing:
158
  s.awaiting = {"intent": intent_cfg, "missing": missing}
159
+ cap = next(p for p in intent_cfg["parameters"]
160
+ if p["name"] == missing[0])["caption"]
161
  return f"{cap} nedir?"
162
 
163
  s.awaiting = None
 
167
  intent_cfg = s.awaiting["intent"]
168
  missing = s.awaiting["missing"]
169
  p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
170
+ p_raw = await spark_generate(s, p_prompt, user_msg)
171
+ if not p_raw.startswith("#PARAMETERS:"):
172
  return "Üzgünüm, anlayamadım."
173
+ if bad := _process_params(s, intent_cfg, p_raw):
174
  return bad
175
 
176
  missing = _missing(s, intent_cfg)
177
  if missing:
178
  s.awaiting["missing"] = missing
179
+ cap = next(p for p in intent_cfg["parameters"]
180
+ if p["name"] == missing[0])["caption"]
181
  return f"{cap} nedir?"
182
 
183
  s.awaiting = None
184
  return await _call_api(s, intent_cfg)
185
 
186
+ def _process_params(s, intent_cfg, p_raw):
187
  try:
188
+ data = json.loads(p_raw[len("#PARAMETERS:"):])
189
  except json.JSONDecodeError:
190
  return "Parametreleri çözemedim."
191
  for pair in data.get("extracted", []):
192
+ p_cfg = next(p for p in intent_cfg["parameters"]
193
+ if p["name"] == pair["name"])
194
  if not _valid(p_cfg, pair["value"]):
195
  return p_cfg.get("invalid_prompt", "Geçersiz değer.")
196
  s.variables[p_cfg["variable_name"]] = pair["value"]
 
204
  api = APIS[intent_cfg["action"]]
205
  token = "testtoken"
206
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
207
+
208
  body = json.loads(json.dumps(api["body_template"]))
209
  for k, v in body.items():
210
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
211
  body[k] = s.variables.get(v[2:-2], "")
212
+
213
  try:
214
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
215
+ r = await c.request(api["method"], api["url"],
216
+ headers=headers, json=body)
217
  r.raise_for_status()
218
  api_json = r.json()
219
  except Exception: