ciyidogan commited on
Commit
c4a5a79
·
verified ·
1 Parent(s): f45e6d9

Update chat_handler.py

Browse files
Files changed (1) hide show
  1. chat_handler.py +80 -138
chat_handler.py CHANGED
@@ -1,90 +1,69 @@
1
  """
2
- Flare – Chat Handler (v2)
3
- ==========================
4
- /start_session • /chat
5
- Intent tespiti, parametre çıkarımı, regex doğrulama, session değişkeni,
6
- backend API çağrısı ve cevap özetleme.
7
  """
8
 
9
- import re
10
- import json
11
- import uuid
12
- import httpx
13
- import commentjson
14
  from datetime import datetime
15
  from typing import Dict, List, Optional
16
 
17
- from fastapi import FastAPI, HTTPException
18
  from pydantic import BaseModel
19
 
20
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
21
 
22
  # ----------------------------------------------------------------------------
23
- # CONFIG LOAD (service_config.jsonc)
24
  # ----------------------------------------------------------------------------
25
- with open("service_config.jsonc", "r", encoding="utf-8") as f:
26
- CFG = commentjson.load(f)
27
-
28
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
29
  APIS = {a["name"]: a for a in CFG["apis"]}
30
 
31
-
32
  # ----------------------------------------------------------------------------
33
- # SESSION OBJECT
34
  # ----------------------------------------------------------------------------
35
  class Session:
36
  def __init__(self, project_name: str):
37
  self.id = str(uuid.uuid4())
38
  self.project = PROJECTS[project_name]
39
  self.history: List[Dict[str, str]] = []
40
- self.variables: Dict[str, str] = {} # variable_name -> value
41
- self.awaiting: Optional[Dict] = None # {"intent":..., "missing":[...]}
42
  log(f"🆕 Session {self.id} for {project_name}")
43
 
44
-
45
  SESSIONS: Dict[str, Session] = {}
46
 
47
-
48
  # ----------------------------------------------------------------------------
49
- # SPARK LLM WRAPPER (basit HTTP JSON)
50
  # ----------------------------------------------------------------------------
51
  async def spark_generate(prompt: str) -> str:
52
- url = CFG["config"]["spark_endpoint"]
53
  async with httpx.AsyncClient(timeout=60) as c:
54
- r = await c.post(url, json={"prompt": prompt})
55
  r.raise_for_status()
56
  return r.json()["text"]
57
 
58
-
59
  # ----------------------------------------------------------------------------
60
- # FASTAPI APP
61
  # ----------------------------------------------------------------------------
62
- app = FastAPI()
63
 
64
- @app.get("/")
65
  def health():
66
  return {"status": "ok"}
67
 
68
-
69
- # ----------------------------------------------------------------------------
70
- # SCHEMAS
71
- # ----------------------------------------------------------------------------
72
  class StartSessionRequest(BaseModel):
73
  project_name: str
74
-
75
  class ChatRequest(BaseModel):
76
  session_id: str
77
  user_input: str
78
-
79
  class ChatResponse(BaseModel):
80
  session_id: str
81
  answer: str
82
 
83
-
84
- # ----------------------------------------------------------------------------
85
- # ENDPOINTS
86
- # ----------------------------------------------------------------------------
87
- @app.post("/start_session", response_model=ChatResponse)
88
  async def start_session(req: StartSessionRequest):
89
  if req.project_name not in PROJECTS:
90
  raise HTTPException(404, "Unknown project")
@@ -92,35 +71,33 @@ async def start_session(req: StartSessionRequest):
92
  SESSIONS[s.id] = s
93
  return ChatResponse(session_id=s.id, answer="Nasıl yardımcı olabilirim?")
94
 
95
- @app.post("/chat", response_model=ChatResponse)
96
  async def chat(req: ChatRequest):
97
  if req.session_id not in SESSIONS:
98
  raise HTTPException(404, "Invalid session")
99
-
100
  s = SESSIONS[req.session_id]
101
  user_msg = req.user_input.strip()
102
  s.history.append({"role": "user", "content": user_msg})
103
 
104
- # ---------------- Parametre follow-up modunda mı? ----------------
105
  if s.awaiting:
106
- answer = await _followup_flow(s, user_msg)
107
  s.history.append({"role": "assistant", "content": answer})
108
  return ChatResponse(session_id=s.id, answer=answer)
109
 
110
- # ---------------- Intent detection ----------------
111
  gen_prompt = s.project["versions"][0]["general_prompt"]
112
- prompt = build_intent_prompt(gen_prompt, s.history, user_msg)
113
- llm_out = await spark_generate(prompt)
114
 
