Spaces:
Building
Building
Update chat_handler.py
Browse files- chat_handler.py +52 -34
chat_handler.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
"""
|
2 |
-
Flare – Chat Handler (Spark /generate
|
3 |
-
|
4 |
• X-Session-ID header
|
5 |
-
•
|
6 |
-
|
|
|
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]] = []
|
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 |
-
#
|
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:], #
|
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
|
|
|
|
|
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 |
-
# ----------------
|
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 |
-
# ----------------
|
107 |
gen_prompt = s.project["versions"][0]["general_prompt"]
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
#
|
127 |
# --------------------------------------------------------------------------- #
|
128 |
def _find_intent(project, name_):
|
129 |
-
return next((i for i in project["versions"][0]["intents"]
|
|
|
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 |
-
|
140 |
-
if
|
141 |
-
if bad := _process_params(s, intent_cfg,
|
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"]
|
|
|
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 |
-
|
158 |
-
if not
|
159 |
return "Üzgünüm, anlayamadım."
|
160 |
-
if bad := _process_params(s, intent_cfg,
|
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"]
|
|
|
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,
|
173 |
try:
|
174 |
-
data = json.loads(
|
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"]
|
|
|
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"],
|
|
|
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:
|