feat: spirit engine — seance WS, entity minting/Codex, Piper TTS voices, wire telemetry
- WS /ws/session: modes, summon, anomaly fragments, streaming direct contact,
passive wire-ghost ambient loop, per-user rate limits, event transcript
- entities: anomaly-signature fingerprinting, LLM minting + procedural
fallback, Codex matching with contact counts and sightings
- tts: 8 local Piper voices (EN/ES), per-entity voice profiles, numpy
effects chain (pitch/rate/bitcrush/echo/static)
- llm: streaming client, submit_stream in bounded queue, SpiritService
with offline fallbacks for every channel
- routes: public /api/codex, /api/codex/{id}, /api/stats; /audio static mount
- models: Entity, EntitySighting, Event, ContactSession(entity_id, language)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config import settings
|
||||
@@ -7,12 +9,51 @@ class OllamaClient:
|
||||
def __init__(self, base_url: str | None = None):
|
||||
self._base_url = base_url or settings.ollama_base_url
|
||||
|
||||
async def generate(self, model: str, prompt: str, system: str | None = None) -> str:
|
||||
payload = {"model": model, "prompt": prompt, "stream": False}
|
||||
async def generate(
|
||||
self,
|
||||
model: str,
|
||||
prompt: str,
|
||||
system: str | None = None,
|
||||
options: dict | None = None,
|
||||
) -> str:
|
||||
payload: dict = {"model": model, "prompt": prompt, "stream": False}
|
||||
if system is not None:
|
||||
payload["system"] = system
|
||||
if options:
|
||||
payload["options"] = options
|
||||
|
||||
async with httpx.AsyncClient(base_url=self._base_url, timeout=120.0) as http_client:
|
||||
response = await http_client.post("/api/generate", json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()["response"]
|
||||
|
||||
async def generate_stream(
|
||||
self,
|
||||
model: str,
|
||||
prompt: str,
|
||||
system: str | None = None,
|
||||
options: dict | None = None,
|
||||
) -> AsyncIterator[str]:
|
||||
"""Yields response tokens as Ollama produces them."""
|
||||
payload: dict = {"model": model, "prompt": prompt, "stream": True}
|
||||
if system is not None:
|
||||
payload["system"] = system
|
||||
if options:
|
||||
payload["options"] = options
|
||||
|
||||
async with httpx.AsyncClient(base_url=self._base_url, timeout=300.0) as http_client:
|
||||
async with http_client.stream(
|
||||
"POST", "/api/generate", json=payload
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.aiter_lines():
|
||||
if not line:
|
||||
continue
|
||||
import json
|
||||
|
||||
chunk = json.loads(line)
|
||||
token = chunk.get("response", "")
|
||||
if token:
|
||||
yield token
|
||||
if chunk.get("done"):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user