4ef90b30bc
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 24s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 24s
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
Domyslnie model LOKALNY — prompt niesie oryginalne opisy z baz, wiec domyslnie NIC nie opuszcza sieci. Chmura wlaczana swiadomie (LOG-32). - app/llm/: LLMProvider (jak EphemerisEngine z LOG-24) + dwie implementacje. Lokalny serwer modelu (Ollama/vLLM/llama.cpp) i OpenAI mowia TYM SAMYM protokolem /chat/completions, wiec obsluguje je jedna klasa; Anthropic ma wlasny /v1/messages. Napisane na samym httpx — bez SDK openai/anthropic: mniej zaleznosci i pelna kontrola nad tym, co wychodzi z sieci. - Kazdy dostawca deklaruje `leaves_lan` — interfejs MUSI jawnie mowic, czy tresc baz opuszcza siec; UI na tej podstawie ostrzega. - Ponawianie z backoffem (429/5xx), timeouty, czytelne bledy zamiast stacktrace. - Klucz wylacznie z LLM_API_KEY (sekret), nigdy w repo ani w UI. - POST /chart/horoscope + GET /llm/health. WAZNE: prompt jest zwracany ZAWSZE — takze gdy model padnie lub brakuje klucza. Dzieki temu awaria dostawcy nie blokuje pracy: prompt mozna skopiowac i uzyc recznie. Prezentacja: wybor modelu (lokalny/Anthropic/OpenAI), przycisk „Napisz horoskop (AI)", wynik z informacja kto go napisal, czy dane opuscily siec, ile wskazan weszlo, oraz zastrzezenie ze to nie porada medyczna (PRE-15). Testy: 13 nowych (transport podstawiony — zaden prawdziwy model nie wolany), calosc 131 passed / 1 skipped. Zweryfikowane e2e na atrapie serwera modelu: horoskop napisany, leaves_lan=false, tokeny zliczone; a przy padnietym modelu / braku klucza / zlym dostawcy — czytelny blad i zachowany prompt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
"""Klient HTTP do warstwy logicznej.
|
|
|
|
Jedyny punkt styku prezentacji w dół. Przekazuje dane z formularza i odbiera
|
|
opracowane wyniki. Prezentacja nie sięga bezpośrednio do bazy ani do silnika.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from app.config import settings
|
|
|
|
|
|
def _auth_headers() -> dict[str, str]:
|
|
"""Token międzywarstwowy (LOG-32) — pusty, gdy ochrona wyłączona."""
|
|
token = os.getenv("INTERNAL_TOKEN", "")
|
|
return {"X-Astrololo-Token": token} if token else {}
|
|
|
|
|
|
class LogicClient:
|
|
def __init__(self, base_url: str | None = None) -> None:
|
|
self.base_url = (base_url or settings.logic_url).rstrip("/")
|
|
|
|
def query(self, query: str, field: str, exact: bool, limit: int) -> dict[str, Any]:
|
|
payload = {"query": query, "field": field, "exact": exact, "limit": limit}
|
|
with httpx.Client(timeout=settings.http_timeout) as client:
|
|
r = client.post(f"{self.base_url}/api/query", json=payload, headers=_auth_headers())
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def positions(
|
|
self,
|
|
when_utc_iso: str,
|
|
lat: float,
|
|
lon: float,
|
|
objects: list[str] | None = None,
|
|
house_system: str = "whole_sign",
|
|
stations: bool = False,
|
|
zodiac: str = "tropical",
|
|
) -> dict[str, Any]:
|
|
"""Pełny horoskop dla danego momentu — woła logic /chart/positions."""
|
|
payload = {
|
|
"when_utc": when_utc_iso,
|
|
"lat": lat,
|
|
"lon": lon,
|
|
"objects": objects,
|
|
"house_system": house_system,
|
|
"stations": stations,
|
|
"zodiac": zodiac,
|
|
}
|
|
# stacje wymagają root-findów — dłuższy timeout
|
|
with httpx.Client(timeout=max(settings.http_timeout, 60.0) if stations else settings.http_timeout) as client:
|
|
r = client.post(f"{self.base_url}/chart/positions", json=payload, headers=_auth_headers())
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def report(
|
|
self, when_utc_iso: str, lat: float, lon: float, limit: int = 5000, group: bool = False
|
|
) -> dict[str, Any]:
|
|
"""Sygnifikatory z obliczeń szukane w bazie — woła logic /chart/report."""
|
|
payload = {"when_utc": when_utc_iso, "lat": lat, "lon": lon, "limit": limit, "group": group}
|
|
with httpx.Client(timeout=max(settings.http_timeout, 30.0)) as client:
|
|
r = client.post(f"{self.base_url}/chart/report", json=payload, headers=_auth_headers())
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def prompt(
|
|
self, profile: str, when_utc_iso: str, lat: float, lon: float,
|
|
budget: str = "medium", from_date: str | None = None, to_date: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Gotowy prompt do LLM z wyliczeń (LOG-29/30) — woła logic /chart/prompt."""
|
|
payload: dict[str, Any] = {
|
|
"profile": profile, "when_utc": when_utc_iso,
|
|
"lat": lat, "lon": lon, "budget": budget,
|
|
}
|
|
if from_date and to_date:
|
|
payload["from_date"], payload["to_date"] = from_date, to_date
|
|
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
|
|
r = client.post(f"{self.base_url}/chart/prompt", json=payload)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def horoscope(
|
|
self, profile: str, when_utc_iso: str, lat: float, lon: float,
|
|
budget: str = "medium", provider: str | None = None,
|
|
from_date: str | None = None, to_date: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Napisany horoskop (LOG-31) — woła logic /chart/horoscope.
|
|
|
|
Generowanie tekstu trwa (zwłaszcza na modelu lokalnym), stąd długi timeout.
|
|
"""
|
|
payload: dict[str, Any] = {
|
|
"profile": profile, "when_utc": when_utc_iso,
|
|
"lat": lat, "lon": lon, "budget": budget,
|
|
}
|
|
if provider:
|
|
payload["provider"] = provider
|
|
if from_date and to_date:
|
|
payload["from_date"], payload["to_date"] = from_date, to_date
|
|
with httpx.Client(timeout=max(settings.http_timeout, 300.0)) as client:
|
|
r = client.post(f"{self.base_url}/chart/horoscope", json=payload)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def timeline(
|
|
self, when_utc_iso: str, lat: float, lon: float,
|
|
from_date: str, to_date: str, interpret: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Oś czasu z technik (+interpretacje z bazy) — woła logic /chart/timeline."""
|
|
payload = {
|
|
"when_utc": when_utc_iso, "lat": lat, "lon": lon,
|
|
"from_date": from_date, "to_date": to_date, "interpret": interpret,
|
|
}
|
|
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
|
|
r = client.post(f"{self.base_url}/chart/timeline", json=payload, headers=_auth_headers())
|
|
r.raise_for_status()
|
|
return r.json()
|