diff --git a/site/app.py b/site/app.py index acc2542..9db4b42 100644 --- a/site/app.py +++ b/site/app.py @@ -13,7 +13,7 @@ from api import api_bp from routes.main import index from routes.parse import parse from routes.misc import health, logs -from routes.llm import process as llm_process +from routes.llm import process as llm_process, chat as llm_chat load_dotenv() @@ -47,6 +47,7 @@ class ContractsApp: # LLM: страница + SSE-извлечение self.app.add_url_rule("/llm", "llm_page", lambda: __import__('flask').render_template('llm.html')) self.app.add_url_rule("/llm/process/", "llm_process", llm_process) + self.app.add_url_rule("/llm/chat/", "llm_chat", llm_chat, methods=["POST"]) # Служебные self.app.add_url_rule("/health", "health", health) diff --git a/site/routes/llm.py b/site/routes/llm.py index ed44645..8d51be5 100644 --- a/site/routes/llm.py +++ b/site/routes/llm.py @@ -1,13 +1,14 @@ -"""routes/llm.py — LLM-извлечение + сравнение: один SSE-поток.""" +"""routes/llm.py — LLM-извлечение + сравнение + чат.""" import json as _json import time as _time -from flask import Response +from flask import Response, request, jsonify import db import extractor import differ +import llm_client def process(cid): @@ -137,3 +138,57 @@ def process(cid): yield f"data: {_json.dumps({'type': 'done', 'total_time_s': total_time, 'all_changes': all_changes})}\n\n" return Response(generate(), mimetype="text/event-stream") + + +def chat(cid): + """ + POST /llm/chat/ — задать вопрос по договору. + + Тело: {"question": "..."} + Контекст: все строки spec_rows этого договора. + Ответ: {"answer": "..."} + """ + data = request.get_json(silent=True) or {} + question = data.get("question", "").strip() + if not question: + return jsonify({"error": "Введите вопрос"}), 400 + + # ── Собрать контекст: все строки спецификации ────────── + rows_res, _ = db.query( + "SELECT s.type, s.number, d.filename, sr.row_num, sr.name, sr.price, sr.qty, sr.sum, sr.date_start " + "FROM spec_rows sr " + "JOIN supplements s ON sr.supplement_id = s.id " + "JOIN documents d ON s.document_id = d.id " + "WHERE s.contract_id = %s ORDER BY s.created_at, sr.row_num", + (cid,), + ) + if not rows_res or not rows_res["rows"]: + return jsonify({"answer": "Нет извлечённых данных. Сначала запустите сравнение."}) + + # Форматируем контекст + lines = [] + for r in rows_res["rows"]: + row = dict(zip(rows_res["columns"], r)) + lines.append( + f"[{row['filename']}] строка {row['row_num']}: {row['name']} | " + f"цена={row['price']} | объём={row['qty']} | сумма={row['sum']} | начало={row['date_start']}" + ) + context = "\n".join(lines) + + # ── Промпт ────────────────────────────────────────────── + prompt = f"""Ты — анализатор договоров облачного провайдера. У тебя есть извлечённые строки спецификации услуг. + +ДАННЫЕ: +{context} + +ВОПРОС ПОЛЬЗОВАТЕЛЯ: +{question} + +Ответь кратко и по делу, опираясь ТОЛЬКО на данные выше. Если данных недостаточно — скажи об этом.""" + + # ── Вызов LLM ─────────────────────────────────────────── + result = llm_client.ask(prompt, max_tokens=1000) + if "error" in result: + return jsonify({"answer": f"Ошибка LLM: {result['error']}"}) + + return jsonify({"answer": result.get("text", "Нет ответа")}) diff --git a/site/templates/llm.html b/site/templates/llm.html index 6ea1d3e..87216ea 100644 --- a/site/templates/llm.html +++ b/site/templates/llm.html @@ -104,6 +104,21 @@
+ + +