added web service
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """Ты помощник инженера по редактированию ГОСТ-таблиц (перечень, спецификация, ведомость).
|
||||
Пользователь даёт команды на русском. Ты отвечаешь ТОЛЬКО валидным JSON без markdown:
|
||||
{
|
||||
"reply": "краткий ответ пользователю",
|
||||
"edits": [
|
||||
{"op": "update_row", "row_id": 123, "fields": {"note": "..."}},
|
||||
{"op": "update_row", "row_index": 5, "fields": {"designation": "..."}},
|
||||
{"op": "add_row", "row_index": 10, "fields": {...}},
|
||||
{"op": "delete_row", "row_id": 123}
|
||||
]
|
||||
}
|
||||
Правила:
|
||||
- Меняй только то, о чём просят.
|
||||
- Используй row_id из снимка таблицы, если есть.
|
||||
- Не выдумывай поля вне списка колонок.
|
||||
- Если правок нет — edits: [].
|
||||
"""
|
||||
|
||||
|
||||
def _extract_json(text: str) -> dict[str, Any]:
|
||||
text = text.strip()
|
||||
if text.startswith("```"):
|
||||
text = re.sub(r"^```(?:json)?\s*", "", text)
|
||||
text = re.sub(r"\s*```$", "", text)
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
m = re.search(r"\{.*\}", text, re.S)
|
||||
if m:
|
||||
return json.loads(m.group(0))
|
||||
return {"reply": text, "edits": []}
|
||||
|
||||
|
||||
def compress_rows(rows: list[dict[str, Any]], max_rows: int) -> list[dict[str, Any]]:
|
||||
slim = []
|
||||
for r in rows[:max_rows]:
|
||||
item = {"row_id": r.get("id"), "row_index": r.get("row_index")}
|
||||
for k, v in r.items():
|
||||
if k in ("id", "project_id", "is_auto_generated", "stretch"):
|
||||
continue
|
||||
if v not in ("", None, False, 0) or k in ("is_header", "is_empty"):
|
||||
item[k] = v
|
||||
slim.append(item)
|
||||
return slim
|
||||
|
||||
|
||||
async def chat_edit_table(
|
||||
message: str,
|
||||
table_type: str,
|
||||
rows: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
settings = get_settings()
|
||||
if not settings.openrouter_api_key:
|
||||
return {
|
||||
"reply": "OpenRouter API key не настроен (OPENROUTER_API_KEY).",
|
||||
"edits": [],
|
||||
}
|
||||
|
||||
snapshot = compress_rows(rows, settings.llm_max_context_rows)
|
||||
user_content = (
|
||||
f"Тип таблицы: {table_type}\n"
|
||||
f"Снимок строк (до {settings.llm_max_context_rows}):\n"
|
||||
f"{json.dumps(snapshot, ensure_ascii=False)}\n\n"
|
||||
f"Команда пользователя: {message}"
|
||||
)
|
||||
|
||||
payload = {
|
||||
"model": settings.openrouter_model,
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{"role": "user", "content": user_content},
|
||||
],
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
headers = {
|
||||
"Authorization": f"Bearer {settings.openrouter_api_key}",
|
||||
"Content-Type": "application/json",
|
||||
"HTTP-Referer": "https://gostgenerator.local",
|
||||
"X-Title": "GostGenerator Web",
|
||||
}
|
||||
url = settings.openrouter_base_url.rstrip("/") + "/chat/completions"
|
||||
|
||||
proxy = (settings.openrouter_proxy or "").strip() or None
|
||||
if proxy and "://" not in proxy:
|
||||
proxy = f"socks5://{proxy}"
|
||||
|
||||
client_kwargs: dict[str, Any] = {"timeout": 90.0}
|
||||
if proxy:
|
||||
client_kwargs["proxy"] = proxy
|
||||
|
||||
async with httpx.AsyncClient(**client_kwargs) as client:
|
||||
resp = await client.post(url, headers=headers, json=payload)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
parsed = _extract_json(content)
|
||||
edits = parsed.get("edits") or []
|
||||
# normalize
|
||||
norm_edits = []
|
||||
for e in edits:
|
||||
if not isinstance(e, dict):
|
||||
continue
|
||||
norm_edits.append(
|
||||
{
|
||||
"op": e.get("op", "update_row"),
|
||||
"row_id": e.get("row_id"),
|
||||
"row_index": e.get("row_index"),
|
||||
"fields": e.get("fields") or {},
|
||||
}
|
||||
)
|
||||
return {"reply": parsed.get("reply") or "", "edits": norm_edits}
|
||||
Reference in New Issue
Block a user