Update 4 files
- /rag/rag_server.py - /bots/rag_client.py - /bots/workers/indexing_worker.py - /bots/handlers/file_handler.py
This commit is contained in:
@@ -9,18 +9,25 @@ RAG-сервер – отдельный HTTP-сервис для обработ
|
||||
|
||||
ИСПОЛЬЗУЕТ: FastAPI для создания REST API и Uvicorn для запуска.
|
||||
ИСТОРИЯ ХРАНИТСЯ НА СЕРВЕРЕ (в PostgreSQL).
|
||||
|
||||
ДОБАВЛЕНО:
|
||||
- Эндпоинт /rag/vision для распознавания текста на изображениях (OCR)
|
||||
- Эндпоинт /rag/transcribe для транскрибации аудио (SaluteSpeech)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
# Добавляем путь к core, если запускаем из корня проекта
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Depends
|
||||
from fastapi import FastAPI, HTTPException, Depends, UploadFile, File as FastAPIFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, Field
|
||||
import uvicorn
|
||||
@@ -94,6 +101,18 @@ class IndexResponse(BaseModel):
|
||||
error: Optional[str] = Field(None, description="Сообщение об ошибке, если есть")
|
||||
|
||||
|
||||
class VisionResponse(BaseModel):
|
||||
"""Модель ответа на распознавание изображения."""
|
||||
text: str = Field(..., description="Распознанный текст")
|
||||
status: str = Field(..., description="Статус: ok, no_text_found, error")
|
||||
|
||||
|
||||
class TranscribeResponse(BaseModel):
|
||||
"""Модель ответа на транскрибацию аудио."""
|
||||
text: str = Field(..., description="Распознанный текст")
|
||||
status: str = Field(..., description="Статус: ok, no_speech_detected, error")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ГЛОБАЛЬНЫЙ ОРКЕСТРАТОР (инициализируется один раз при старте)
|
||||
# ============================================================
|
||||
@@ -311,6 +330,87 @@ async def health_check():
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# НОВЫЕ ЭНДПОИНТЫ: OCR и транскрибация
|
||||
# ============================================================
|
||||
|
||||
@app.post("/rag/vision", response_model=VisionResponse)
|
||||
async def vision_endpoint(
|
||||
file: UploadFile = FastAPIFile(...),
|
||||
user_jid: str = None,
|
||||
room_jid: str = None
|
||||
):
|
||||
"""
|
||||
Распознаёт текст на изображении через OCR (Tesseract).
|
||||
Возвращает JSON: {"text": str, "status": "ok" | "no_text_found" | "error"}
|
||||
"""
|
||||
orchestrator = get_orchestrator()
|
||||
|
||||
# Проверяем расширение
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff'):
|
||||
raise HTTPException(status_code=400, detail="Поддерживаются только изображения (jpg, png, bmp, tiff)")
|
||||
|
||||
# Сохраняем во временный файл
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp:
|
||||
tmp_path = tmp.name
|
||||
shutil.copyfileobj(file.file, tmp)
|
||||
|
||||
try:
|
||||
# Извлекаем текст через существующий FileService (синхронный метод)
|
||||
loop = asyncio.get_running_loop()
|
||||
text = await loop.run_in_executor(
|
||||
None,
|
||||
orchestrator.files.extract_text_from_image,
|
||||
tmp_path
|
||||
)
|
||||
if not text:
|
||||
return VisionResponse(text="", status="no_text_found")
|
||||
return VisionResponse(text=text, status="ok")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка OCR: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
@app.post("/rag/transcribe", response_model=TranscribeResponse)
|
||||
async def transcribe_endpoint(
|
||||
file: UploadFile = FastAPIFile(...),
|
||||
user_jid: str = None,
|
||||
room_jid: str = None
|
||||
):
|
||||
"""
|
||||
Транскрибирует аудиофайл через SaluteSpeech.
|
||||
Возвращает JSON: {"text": str, "status": "ok" | "no_speech_detected" | "error"}
|
||||
"""
|
||||
orchestrator = get_orchestrator()
|
||||
|
||||
# Проверяем расширение
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in ('.ogg', '.wav', '.mp3', '.amr', '.m4a'):
|
||||
raise HTTPException(status_code=400, detail="Поддерживаются только аудио (ogg, wav, mp3, amr, m4a)")
|
||||
|
||||
# Сохраняем во временный файл
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp:
|
||||
tmp_path = tmp.name
|
||||
shutil.copyfileobj(file.file, tmp)
|
||||
|
||||
try:
|
||||
# Транскрибируем через существующий FileService (асинхронный метод)
|
||||
text = await orchestrator.files.transcribe_audio(tmp_path)
|
||||
if not text:
|
||||
return TranscribeResponse(text="", status="no_speech_detected")
|
||||
return TranscribeResponse(text=text, status="ok")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка транскрибации: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ТОЧКА ВХОДА
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user