SolusOps commited on
Commit
63645ac
Β·
verified Β·
1 Parent(s): 676d6d1

feat: services package

Browse files
services/__init__.py ADDED
File without changes
services/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (178 Bytes). View file
 
services/__pycache__/featherless_provider.cpython-311.pyc ADDED
Binary file (2.13 kB). View file
 
services/__pycache__/hf_provider.cpython-311.pyc ADDED
Binary file (5.32 kB). View file
 
services/__pycache__/ingestion.cpython-311.pyc ADDED
Binary file (3.97 kB). View file
 
services/__pycache__/json_parser.cpython-311.pyc ADDED
Binary file (1.52 kB). View file
 
services/__pycache__/model_router.cpython-311.pyc ADDED
Binary file (3.43 kB). View file
 
services/featherless_provider.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ import urllib.request
4
+
5
+ FEATHERLESS_API_BASE = "https://api.featherless.ai/v1"
6
+
7
+ def generate(model_id: str, prompt: str, system: str = "",
8
+ api_key: str = "", max_tokens: int = 1024, temperature: float = 0.3) -> str:
9
+ """Featherless.ai (OpenAI-compatible) β€” Nemotron reasoning tasks."""
10
+ url = f"{FEATHERLESS_API_BASE}/chat/completions"
11
+ messages = []
12
+ if system:
13
+ messages.append({"role": "system", "content": system})
14
+ messages.append({"role": "user", "content": prompt})
15
+ payload = {"model": model_id, "messages": messages,
16
+ "max_tokens": max_tokens, "temperature": temperature}
17
+ data = json.dumps(payload).encode("utf-8")
18
+ headers = {"Content-Type": "application/json",
19
+ "Authorization": f"Bearer {api_key}"}
20
+ req = urllib.request.Request(url, data=data, headers=headers, method="POST")
21
+ with urllib.request.urlopen(req, timeout=120) as resp:
22
+ result = json.loads(resp.read().decode("utf-8"))
23
+ return result["choices"][0]["message"]["content"]
services/hf_provider.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ import urllib.request
4
+
5
+ HF_API_BASE = "https://api-inference.huggingface.co/models"
6
+
7
+ def generate(model_id: str, prompt: str, system: str = "",
8
+ api_key: str = "", max_tokens: int = 1024, temperature: float = 0.3) -> str:
9
+ """Text generation β€” used for MiniCPM concept extraction and Tiny Aya translation."""
10
+ url = f"{HF_API_BASE}/{model_id}"
11
+ messages = []
12
+ if system:
13
+ messages.append({"role": "system", "content": system})
14
+ messages.append({"role": "user", "content": prompt})
15
+ payload = {"inputs": {"messages": messages},
16
+ "parameters": {"max_new_tokens": max_tokens, "temperature": temperature}}
17
+ data = json.dumps(payload).encode("utf-8")
18
+ headers = {"Content-Type": "application/json"}
19
+ if api_key:
20
+ headers["Authorization"] = f"Bearer {api_key}"
21
+ req = urllib.request.Request(url, data=data, headers=headers, method="POST")
22
+ with urllib.request.urlopen(req, timeout=60) as resp:
23
+ result = json.loads(resp.read().decode("utf-8"))
24
+ if isinstance(result, list): return result[0].get("generated_text", str(result[0]))
25
+ if isinstance(result, dict): return result.get("generated_text", result.get("text", str(result)))
26
+ return str(result)
27
+
28
+ def vision_generate(model_id: str, image_b64: str, prompt: str,
29
+ api_key: str = "", max_tokens: int = 1024) -> str:
30
+ """Vision+language β€” MiniCPM-V for OCR and visual understanding."""
31
+ url = f"{HF_API_BASE}/{model_id}"
32
+ payload = {"inputs": {"image": image_b64, "question": prompt},
33
+ "parameters": {"max_new_tokens": max_tokens}}
34
+ data = json.dumps(payload).encode("utf-8")
35
+ headers = {"Content-Type": "application/json"}
36
+ if api_key:
37
+ headers["Authorization"] = f"Bearer {api_key}"
38
+ req = urllib.request.Request(url, data=data, headers=headers, method="POST")
39
+ with urllib.request.urlopen(req, timeout=90) as resp:
40
+ result = json.loads(resp.read().decode("utf-8"))
41
+ if isinstance(result, list): return str(result[0])
42
+ if isinstance(result, dict): return result.get("generated_text", result.get("text", str(result)))
43
+ return str(result)
44
+
45
+ def transcribe(model_id: str, audio_bytes: bytes, api_key: str = "") -> str:
46
+ """ASR β€” Whisper via HuggingFace for voice input."""
47
+ url = f"{HF_API_BASE}/{model_id}"
48
+ headers = {"Content-Type": "audio/wav"}
49
+ if api_key:
50
+ headers["Authorization"] = f"Bearer {api_key}"
51
+ req = urllib.request.Request(url, data=audio_bytes, headers=headers, method="POST")
52
+ with urllib.request.urlopen(req, timeout=60) as resp:
53
+ result = json.loads(resp.read().decode("utf-8"))
54
+ return result.get("text", "")
services/ingestion.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import os
3
+ import base64
4
+ from typing import Tuple
5
+
6
+ def load_file(path: str) -> Tuple[str, str]:
7
+ """
8
+ Returns (text, image_b64).
9
+ - text: extracted text layer (empty if image-only)
10
+ - image_b64: base64 image (empty if plain text)
11
+ Callers decide which to use. For PDF: both may be non-empty.
12
+ """
13
+ if not os.path.exists(path):
14
+ raise FileNotFoundError(f"File not found: {path}")
15
+ ext = os.path.splitext(path)[1].lower()
16
+ if ext in {".txt", ".md"}:
17
+ return _read_text(path), ""
18
+ if ext == ".pdf":
19
+ return _pdf_text(path), _pdf_first_page_b64(path)
20
+ if ext in {".png", ".jpg", ".jpeg", ".webp"}:
21
+ return "", _image_b64(path)
22
+ return _read_text(path), ""
23
+
24
+ def _read_text(path: str) -> str:
25
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
26
+ return f.read()
27
+
28
+ def _pdf_text(path: str) -> str:
29
+ try:
30
+ import fitz
31
+ doc = fitz.open(path)
32
+ text = "\n".join(page.get_text("text") for page in doc).strip()
33
+ doc.close()
34
+ return text
35
+ except Exception:
36
+ return ""
37
+
38
+ def _pdf_first_page_b64(path: str) -> str:
39
+ try:
40
+ import fitz
41
+ doc = fitz.open(path)
42
+ pix = doc[0].get_pixmap(dpi=150)
43
+ b64 = base64.b64encode(pix.tobytes("png")).decode("utf-8")
44
+ doc.close()
45
+ return b64
46
+ except Exception:
47
+ return ""
48
+
49
+ def _image_b64(path: str) -> str:
50
+ with open(path, "rb") as f:
51
+ return base64.b64encode(f.read()).decode("utf-8")
services/json_parser.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ import re
4
+
5
+ def extract_json(text: str) -> dict:
6
+ """
7
+ Robustly extract JSON from LLM response.
8
+ Handles: raw JSON, markdown code fences, leading/trailing prose.
9
+ """
10
+ text = text.strip()
11
+ text = re.sub(r"```(?:json)?\s*", "", text).replace("```", "")
12
+ try:
13
+ return json.loads(text)
14
+ except json.JSONDecodeError:
15
+ pass
16
+ for pattern in (r"\{[\s\S]*\}", r"\[[\s\S]*\]"):
17
+ match = re.search(pattern, text)
18
+ if match:
19
+ try:
20
+ return json.loads(match.group())
21
+ except json.JSONDecodeError:
22
+ pass
23
+ raise ValueError(f"No valid JSON found in response. First 200 chars: {text[:200]}")
services/model_router.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from services import hf_provider, featherless_provider
3
+
4
+ class ModelRouter:
5
+ """
6
+ Routes tasks to the correct model and provider.
7
+ understand() β†’ MiniCPM-V (HF) β€” all document tasks
8
+ reason() β†’ Nemotron 4B (Featherless) β€” all reasoning tasks
9
+ translate() β†’ Tiny Aya (HF) β€” multilingual
10
+ transcribe() β†’ Whisper (HF) β€” speech to text
11
+ """
12
+ def __init__(self, ocr_model: str, reasoning_model: str,
13
+ multilingual_model: str, speech_model: str,
14
+ hf_api_key: str = "", featherless_api_key: str = "",
15
+ max_tokens: int = 1024, temperature: float = 0.3):
16
+ self._ocr_model = ocr_model
17
+ self._reason_model = reasoning_model
18
+ self._multi_model = multilingual_model
19
+ self._speech_model = speech_model
20
+ self._hf_key = hf_api_key
21
+ self._fl_key = featherless_api_key
22
+ self._max_tokens = max_tokens
23
+ self._temperature = temperature
24
+
25
+ def understand(self, prompt: str, image_b64: str = "") -> str:
26
+ """MiniCPM-V via HuggingFace β€” OCR, diagram reading, concept extraction."""
27
+ if image_b64:
28
+ return hf_provider.vision_generate(
29
+ self._ocr_model, image_b64, prompt,
30
+ self._hf_key, self._max_tokens)
31
+ return hf_provider.generate(
32
+ self._ocr_model, prompt, api_key=self._hf_key,
33
+ max_tokens=self._max_tokens, temperature=self._temperature)
34
+
35
+ def reason(self, prompt: str, system: str = "") -> str:
36
+ """Nemotron 3 Nano 4B via Featherless β€” quests, questions, tutor."""
37
+ return featherless_provider.generate(
38
+ self._reason_model, prompt, system,
39
+ self._fl_key, self._max_tokens, self._temperature)
40
+
41
+ def translate(self, prompt: str, system: str = "") -> str:
42
+ """Tiny Aya 3.3B via HuggingFace β€” multilingual explanations."""
43
+ return hf_provider.generate(
44
+ self._multi_model, prompt, system,
45
+ self._hf_key, self._max_tokens, self._temperature)
46
+
47
+ def transcribe(self, audio_bytes: bytes) -> str:
48
+ """Whisper via HuggingFace β€” speech to text."""
49
+ return hf_provider.transcribe(self._speech_model, audio_bytes, self._hf_key)