fix(server): идемпотентность upload, WAL, буфер ELM, таймаут LLM
- api/db.py: WAL mode, busy_timeout, request_id UNIQUE, close(), контекстный менеджер - api/routes.py: проверка request_id при upload, /ping-llm кэш 60с, /chat через roles - api/config.py: lru_cache на load() - obd/protocol.py: reset_input_buffer перед _write(), условный READY в send() - brain/client.py: модель 120b, таймаут из параметра, LLMError класс, обработка 429/5xx
This commit is contained in:
@@ -18,10 +18,24 @@ from pathlib import Path
|
||||
class Database:
|
||||
def __init__(self, path: str | Path = "elmer.db"):
|
||||
self.path = Path(path)
|
||||
self.conn = sqlite3.connect(str(self.path))
|
||||
self.conn = sqlite3.connect(str(self.path), timeout=30, check_same_thread=False)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self.conn.execute("PRAGMA journal_mode=WAL")
|
||||
self.conn.execute("PRAGMA busy_timeout=30000")
|
||||
self._init_schema()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
def _init_schema(self):
|
||||
self.conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS cars (
|
||||
@@ -113,22 +127,40 @@ class Database:
|
||||
llm_success INTEGER DEFAULT 0,
|
||||
|
||||
-- Сырые данные (JSON)
|
||||
raw_responses TEXT
|
||||
raw_responses TEXT,
|
||||
|
||||
-- Идемпотентность
|
||||
request_id TEXT UNIQUE,
|
||||
response_json TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_vin ON sessions(vin);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_mac ON sessions(elm_mac);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_aid ON sessions(android_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_vin ON sessions(vin);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_mac ON sessions(elm_mac);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_aid ON sessions(android_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_request_id ON sessions(request_id);
|
||||
""")
|
||||
self.conn.commit()
|
||||
|
||||
# ── sessions ──────────────────────────────────────────
|
||||
|
||||
def get_cached_response(self, request_id: str) -> dict | None:
|
||||
"""Возвращает сохранённый ответ сессии по request_id, или None."""
|
||||
row = self.conn.execute(
|
||||
"SELECT response_json FROM sessions WHERE request_id = ?", (request_id,)
|
||||
).fetchone()
|
||||
if row and row["response_json"]:
|
||||
return json.loads(row["response_json"])
|
||||
return None
|
||||
|
||||
def save_session(self, client_info: dict, responses: list[dict],
|
||||
diagnosis: str = "", llm_model: str = "",
|
||||
llm_duration_ms: int = 0, llm_success: bool = False):
|
||||
"""Сохраняет сводную запись о сессии."""
|
||||
llm_duration_ms: int = 0, llm_success: bool = False,
|
||||
request_id: str = "", response_json: dict | None = None):
|
||||
"""Сохраняет сводную запись о сессии.
|
||||
|
||||
Если request_id передан и уже существует — silently return (идемпотентность).
|
||||
"""
|
||||
ci = client_info
|
||||
|
||||
# Подсчёт DTC/PID из ответов
|
||||
@@ -151,8 +183,10 @@ class Database:
|
||||
vin = None
|
||||
break
|
||||
|
||||
resp_json_str = json.dumps(response_json, ensure_ascii=False) if response_json else None
|
||||
|
||||
self.conn.execute("""
|
||||
INSERT INTO sessions (
|
||||
INSERT OR IGNORE INTO sessions (
|
||||
client_ip, real_ip, user_agent, content_length,
|
||||
phone_model, phone_maker, android_version, android_sdk,
|
||||
app_version, android_id,
|
||||
@@ -163,9 +197,9 @@ class Database:
|
||||
transport, mock_mode,
|
||||
diagnosis_text, diagnosis_len, llm_model,
|
||||
llm_duration_ms, llm_success,
|
||||
raw_responses
|
||||
raw_responses, request_id, response_json
|
||||
) VALUES (?,?,?,?, ?,?,?,?, ?,?, ?,?,?, ?,?,?, ?,?,?, ?,?,?, ?,?,
|
||||
?,?,?, ?,?, ?)
|
||||
?,?,?, ?,?, ?,?,?)
|
||||
""", (
|
||||
ci.get("client_ip"), ci.get("real_ip"), ci.get("user_agent"),
|
||||
ci.get("content_length"),
|
||||
@@ -179,6 +213,8 @@ class Database:
|
||||
diagnosis, len(diagnosis), llm_model,
|
||||
llm_duration_ms, 1 if llm_success else 0,
|
||||
json.dumps(responses, ensure_ascii=False) if responses else None,
|
||||
request_id if request_id else None,
|
||||
resp_json_str,
|
||||
))
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user