1
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from backend.app.config import settings
|
||||
|
||||
|
||||
class DeepgramClient:
|
||||
endpoint = "https://api.deepgram.com/v1/listen?model=nova-3&smart_format=true&language=es"
|
||||
|
||||
def transcribe_file(self, path: Path) -> dict:
|
||||
if not settings.deepgram_api_key:
|
||||
raise RuntimeError("DEEPGRAM_API_KEY no esta configurada")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Token {settings.deepgram_api_key}",
|
||||
"Content-Type": self._content_type(path),
|
||||
}
|
||||
with path.open("rb") as audio_file:
|
||||
response = requests.post(self.endpoint, headers=headers, data=audio_file, timeout=600)
|
||||
|
||||
if response.status_code == 401:
|
||||
raise RuntimeError("API Key de Deepgram invalida")
|
||||
if not response.ok:
|
||||
try:
|
||||
error = response.json()
|
||||
except ValueError:
|
||||
error = response.text
|
||||
raise RuntimeError(f"Deepgram error {response.status_code}: {error}")
|
||||
return response.json()
|
||||
|
||||
def transcript_from_response(self, data: dict) -> tuple[str, float | None]:
|
||||
alternative = data.get("results", {}).get("channels", [{}])[0].get("alternatives", [{}])[0]
|
||||
transcript = alternative.get("paragraphs", {}).get("transcript") or alternative.get("transcript") or ""
|
||||
confidence = alternative.get("confidence")
|
||||
if not transcript.strip():
|
||||
raise RuntimeError("Deepgram no devolvio transcripcion")
|
||||
return transcript.strip(), confidence
|
||||
|
||||
def _content_type(self, path: Path) -> str:
|
||||
ext = path.suffix.lower()
|
||||
return {
|
||||
".mp3": "audio/mpeg",
|
||||
".wav": "audio/wav",
|
||||
".m4a": "audio/mp4",
|
||||
".mp4": "audio/mp4",
|
||||
".webm": "audio/webm",
|
||||
".ogg": "audio/ogg",
|
||||
".flac": "audio/flac",
|
||||
}.get(ext, "application/octet-stream")
|
||||
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from backend.app.config import settings
|
||||
|
||||
|
||||
class MistralClient:
|
||||
endpoint = "https://api.mistral.ai/v1/chat/completions"
|
||||
model = "pixtral-12b-2409"
|
||||
|
||||
def ocr_image(self, image_path: Path) -> str:
|
||||
if not settings.mistral_api_key:
|
||||
raise RuntimeError("MISTRAL_API_KEY no esta configurada")
|
||||
data_url = self._image_data_url(image_path)
|
||||
response = requests.post(
|
||||
self.endpoint,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {settings.mistral_api_key}",
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Extract all text from this image and format it as Markdown. If there are tables, represent them using Markdown table syntax. Do not include commentary.",
|
||||
},
|
||||
{"type": "image_url", "image_url": data_url},
|
||||
],
|
||||
}
|
||||
],
|
||||
"max_tokens": 3000,
|
||||
},
|
||||
timeout=180,
|
||||
)
|
||||
if response.status_code == 401:
|
||||
raise RuntimeError("API Key de Mistral invalida")
|
||||
if not response.ok:
|
||||
raise RuntimeError(f"Mistral error {response.status_code}: {response.text}")
|
||||
data = response.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
def _image_data_url(self, image_path: Path) -> str:
|
||||
encoded = base64.b64encode(image_path.read_bytes()).decode("ascii")
|
||||
return f"data:image/jpeg;base64,{encoded}"
|
||||
Reference in New Issue
Block a user