115
- if not llm_out.startswith("#DETECTED_INTENT:"):
116
- # Small-talk cevabı
117
- s.history.append({"role": "assistant", "content": llm_out})
118
- return ChatResponse(session_id=s.id, answer=llm_out)
119
 
120
- intent_name = llm_out.split(":", 1)[1].strip()
121
- intent_cfg = _get_intent_cfg(s.project, intent_name)
122
  if not intent_cfg:
123
- err = "Üzgünüm, bu konuda yardımcı olamıyorum."
124
  s.history.append({"role": "assistant", "content": err})
125
  return ChatResponse(session_id=s.id, answer=err)
126
 
@@ -128,117 +105,82 @@ async def chat(req: ChatRequest):
128
  s.history.append({"role": "assistant", "content": answer})
129
  return ChatResponse(session_id=s.id, answer=answer)
130
 
131
-
132
  # ----------------------------------------------------------------------------
133
- # CORE HELPERS
134
  # ----------------------------------------------------------------------------
135
- def _get_intent_cfg(project: Dict, name_: str) -> Optional[Dict]:
136
- for it in project["versions"][0]["intents"]:
137
- if it["name"] == name_:
138
- return it
139
- return None
140
-
141
- def _current_missing(session: Session, intent_cfg: Dict) -> List[str]:
142
- return [
143
- p["name"]
144
- for p in intent_cfg["parameters"]
145
- if p["variable_name"] not in session.variables
146
- ]
147
 
148
- async def _handle_intent(session: Session,
149
- intent_cfg: Dict,
150
- user_msg: str) -> str:
151
- missing = _current_missing(session, intent_cfg)
152
 
153
- # --- Parametre extraction denemesi (ilk mesajda) ---
 
154
  if missing:
155
- p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, session.history)
156
- llm_out = await spark_generate(p_prompt)
157
-
158
- if llm_out.startswith("#PARAMETERS:"):
159
- ok = _process_param_output(session, intent_cfg, llm_out)
160
- if not ok:
161
- return ok # invalid prompt dönebilir
162
- missing = _current_missing(session, intent_cfg)
163
-
164
- # --- Hâlâ eksik mi? follow-up sor ---
165
  if missing:
166
- session.awaiting = {"intent": intent_cfg, "missing": missing}
167
- first_caption = next(p for p in intent_cfg["parameters"]
168
- if p["name"] == missing[0])["caption"]
169
- return f"{first_caption} nedir?"
170
-
171
- # --- Tüm parametreler hazır → API çağır ---
172
- return await _call_api(session, intent_cfg)
173
-
174
- async def _followup_flow(session: Session,
175
- user_msg: str) -> str:
176
- intent_cfg = session.awaiting["intent"]
177
- missing = session.awaiting["missing"]
178
-
179
- p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, session.history)
180
- llm_out = await spark_generate(p_prompt)
181
-
182
- if not llm_out.startswith("#PARAMETERS:"):
183
- return "Üzgünüm, bilgileri anlayamadım."
184
-
185
- ok = _process_param_output(session, intent_cfg, llm_out)
186
- if not ok:
187
- return ok
188
-
189
- missing = _current_missing(session, intent_cfg)
190
  if missing:
191
- session.awaiting["missing"] = missing
192
- first_caption = next(p for p in intent_cfg["parameters"]
193
- if p["name"] == missing[0])["caption"]
194
- return f"{first_caption} nedir?"
 
195
 
196
- session.awaiting = None
197
- return await _call_api(session, intent_cfg)
198
-
199
- def _process_param_output(session: Session,
200
- intent_cfg: Dict,
201
- llm_out: str) -> Optional[str]:
202
  try:
203
- data = json.loads(llm_out[len("#PARAMETERS:"):])
204
  except json.JSONDecodeError:
205
- return "Üzgünüm, parametreleri çözemedim."
206
-
207
  for pair in data.get("extracted", []):
208
  p_cfg = next(p for p in intent_cfg["parameters"] if p["name"] == pair["name"])
209
- if not _validate(p_cfg, pair["value"]):
210
  return p_cfg.get("invalid_prompt", "Geçersiz değer.")
211
- session.variables[p_cfg["variable_name"]] = pair["value"]
212
- return None # success
213
 
214
- def _validate(p_cfg: Dict, value: str) -> bool:
215
- pattern = p_cfg.get("validation_regex")
216
- return re.match(pattern, value) is not None if pattern else True
217
 
218
- async def _call_api(session: Session, intent_cfg: Dict) -> str:
219
  api = APIS[intent_cfg["action"]]
220
-
221
- # Simple token
222
- token = "testtoken"
223
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
224
 
