Spaces:
Running
Running
""" | |
Flare – Chat Handler (v2) | |
========================== | |
• /start_session • /chat | |
Intent tespiti, parametre çıkarımı, regex doğrulama, session değişkeni, | |
backend API çağrısı ve cevap özetleme. | |
""" | |
import re | |
import json | |
import uuid | |
import httpx | |
import commentjson | |
from datetime import datetime | |
from typing import Dict, List, Optional | |
from fastapi import FastAPI, HTTPException | |
from pydantic import BaseModel | |
from prompt_builder import build_intent_prompt, build_parameter_prompt, log | |
# ---------------------------------------------------------------------------- | |
# CONFIG LOAD (service_config.jsonc) | |
# ---------------------------------------------------------------------------- | |
with open("service_config.jsonc", "r", encoding="utf-8") as f: | |
CFG = commentjson.load(f) | |
PROJECTS = {p["name"]: p for p in CFG["projects"]} | |
APIS = {a["name"]: a for a in CFG["apis"]} | |
# ---------------------------------------------------------------------------- | |
# SESSION OBJECT | |
# ---------------------------------------------------------------------------- | |
class Session: | |
def __init__(self, project_name: str): | |
self.id = str(uuid.uuid4()) | |
self.project = PROJECTS[project_name] | |
self.history: List[Dict[str, str]] = [] | |
self.variables: Dict[str, str] = {} # variable_name -> value | |
self.awaiting: Optional[Dict] = None # {"intent":..., "missing":[...]} | |
log(f"🆕 Session {self.id} for {project_name}") | |
SESSIONS: Dict[str, Session] = {} | |
# ---------------------------------------------------------------------------- | |
# SPARK LLM WRAPPER (basit HTTP JSON) | |
# ---------------------------------------------------------------------------- | |
async def spark_generate(prompt: str) -> str: | |
url = CFG["config"]["spark_endpoint"] | |
async with httpx.AsyncClient(timeout=60) as c: | |
r = await c.post(url, json={"prompt": prompt}) | |
r.raise_for_status() | |
return r.json()["text"] | |
# ---------------------------------------------------------------------------- | |
# FASTAPI APP | |
# ---------------------------------------------------------------------------- | |
app = FastAPI() | |
def health(): | |
return {"status": "ok"} | |
# ---------------------------------------------------------------------------- | |
# SCHEMAS | |
# ---------------------------------------------------------------------------- | |
class StartSessionRequest(BaseModel): | |
project_name: str | |
class ChatRequest(BaseModel): | |
session_id: str | |
user_input: str | |
class ChatResponse(BaseModel): | |
session_id: str | |
answer: str | |
# ---------------------------------------------------------------------------- | |
# ENDPOINTS | |
# ---------------------------------------------------------------------------- | |
async def start_session(req: StartSessionRequest): | |
if req.project_name not in PROJECTS: | |
raise HTTPException(404, "Unknown project") | |
s = Session(req.project_name) | |
SESSIONS[s.id] = s | |
return ChatResponse(session_id=s.id, answer="Nasıl yardımcı olabilirim?") | |
async def chat(req: ChatRequest): | |
if req.session_id not in SESSIONS: | |
raise HTTPException(404, "Invalid session") | |
s = SESSIONS[req.session_id] | |
user_msg = req.user_input.strip() | |
s.history.append({"role": "user", "content": user_msg}) | |
# ---------------- Parametre follow-up modunda mı? ---------------- | |
if s.awaiting: | |
answer = await _followup_flow(s, user_msg) | |
s.history.append({"role": "assistant", "content": answer}) | |
return ChatResponse(session_id=s.id, answer=answer) | |
# ---------------- Intent detection ---------------- | |
gen_prompt = s.project["versions"][0]["general_prompt"] | |
prompt = build_intent_prompt(gen_prompt, s.history, user_msg) | |
llm_out = await spark_generate(prompt) | |
if not llm_out.startswith("#DETECTED_INTENT:"): | |
# Small-talk cevabı | |
s.history.append({"role": "assistant", "content": llm_out}) | |
return ChatResponse(session_id=s.id, answer=llm_out) | |
intent_name = llm_out.split(":", 1)[1].strip() | |
intent_cfg = _get_intent_cfg(s.project, intent_name) | |
if not intent_cfg: | |
err = "Üzgünüm, bu konuda yardımcı olamıyorum." | |
s.history.append({"role": "assistant", "content": err}) | |
return ChatResponse(session_id=s.id, answer=err) | |
answer = await _handle_intent(s, intent_cfg, user_msg) | |
s.history.append({"role": "assistant", "content": answer}) | |
return ChatResponse(session_id=s.id, answer=answer) | |
# ---------------------------------------------------------------------------- | |
# CORE HELPERS | |
# ---------------------------------------------------------------------------- | |
def _get_intent_cfg(project: Dict, name_: str) -> Optional[Dict]: | |
for it in project["versions"][0]["intents"]: | |
if it["name"] == name_: | |
return it | |
return None | |
def _current_missing(session: Session, intent_cfg: Dict) -> List[str]: | |
return [ | |
p["name"] | |
for p in intent_cfg["parameters"] | |
if p["variable_name"] not in session.variables | |
] | |
async def _handle_intent(session: Session, | |
intent_cfg: Dict, | |
user_msg: str) -> str: | |
missing = _current_missing(session, intent_cfg) | |
# --- Parametre extraction denemesi (ilk mesajda) --- | |
if missing: | |
p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, session.history) | |
llm_out = await spark_generate(p_prompt) | |
if llm_out.startswith("#PARAMETERS:"): | |
ok = _process_param_output(session, intent_cfg, llm_out) | |
if not ok: | |
return ok # invalid prompt dönebilir | |
missing = _current_missing(session, intent_cfg) | |
# --- Hâlâ eksik mi? follow-up sor --- | |
if missing: | |
session.awaiting = {"intent": intent_cfg, "missing": missing} | |
first_caption = next(p for p in intent_cfg["parameters"] | |
if p["name"] == missing[0])["caption"] | |
return f"{first_caption} nedir?" | |
# --- Tüm parametreler hazır → API çağır --- | |
return await _call_api(session, intent_cfg) | |
async def _followup_flow(session: Session, | |
user_msg: str) -> str: | |
intent_cfg = session.awaiting["intent"] | |
missing = session.awaiting["missing"] | |
p_prompt = build_parameter_prompt(intent_cfg, missing, user_msg, session.history) | |
llm_out = await spark_generate(p_prompt) | |
if not llm_out.startswith("#PARAMETERS:"): | |
return "Üzgünüm, bilgileri anlayamadım." | |
ok = _process_param_output(session, intent_cfg, llm_out) | |
if not ok: | |
return ok | |
missing = _current_missing(session, intent_cfg) | |
if missing: | |
session.awaiting["missing"] = missing | |
first_caption = next(p for p in intent_cfg["parameters"] | |
if p["name"] == missing[0])["caption"] | |
return f"{first_caption} nedir?" | |
session.awaiting = None | |
return await _call_api(session, intent_cfg) | |
def _process_param_output(session: Session, | |
intent_cfg: Dict, | |
llm_out: str) -> Optional[str]: | |
try: | |
data = json.loads(llm_out[len("#PARAMETERS:"):]) | |
except json.JSONDecodeError: | |
return "Üzgünüm, parametreleri çözemedim." | |
for pair in data.get("extracted", []): | |
p_cfg = next(p for p in intent_cfg["parameters"] if p["name"] == pair["name"]) | |
if not _validate(p_cfg, pair["value"]): | |
return p_cfg.get("invalid_prompt", "Geçersiz değer.") | |
session.variables[p_cfg["variable_name"]] = pair["value"] | |
return None # success | |
def _validate(p_cfg: Dict, value: str) -> bool: | |
pattern = p_cfg.get("validation_regex") | |
return re.match(pattern, value) is not None if pattern else True | |
async def _call_api(session: Session, intent_cfg: Dict) -> str: | |
api = APIS[intent_cfg["action"]] | |
# Simple token | |
token = "testtoken" | |
headers = {k: v.replace("{{token}}", token) for k, v in api["headers"].items()} | |
body = json.loads(json.dumps(api["body_template"])) # deep copy | |
for k, v in body.items(): | |
if isinstance(v, str) and v.startswith("{{") and v.endswith("}}"): | |
body[k] = session.variables.get(v[2:-2], "") | |
log(f"➡️ {api['name']} {body}") | |
try: | |
async with httpx.AsyncClient(timeout=api["timeout_seconds"]) as c: | |
r = await c.request(api["method"], api["url"], headers=headers, json=body) | |
r.raise_for_status() | |
api_json = r.json() | |
except Exception as ex: | |
log(f"❌ API error: {ex}") | |
return intent_cfg["fallback_error_prompt"] | |
# LLM’ye özetlet | |
summary_prompt = api["response_prompt"].replace( | |
"{{api_response}}", json.dumps(api_json, ensure_ascii=False) | |
) | |
return await spark_generate(summary_prompt) | |