Spaces:
Building
Building
Update chat_handler.py
Browse files- chat_handler.py +60 -47
chat_handler.py
CHANGED
@@ -1,72 +1,84 @@
|
|
1 |
"""
|
2 |
-
Flare – Chat Handler
|
3 |
-
|
4 |
-
|
5 |
-
/
|
|
|
6 |
"""
|
7 |
|
8 |
import re, json, uuid, 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 |
|
15 |
from prompt_builder import build_intent_prompt, build_parameter_prompt, log
|
16 |
|
17 |
-
#
|
18 |
# CONFIG
|
19 |
-
#
|
20 |
CFG = commentjson.load(open("service_config.jsonc", encoding="utf-8"))
|
21 |
PROJECTS = {p["name"]: p for p in CFG["projects"]}
|
22 |
APIS = {a["name"]: a for a in CFG["apis"]}
|
|
|
23 |
|
24 |
-
#
|
25 |
# SESSION
|
26 |
-
#
|
27 |
class Session:
|
28 |
def __init__(self, project_name: str):
|
29 |
self.id = str(uuid.uuid4())
|
30 |
self.project = PROJECTS[project_name]
|
31 |
-
self.history: List[Dict[str, str]] = []
|
32 |
self.variables: Dict[str, str] = {}
|
33 |
self.awaiting: Optional[Dict] = None
|
34 |
log(f"🆕 Session {self.id} for {project_name}")
|
35 |
|
36 |
SESSIONS: Dict[str, Session] = {}
|
37 |
|
38 |
-
#
|
39 |
-
#
|
40 |
-
#
|
41 |
-
async def spark_generate(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
async with httpx.AsyncClient(timeout=60) as c:
|
43 |
-
r = await c.post(
|
44 |
r.raise_for_status()
|
45 |
-
|
|
|
|
|
46 |
|
47 |
-
#
|
48 |
# FASTAPI ROUTER
|
49 |
-
#
|
50 |
router = APIRouter()
|
51 |
|
52 |
@router.get("/")
|
53 |
def health():
|
54 |
return {"status": "ok"}
|
55 |
|
56 |
-
# Schemas
|
57 |
class StartSessionRequest(BaseModel):
|
58 |
project_name: str
|
59 |
|
60 |
class ChatBody(BaseModel):
|
61 |
user_input: str
|
62 |
-
# session_id optional for legacy
|
63 |
-
session_id: Optional[str] = None
|
64 |
|
65 |
class ChatResponse(BaseModel):
|
66 |
session_id: str
|
67 |
answer: str
|
68 |
|
69 |
-
#
|
|
|
|
|
70 |
@router.post("/start_session", response_model=ChatResponse)
|
71 |
async def start_session(req: StartSessionRequest):
|
72 |
if req.project_name not in PROJECTS:
|
@@ -77,14 +89,11 @@ async def start_session(req: StartSessionRequest):
|
|
77 |
|
78 |
@router.post("/chat", response_model=ChatResponse)
|
79 |
async def chat(body: ChatBody,
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
raise HTTPException(404, "Invalid or missing session")
|
86 |
-
|
87 |
-
s = SESSIONS[sid]
|
88 |
user_msg = body.user_input.strip()
|
89 |
s.history.append({"role": "user", "content": user_msg})
|
90 |
|
@@ -94,10 +103,9 @@ async def chat(body: ChatBody,
|
|
94 |
s.history.append({"role": "assistant", "content": answer})
|
95 |
return ChatResponse(session_id=s.id, answer=answer)
|
96 |
|
97 |
-
# ---------------- intent
|
98 |
gen_prompt = s.project["versions"][0]["general_prompt"]
|
99 |
-
|
100 |
-
intent_out = await spark_generate(intent_prompt)
|
101 |
|
102 |
if not intent_out.startswith("#DETECTED_INTENT:"):
|
103 |
s.history.append({"role": "assistant", "content": intent_out})
|
@@ -114,28 +122,31 @@ async def chat(body: ChatBody,
|
|
114 |
s.history.append({"role": "assistant", "content": answer})
|
115 |
return ChatResponse(session_id=s.id, answer=answer)
|
116 |
|
117 |
-
#
|
118 |
-
# Helper
|
119 |
-
#
|
120 |
def _find_intent(project, name_):
|
121 |
return next((i for i in project["versions"][0]["intents"] if i["name"] == name_), None)
|
122 |
|
123 |
def _missing(s, intent_cfg):
|
124 |
-
return [p["name"] for p in intent_cfg["parameters"]
|
|
|
125 |
|
126 |
async def _handle_intent(s, intent_cfg, user_msg):
|
127 |
missing = _missing(s, intent_cfg)
|
128 |
if missing:
|
129 |
p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
|
130 |
-
|
131 |
-
if
|
132 |
-
if bad := _process_params(s, intent_cfg,
|
133 |
return bad
|
134 |
missing = _missing(s, intent_cfg)
|
|
|
135 |
if missing:
|
136 |
s.awaiting = {"intent": intent_cfg, "missing": missing}
|
137 |
cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
|
138 |
return f"{cap} nedir?"
|
|
|
139 |
s.awaiting = None
|
140 |
return await _call_api(s, intent_cfg)
|
141 |
|
@@ -143,22 +154,24 @@ async def _followup(s, user_msg):
|
|
143 |
intent_cfg = s.awaiting["intent"]
|
144 |
missing = s.awaiting["missing"]
|
145 |
p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
|
146 |
-
|
147 |
-
if not
|
148 |
return "Üzgünüm, anlayamadım."
|
149 |
-
if bad := _process_params(s, intent_cfg,
|
150 |
return bad
|
|
|
151 |
missing = _missing(s, intent_cfg)
|
152 |
if missing:
|
153 |
s.awaiting["missing"] = missing
|
154 |
cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
|
155 |
return f"{cap} nedir?"
|
|
|
156 |
s.awaiting = None
|
157 |
return await _call_api(s, intent_cfg)
|
158 |
|
159 |
-
def _process_params(s, intent_cfg,
|
160 |
try:
|
161 |
-
data = json.loads(
|
162 |
except json.JSONDecodeError:
|
163 |
return "Parametreleri çözemedim."
|
164 |
for pair in data.get("extracted", []):
|
@@ -188,7 +201,7 @@ async def _call_api(s, intent_cfg):
|
|
188 |
except Exception:
|
189 |
return intent_cfg["fallback_error_prompt"]
|
190 |
|
191 |
-
|
192 |
"{{api_response}}", json.dumps(api_json, ensure_ascii=False)
|
193 |
)
|
194 |
-
return await spark_generate(
|
|
|
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
|
10 |
from datetime import datetime
|
11 |
from typing import Dict, List, Optional
|
12 |
|
13 |
+
from fastapi import APIRouter, HTTPException, Header
|
14 |
from pydantic import BaseModel
|
15 |
|
16 |
from prompt_builder import build_intent_prompt, build_parameter_prompt, log
|
17 |
|
18 |
+
# --------------------------------------------------------------------------- #
|
19 |
# CONFIG
|
20 |
+
# --------------------------------------------------------------------------- #
|
21 |
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
|
28 |
+
# --------------------------------------------------------------------------- #
|
29 |
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}")
|
37 |
|
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
|
62 |
+
# --------------------------------------------------------------------------- #
|
63 |
router = APIRouter()
|
64 |
|
65 |
@router.get("/")
|
66 |
def health():
|
67 |
return {"status": "ok"}
|
68 |
|
|
|
69 |
class StartSessionRequest(BaseModel):
|
70 |
project_name: str
|
71 |
|
72 |
class ChatBody(BaseModel):
|
73 |
user_input: str
|
|
|
|
|
74 |
|
75 |
class ChatResponse(BaseModel):
|
76 |
session_id: str
|
77 |
answer: str
|
78 |
|
79 |
+
# --------------------------------------------------------------------------- #
|
80 |
+
# ENDPOINTS
|
81 |
+
# --------------------------------------------------------------------------- #
|
82 |
@router.post("/start_session", response_model=ChatResponse)
|
83 |
async def start_session(req: StartSessionRequest):
|
84 |
if req.project_name not in PROJECTS:
|
|
|
89 |
|
90 |
@router.post("/chat", response_model=ChatResponse)
|
91 |
async def chat(body: ChatBody,
|
92 |
+
x_session_id: str = Header(...)):
|
93 |
+
if x_session_id not in SESSIONS:
|
94 |
+
raise HTTPException(404, "Invalid session")
|
95 |
+
|
96 |
+
s = SESSIONS[x_session_id]
|
|
|
|
|
|
|
97 |
user_msg = body.user_input.strip()
|
98 |
s.history.append({"role": "user", "content": user_msg})
|
99 |
|
|
|
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})
|
|
|
122 |
s.history.append({"role": "assistant", "content": answer})
|
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"]
|
133 |
+
if p["variable_name"] not in s.variables]
|
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
|
151 |
return await _call_api(s, intent_cfg)
|
152 |
|
|
|
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", []):
|
|
|
201 |
except Exception:
|
202 |
return intent_cfg["fallback_error_prompt"]
|
203 |
|
204 |
+
summary_prompt = api["response_prompt"].replace(
|
205 |
"{{api_response}}", json.dumps(api_json, ensure_ascii=False)
|
206 |
)
|
207 |
+
return await spark_generate(s, summary_prompt, "")
|