89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
"""The two HTTP contracts exposed by the supplied n8n workflows."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
|
|
from config import Settings, require_webhook_url
|
|
|
|
|
|
class WebhookError(RuntimeError):
|
|
"""An n8n request failed or returned an unusable response."""
|
|
|
|
|
|
class N8NClient:
|
|
def __init__(
|
|
self,
|
|
settings: Settings,
|
|
transport: httpx.AsyncBaseTransport | None = None,
|
|
) -> None:
|
|
self.settings = settings
|
|
self._transport = transport
|
|
|
|
async def ask(self, message: str, collection_name: str, session_id: str) -> str:
|
|
message = message.strip()
|
|
collection_name = collection_name.strip()
|
|
if not message or not collection_name or not session_id:
|
|
raise ValueError("Укажите вопрос и коллекцию.")
|
|
|
|
url = require_webhook_url(self.settings.chat_webhook_url, "вопросов")
|
|
try:
|
|
async with httpx.AsyncClient(
|
|
transport=self._transport,
|
|
timeout=self.settings.chat_timeout_seconds,
|
|
) as client:
|
|
response = await client.post(
|
|
url,
|
|
json={
|
|
"message": message,
|
|
"collection_name": collection_name,
|
|
"session_id": session_id,
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
except httpx.TimeoutException as exc:
|
|
raise WebhookError("Время ожидания ответа истекло. Попробуйте ещё раз.") from exc
|
|
except httpx.HTTPStatusError as exc:
|
|
raise WebhookError(
|
|
f"Webhook вопросов вернул HTTP {exc.response.status_code}."
|
|
) from exc
|
|
except httpx.RequestError as exc:
|
|
raise WebhookError("Не удалось связаться с webhook вопросов.") from exc
|
|
|
|
answer = response.text.strip()
|
|
if not answer:
|
|
raise WebhookError("Webhook вопросов вернул пустой ответ.")
|
|
return answer
|
|
|
|
async def upload(self, filename: str, contents: bytes, collection_name: str) -> None:
|
|
collection_name = collection_name.strip()
|
|
if not collection_name:
|
|
raise ValueError("Укажите коллекцию.")
|
|
if not filename.lower().endswith(".md"):
|
|
raise ValueError("Можно загружать только файлы .md.")
|
|
if not contents:
|
|
raise ValueError("Выбранный файл пуст.")
|
|
if len(contents) > self.settings.max_file_bytes:
|
|
raise ValueError("Размер файла превышает допустимый предел.")
|
|
|
|
url = require_webhook_url(self.settings.upload_webhook_url, "загрузки")
|
|
try:
|
|
async with httpx.AsyncClient(
|
|
transport=self._transport,
|
|
timeout=self.settings.upload_timeout_seconds,
|
|
) as client:
|
|
response = await client.post(
|
|
url,
|
|
data={"collection_name": collection_name},
|
|
files={"file": (filename, contents, "text/markdown")},
|
|
)
|
|
response.raise_for_status()
|
|
except httpx.TimeoutException as exc:
|
|
raise WebhookError("Время ожидания загрузки истекло. Проверьте n8n перед повтором.") from exc
|
|
except httpx.HTTPStatusError as exc:
|
|
raise WebhookError(
|
|
f"Webhook загрузки вернул HTTP {exc.response.status_code}."
|
|
) from exc
|
|
except httpx.RequestError as exc:
|
|
raise WebhookError("Не удалось связаться с webhook загрузки.") from exc
|