225
- body = json.loads(json.dumps(api["body_template"])) # deep copy
226
  for k, v in body.items():
227
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
228
- body[k] = session.variables.get(v[2:-2], "")
229
-
230
- log(f"➡️ {api['name']} {body}")
231
  try:
232
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
233
  r = await c.request(api["method"], api["url"], headers=headers, json=body)
234
  r.raise_for_status()
235
  api_json = r.json()
236
- except Exception as ex:
237
- log(f"❌ API error: {ex}")
238
  return intent_cfg["fallback_error_prompt"]
239
 
240
- # LLM’ye özetlet
241
- summary_prompt = api["response_prompt"].replace(
242
  "{{api_response}}", json.dumps(api_json, ensure_ascii=False)
243
  )
244
- return await spark_generate(summary_prompt)
 
1
  """
2
+ Flare – Chat Handler (router edition)
3
+ =====================================
4
+ app.py from chat_handler import router
 
 
5
  """
6
 
7
+ import re, json, uuid, httpx, commentjson
 
 
 
 
8
  from datetime import datetime
9
  from typing import Dict, List, Optional
10
 
11
+ from fastapi import APIRouter, HTTPException
12
  from pydantic import BaseModel
13
 
14
  from prompt_builder import build_intent_prompt, build_parameter_prompt, log
15
 
16
  # ----------------------------------------------------------------------------
17
+ # CONFIG
18
  # ----------------------------------------------------------------------------
19
+ CFG = commentjson.load(open("service_config.jsonc", encoding="utf-8"))
 
 
20
  PROJECTS = {p["name"]: p for p in CFG["projects"]}
21
  APIS = {a["name"]: a for a in CFG["apis"]}
22
 
 
23
  # ----------------------------------------------------------------------------
24
+ # SESSION
25
  # ----------------------------------------------------------------------------
26
  class Session:
27
  def __init__(self, project_name: str):
28
  self.id = str(uuid.uuid4())
29
  self.project = PROJECTS[project_name]
30
  self.history: List[Dict[str, str]] = []
31
+ self.variables: Dict[str, str] = {}
32
+ self.awaiting: Optional[Dict] = None
33
  log(f"🆕 Session {self.id} for {project_name}")
34
 
 
35
  SESSIONS: Dict[str, Session] = {}
36
 
 
37
  # ----------------------------------------------------------------------------
38
+ # SPARK
39
  # ----------------------------------------------------------------------------
40
  async def spark_generate(prompt: str) -> str:
 
41
  async with httpx.AsyncClient(timeout=60) as c:
42
+ r = await c.post(CFG["config"]["spark_endpoint"], json={"prompt": prompt})
43
  r.raise_for_status()
44
  return r.json()["text"]
45
 
 
46
  # ----------------------------------------------------------------------------
47
+ # FASTAPI ROUTER
48
  # ----------------------------------------------------------------------------
49
+ router = APIRouter() # <<–– exported to app.py
50
 
51
+ @router.get("/")
52
  def health():
53
  return {"status": "ok"}
54
 
55
+ # Schemas
 
 
 
56
  class StartSessionRequest(BaseModel):
57
  project_name: str
 
58
  class ChatRequest(BaseModel):
59
  session_id: str
60
  user_input: str
 
61
  class ChatResponse(BaseModel):
62
  session_id: str
63
  answer: str
64
 
65
+ # Endpoints
66
+ @router.post("/start_session", response_model=ChatResponse)
 
 
 
67
  async def start_session(req: StartSessionRequest):
68
  if req.project_name not in PROJECTS:
69
  raise HTTPException(404, "Unknown project")
 
71
  SESSIONS[s.id] = s
72
  return ChatResponse(session_id=s.id, answer="Nasıl yardımcı olabilirim?")
73
 
74
+ @router.post("/chat", response_model=ChatResponse)
75
  async def chat(req: ChatRequest):
76
  if req.session_id not in SESSIONS:
77
  raise HTTPException(404, "Invalid session")
 
78
  s = SESSIONS[req.session_id]
79
  user_msg = req.user_input.strip()
80
  s.history.append({"role": "user", "content": user_msg})
81
 
82
+ # --- follow-up? ---
83
  if s.awaiting:
84
+ answer = await _followup(s, user_msg)
85
  s.history.append({"role": "assistant", "content": answer})
86
  return ChatResponse(session_id=s.id, answer=answer)
87
 
88
+ # --- intent detect ---
89
  gen_prompt = s.project["versions"][0]["general_prompt"]
