mirror of
https://github.com/Jeuners/astra-vision.git
synced 2026-09-09 15:02:35 +02:00
Zwei neue, sauber getrennte Fähigkeiten, jede in ihrem eigenen Modul: - astra/comfyui.py: async HTTP-Client für einen lokalen ComfyUI-Server (z-image-turbo-Workflow), kennt nichts von der Pipeline. - astra/documents.py: PDF-Textextraktion via pypdf, keine Netzwerkzugriffe. - astra/tools.py: verdrahtet generate_image als natives Ollama-Tool-Call — Qwen 3.5 unterstützt Tools und Vision bereits nativ laut `ollama show`. Dafür wurde astra/services.py so erweitert, dass NativeOllamaService Ollamas native tool_calls im Streaming-Response erkennt und als ChatCompletionChunk-Deltas an Pipecats bereits vorhandene, generische Function-Calling-Maschinerie (_process_context/run_function_calls) weiterreicht — die musste dafür nicht angefasst werden. trim_messages in core.py bewahrt jetzt Tool-Roundtrips und Bild-Anhänge vollständig statt sie auf role/content zu reduzieren. Neuer Upload-Button im UI (Bild oder PDF, während eines laufenden Gesprächs): PDFs gehen als Text, Bilder als Base64 über Qwens Vision in den Gesprächskontext ein. Generierte Bilder werden über /api/media/<id> ausgeliefert und per Datenkanal im Transkript angezeigt. Kompletter Function-Calling-Roundtrip end-to-end gegen echtes Ollama und echtes ComfyUI verifiziert (Modell ruft generate_image korrekt auf, Bild wird erzeugt und im media_store abgelegt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
import httpx
|
|
import pytest
|
|
|
|
from astra.comfyui import ComfyUIError, generate_image
|
|
|
|
|
|
def _patched_client(monkeypatch, handler):
|
|
real_async_client = httpx.AsyncClient
|
|
|
|
def factory(*args, **kwargs):
|
|
kwargs["transport"] = httpx.MockTransport(handler)
|
|
return real_async_client(**kwargs)
|
|
|
|
monkeypatch.setattr(httpx, "AsyncClient", factory)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_image_returns_bytes_from_completed_job(monkeypatch):
|
|
png_bytes = b"\x89PNG\r\n\x1a\nfake-image-bytes"
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path == "/prompt":
|
|
return httpx.Response(200, json={"prompt_id": "abc123"})
|
|
if request.url.path == "/history/abc123":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"abc123": {
|
|
"outputs": {
|
|
"9": {
|
|
"images": [
|
|
{"filename": "out.png", "subfolder": "", "type": "output"}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
)
|
|
if request.url.path == "/view":
|
|
return httpx.Response(200, content=png_bytes)
|
|
raise AssertionError(f"unexpected request: {request.url}")
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
result = await generate_image("http://fake-comfyui", "a cat")
|
|
assert result == png_bytes
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_image_raises_when_comfyui_unreachable(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
raise httpx.ConnectError("refused", request=request)
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
with pytest.raises(ComfyUIError):
|
|
await generate_image("http://fake-comfyui", "a cat")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_image_times_out_if_job_never_completes(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path == "/prompt":
|
|
return httpx.Response(200, json={"prompt_id": "abc123"})
|
|
if request.url.path == "/history/abc123":
|
|
return httpx.Response(200, json={})
|
|
raise AssertionError(f"unexpected request: {request.url}")
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
with pytest.raises(ComfyUIError, match="Zeitüberschreitung"):
|
|
await generate_image("http://fake-comfyui", "a cat", timeout=0.2)
|