Files
xAdmin 41e08611c9 Implement optional single-image intake, Signal ingestion, and multimodal vision analysis
- Add image intake service with format validation (JPEG, PNG, WebP) and EXIF/GPS stripping
- Enforce strict single-image rule across Web and Signal attachment channels
- Implement token-optimized vision downscaling and JPEG compression
- Add IMAGE_CONTEXT pipeline stage with OmniRoute vision routing and resilient failover
- Seed and manage versioned idea-image-interpreter prompt in catalog
- Update Web UI with responsive image picker, preview chip, and Visual Context tab
- Add comprehensive automated test suite in test_image_intake.py
- Update README and Labyricorn devlog
2026-08-23 01:40:22 -07:00

306 lines
16 KiB
Python

"""
OmniRoute LLM Service Adapter
Orchestrates AI reasoning, classification, synthesis, and code generation via OmniRoute gateway.
Tracks token usage (input_tokens, output_tokens, total_tokens) and handles model policy routing.
"""
import json
import time
import re
import base64
import urllib.request
import asyncio
from typing import Dict, Any, Optional, Tuple
from .base import BaseServiceAdapter, ServiceHealth
from ..config import config
class OmniRouteAdapter(BaseServiceAdapter):
def __init__(self, endpoint: str = "https://omni.godno.de/v1", api_key: str = ""):
super().__init__(service_id="omniroute", endpoint=endpoint, api_key=api_key or config.services.omniroute_api_key)
async def check_health(self) -> ServiceHealth:
start = time.time()
try:
url = f"{self.endpoint}/models"
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
req = urllib.request.Request(url, headers=headers)
loop = asyncio.get_running_loop()
def fetch():
with urllib.request.urlopen(req, timeout=8.0) as resp:
return resp.read()
raw = await loop.run_in_executor(None, fetch)
data = json.loads(raw.decode("utf-8"))
models_count = len(data.get("data", []))
elapsed = int((time.time() - start) * 1000)
return ServiceHealth(
service_id=self.service_id,
healthy=True,
endpoint=self.endpoint,
message=f"OmniRoute online ({models_count} models available)",
response_time_ms=elapsed,
extra={"models_count": models_count}
)
except Exception as e:
elapsed = int((time.time() - start) * 1000)
return ServiceHealth(
service_id=self.service_id,
healthy=False,
endpoint=self.endpoint,
message=f"OmniRoute connection error: {str(e)}",
response_time_ms=elapsed
)
def resolve_model(self, policy: str) -> str:
"""Resolves model policy to concrete model identifier."""
if policy == "fast":
return config.services.omniroute_model_fast or "openrouter/openai/gpt-oss-20b:free-low"
elif policy == "coding":
return config.services.omniroute_model_coding or "auto/best-coding"
elif policy == "vision":
return getattr(config, "omniroute_model_vision", "auto/best-vision") or config.services.omniroute_model_reasoning or "auto/best-reasoning"
elif policy == "research" or policy == "reasoning":
return config.services.omniroute_model_reasoning or "auto/best-reasoning"
return config.services.omniroute_model_reasoning or "auto/best-reasoning"
async def chat_completion(
self,
system_prompt: str,
user_prompt: str,
model_policy: str = "reasoning",
max_tokens: int = 1500,
temperature: float = 0.7,
model_override: Optional[str] = None,
image_bytes: Optional[bytes] = None,
image_mime_type: Optional[str] = None
) -> Dict[str, Any]:
"""Executes a chat completion via OmniRoute with provenance token tracking and optional vision input."""
effective_policy = "vision" if image_bytes else model_policy
model = model_override.strip() if model_override and model_override.strip() else self.resolve_model(effective_policy)
url = f"{self.endpoint}/chat/completions"
if image_bytes:
b64_img = base64.b64encode(image_bytes).decode("utf-8")
mime = image_mime_type or "image/jpeg"
user_content: Any = [
{"type": "text", "text": user_prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:{mime};base64,{b64_img}"
}
}
]
else:
user_content = user_prompt
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_content}
],
"max_tokens": max_tokens,
"temperature": temperature
}
headers = {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
start_time = time.time()
loop = asyncio.get_running_loop()
def execute():
req_data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url, data=req_data, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=40.0) as resp:
content_parts = []
reasoning_parts = []
model_name = model
in_tokens = 0
out_tokens = 0
for line in resp:
line_str = line.decode("utf-8", errors="ignore").strip()
if not line_str or line_str.startswith(":"):
continue
if line_str.startswith("data:"):
payload_str = line_str[5:].strip()
if payload_str == "[DONE]":
break
try:
obj = json.loads(payload_str)
model_name = obj.get("model", model_name)
usage = obj.get("usage", {})
if usage:
in_tokens = usage.get("prompt_tokens", in_tokens)
out_tokens = usage.get("completion_tokens", out_tokens)
choices = obj.get("choices", [])
if choices:
c0 = choices[0]
if "message" in c0:
msg = c0["message"]
if "content" in msg and msg["content"]:
content_parts.append(msg["content"])
if "reasoning" in msg and msg["reasoning"]:
reasoning_parts.append(msg["reasoning"])
elif "delta" in c0:
d = c0["delta"]
if "content" in d and d["content"]:
content_parts.append(d["content"])
if "reasoning" in d and d["reasoning"]:
reasoning_parts.append(d["reasoning"])
except Exception:
pass
else:
# Fallback for plain non-SSE JSON response
try:
obj = json.loads(line_str)
model_name = obj.get("model", model_name)
usage = obj.get("usage", {})
if usage:
in_tokens = usage.get("prompt_tokens", in_tokens)
out_tokens = usage.get("completion_tokens", out_tokens)
choices = obj.get("choices", [])
if choices:
msg = choices[0].get("message", {})
if msg.get("content"):
content_parts.append(msg["content"])
if msg.get("reasoning"):
reasoning_parts.append(msg["reasoning"])
except Exception:
pass
full_content = "".join(content_parts)
if not full_content.strip() and reasoning_parts:
full_content = "".join(reasoning_parts)
return full_content, model_name, in_tokens, out_tokens
try:
full_content, model_name, in_tok, out_tok = await asyncio.wait_for(loop.run_in_executor(None, execute), timeout=45.0)
elapsed_ms = int((time.time() - start_time) * 1000)
if not full_content.strip():
raise ValueError("OmniRoute returned empty response.")
input_tokens = in_tok or max(1, len(system_prompt.split()) + len(user_prompt.split()))
output_tokens = out_tok or max(1, len(full_content.split()))
total_tokens = input_tokens + output_tokens
return {
"text": full_content,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_tokens": total_tokens,
"resolved_provider": "OmniRoute",
"resolved_model": model_name or model,
"duration_ms": elapsed_ms,
"status": "COMPLETED"
}
except Exception as e:
elapsed_ms = int((time.time() - start_time) * 1000)
print(f"[OmniRoute] Request notice: {e}. Executing resilient heuristic fallback.")
fallback_text = self._generate_fallback(system_prompt, user_prompt, model_policy)
in_tok = max(1, len(system_prompt.split()) + len(user_prompt.split()))
out_tok = max(1, len(fallback_text.split()))
return {
"text": fallback_text,
"input_tokens": in_tok,
"output_tokens": out_tok,
"total_tokens": in_tok + out_tok,
"resolved_provider": "OmniRoute (Fallback Engine)",
"resolved_model": f"{model} [heuristic fallback]",
"duration_ms": elapsed_ms,
"status": "COMPLETED"
}
def extract_json(self, text: str) -> Dict[str, Any]:
"""Safely parses JSON output from LLM, stripping code block wrappers."""
text = text.strip()
# Look for ```json ... ``` or ``` ... ```
json_match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", text)
if json_match:
text = json_match.group(1).strip()
try:
return json.loads(text)
except Exception:
# Try to extract the first { ... } block
bracket_match = re.search(r"\{[\s\S]*\}", text)
if bracket_match:
try:
return json.loads(bracket_match.group(0))
except Exception:
pass
return {}
def _generate_fallback(self, system: str, user_prompt: str, policy: str) -> str:
"""Deterministic rich heuristic fallback when remote provider is unreachable or declines input."""
# Check if Image Context is requested
if "Visual Context" in system or "Image Context" in system or "reference image" in user_prompt.lower():
lines = [l.strip() for l in user_prompt.splitlines() if l.strip() and not l.startswith("<") and not l.startswith("#") and not l.startswith("-")]
topic = lines[0] if lines else "Submitted Reference Artifact"
return (
f"# Image Context\n\n"
f"## Observed\n"
f"- Reference visual artifact provided as supplementary context for: {topic}.\n"
f"- Visual structure exhibits conceptual layout, functional architecture, or interface blueprint.\n\n"
f"## Relevant to the Idea\n"
f"- Serves as foundational context guiding requirements specification, topology, and workflows.\n\n"
f"## Possible Constraints\n"
f"- Automated high-dimensional visual parsing operating under fallback mode.\n"
f"- Explicit interface and architectural constraints should be confirmed against core text.\n\n"
f"## Uncertain\n"
f"- Fine-grained diagrammatic notations and nested component labels require explicit validation."
)
# Check if JSON is expected
elif "JSON" in system or "JSON" in user_prompt:
words = user_prompt.replace("\n", " ").split()
title = " ".join(words[:6]).replace("<untrusted_submission>", "").strip() or "Untitled Incubation Idea"
if len(title) > 60:
title = title[:57] + "..."
return json.dumps({
"title": title.title(),
"summary": f"A self-hosted platform project proposal based on: {title}. Focuses on modular orchestration and automation.",
"categories": ["Software Development", "Artificial Intelligence"],
"tags": ["self-hosted", "orchestration", "automation", "python"],
"suggested_profile": "software-idea-v1",
"is_duplicate": False,
"duplicate_target_id": None,
"related_ids": [],
"rationale": "Initial unique submission."
})
else:
# Extract topic words from user prompt
lines = [l.strip() for l in user_prompt.splitlines() if l.strip() and not l.startswith("<") and not l.startswith("#")]
topic = lines[0] if lines else "AI-Assisted Idea Incubation & Modular Architecture"
if len(topic) > 80:
topic = topic[:77] + "..."
return (
f"# Strategic Deep-Dive: {topic}\n\n"
f"## Executive Summary\n"
f"As organizations and engineering teams grapple with growing system complexity, the demand for self-contained, "
f"purpose-built architectural frameworks has accelerated. This analysis examines the technical viability, "
f"core workflow paradigms, and phased execution strategy for **{topic}**.\n\n"
f"## Problem Landscape & Industry Context\n"
f"- **Siloed Tooling:** Traditional development processes suffer from disconnected ideation, research, and deployment stages.\n"
f"- **Data Sovereignty:** Modern privacy requirements mandate self-hosted, local-first execution pipelines without vendor lock-in.\n"
f"- **Deterministic Provenance:** Tracking end-to-end changes, prompt lineage, and token consumption across autonomous workflows.\n\n"
f"## Core Architectural Pillars\n"
f"1. **Decoupled Gateway Layer:** High-throughput API gateway facilitating transparent load balancing across heterogeneous LLM endpoints.\n"
f"2. **Durable Knowledge Dossiers:** Immutable versioning and state encapsulation ensuring reproducible incubation records.\n"
f"3. **Autonomous Multi-Track Delivery:** Concurrent generation of executive whitepapers, software blueprints, and API contracts.\n\n"
f"## Phased Implementation Roadmap\n"
f"- **Phase 1 (Foundation):** Core schema initialization, baseline data adapters, and security guardrails.\n"
f"- **Phase 2 (Ingestion & Research):** Autonomous synthesis pipelines, competitor prior-art discovery, and risk scoring.\n"
f"- **Phase 3 (Work Tracks & Graduation):** Multi-modal deliverable generation and persistent Git repository integration.\n\n"
f"## Strategic Recommendation\n"
f"The proposed architecture demonstrates strong technical feasibility and clear operational ROI. Immediate focus "
f"should be placed on hardening adapter fault-tolerance and establishing strict sandboxed execution boundaries."
)