90
+ intent_prompt = build_intent_prompt(gen_prompt, s.history, user_msg)
91
+ intent_out = await spark_generate(intent_prompt)
92
 
93
+ if not intent_out.startswith("#DETECTED_INTENT:"):
94
+ s.history.append({"role": "assistant", "content": intent_out})
95
+ return ChatResponse(session_id=s.id, answer=intent_out)
 
96
 
97
+ intent_name = intent_out.split(":", 1)[1].strip()
98
+ intent_cfg = _find_intent(s.project, intent_name)
99
  if not intent_cfg:
100
+ err = "Üzgünüm, anlayamadım."
101
  s.history.append({"role": "assistant", "content": err})
102
  return ChatResponse(session_id=s.id, answer=err)
103
 
 
105
  s.history.append({"role": "assistant", "content": answer})
106
  return ChatResponse(session_id=s.id, answer=answer)
107
 
 
108
  # ----------------------------------------------------------------------------
109
+ # Helper funcs (değişmedi, sadece router’a ihtiyaç yok)
110
  # ----------------------------------------------------------------------------
111
+ def _find_intent(project, name_):
112
+ return next((i for i in project["versions"][0]["intents"] if i["name"] == name_), None)
 
 
 
 
 
 
 
 
 
 
113
 
114
+ def _missing(session, intent_cfg):
115
+ return [p["name"] for p in intent_cfg["parameters"] if p["variable_name"] not in session.variables]
 
 
116
 
117
+ async def _handle_intent(s, intent_cfg, user_msg):
118
+ missing = _missing(s, intent_cfg)
119
  if missing:
120
+ p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
121
+ p_out = await spark_generate(p_prompt)
122
+ if p_out.startswith("#PARAMETERS:"):
123
+ if bad := _process_params(s, intent_cfg, p_out):
124
+ return bad
125
+ missing = _missing(s, intent_cfg)
 
 
 
 
126
  if missing:
127
+ s.awaiting = {"intent": intent_cfg, "missing": missing}
128
+ cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
129
+ return f"{cap} nedir?"
130
+ s.awaiting = None
131
+ return await _call_api(s, intent_cfg)
132
+
133
+ async def _followup(s, user_msg):
134
+ intent_cfg = s.awaiting["intent"]
135
+ missing = s.awaiting["missing"]
136
+ p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, s.history)
137
+ p_out = await spark_generate(p_prompt)
138
+ if not p_out.startswith("#PARAMETERS:"):
139
+ return "Üzgünüm, anlayamadım."
140
+ if bad := _process_params(s, intent_cfg, p_out):
141
+ return bad
142
+ missing = _missing(s, intent_cfg)
 
 
 
 
 
 
 
 
143
  if missing:
144
+ s.awaiting["missing"] = missing
145
+ cap = next(p for p in intent_cfg["parameters"] if p["name"] == missing[0])["caption"]
146
+ return f"{cap} nedir?"
147
+ s.awaiting = None
148
+ return await _call_api(s, intent_cfg)
149
 
150
+ def _process_params(s, intent_cfg, p_out):
 
 
 
 
 
151
  try:
152
+ data = json.loads(p_out[len("#PARAMETERS:"):])
153
  except json.JSONDecodeError:
154
+ return "Üzgünüm, parametreleri çözemiyorum."
 
155
  for pair in data.get("extracted", []):
156
  p_cfg = next(p for p in intent_cfg["parameters"] if p["name"] == pair["name"])
157
+ if not _valid(p_cfg, pair["value"]):
158
  return p_cfg.get("invalid_prompt", "Geçersiz değer.")
159
+ s.variables[p_cfg["variable_name"]] = pair["value"]
160
+ return None
161
 
162
+ def _valid(p_cfg, val):
163
+ rx = p_cfg.get("validation_regex")
164
+ return re.match(rx, val) is not None if rx else True
165
 
166
+ async def _call_api(s, intent_cfg):
167
  api = APIS[intent_cfg["action"]]
168
+ token = "testtoken"
 
 
169
  headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()}
170
 
171
+ body = json.loads(json.dumps(api["body_template"]))
172
  for k, v in body.items():
173
  if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"):
174
+ body[k] = s.variables.get(v[2:-2], "")
 
 
175
  try:
176
  async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c:
177
  r = await c.request(api["method"], api["url"], headers=headers, json=body)
178
  r.raise_for_status()
179
  api_json = r.json()
180
+ except Exception:
 
181
  return intent_cfg["fallback_error_prompt"]
182
 
183
+ summary = api["response_prompt"].replace(
 
184
  "{{api_response}}", json.dumps(api_json, ensure_ascii=False)
185
  )
186
+ return await spark_generate(summary)