Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ POSTGRES_DB=synthora
# --- LLM providers ---
OPENAI_API_KEY=
OPENAI_BASE_URL= # optional: OpenRouter / vLLM / LM Studio
# When using docker compose --profile ollama with docker-compose.ollama.yml:
# OLLAMA_BASE_URL=http://ollama:11434
OLLAMA_BASE_URL=http://localhost:11434

# --- Search ---
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ LangGraph Studio: `uv run langgraph dev` (uses `langgraph.json`).
docker compose up -d
```

Brings up: API (`:8000`), worker, web UI (`:3000`), Postgres, Redis, SearXNG. Optional local LLM: `docker compose --profile ollama up -d`.
Brings up: API (`:8000`), worker, web UI (`:3000`), Postgres, Redis, SearXNG. Optional local LLM:

```bash
docker compose --profile ollama -f docker-compose.yml -f docker-compose.ollama.yml up -d
```

The overlay sets `OLLAMA_BASE_URL=http://ollama:11434` on API/worker. Default embeddings use `SYNTHORA_EMBEDDINGS=hash` (no Ollama required).

Key environment variables (see `.env.example`): `SYNTHORA_DATABASE_URL`, `SYNTHORA_REDIS_URL`, `SYNTHORA_AUTH_MODE` (`none`|`session`), `OPENAI_API_KEY` / `OPENAI_BASE_URL`, `TAVILY_API_KEY`, `SEARXNG_URL`.

Expand Down
10 changes: 10 additions & 0 deletions docker-compose.ollama.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Optional overlay when starting with `--profile ollama`.
# Usage:
# docker compose --profile ollama -f docker-compose.yml -f docker-compose.ollama.yml up -d
services:
api:
environment:
OLLAMA_BASE_URL: http://ollama:11434
worker:
environment:
OLLAMA_BASE_URL: http://ollama:11434
5 changes: 5 additions & 0 deletions docs/feature-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ Closed on ``feat/compose-embeddings-default``: compose defaults
when profile disabled); sync ``SynthoraClient.iter_run_events``; embedding
default tests; parity audit doc sync.

Closed on ``feat/ollama-profile-and-async-ws``: ``docker-compose.ollama.yml``
overlay wires ``OLLAMA_BASE_URL`` for the ollama profile; async
``iter_run_events`` handles ``ConnectionClosed`` like sync; README/smoke
comments updated.

No known functional gaps remain beyond explicit non-goals below.

Chat remains session-scoped ``fast_research`` with prior-report memory —
Expand Down
3 changes: 2 additions & 1 deletion docs/parity-audit.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Synthora parity audit checklist

Living checklist against Open Deep Research, STORM/Co-STORM, and Local Deep
Research. Last verified on `main` after PR #12 (`2537bd6`).
Research. Last verified on `main` after PR #13 (`8e13595`) and ollama-profile overlay.

## Open Deep Research

Expand Down Expand Up @@ -69,6 +69,7 @@ Research. Last verified on `main` after PR #12 (`2537bd6`).
| Item | Status |
|---|---|
| Compose default embeddings (`SYNTHORA_EMBEDDINGS=hash`) without Ollama profile | done (PR #13) |
| Ollama profile overlay (`docker-compose.ollama.yml` sets service URL) | done (PR #14) |
| Live compose smoke (`scripts/smoke.sh`: research, export, upload) | done |
| Playwright UI e2e | API-mocked (CI speed); not a live-stack browser gate |

Expand Down
6 changes: 5 additions & 1 deletion packages/sdk/src/synthora/sdk/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,18 @@ async def wait_for_report(
async def iter_run_events(self, run_id: str) -> AsyncIterator[dict]:
"""Stream live events from the run WebSocket."""
import websockets
from websockets.exceptions import ConnectionClosed

url = self.events_ws_url(run_id)
headers = []
if self.token:
headers.append(("Authorization", f"Bearer {self.token}"))
async with websockets.connect(url, additional_headers=headers) as ws:
while True:
raw = await ws.recv()
try:
raw = await ws.recv()
except ConnectionClosed:
break
yield json.loads(raw)

async def chat(self, message: str, *, session_id: Optional[str] = None) -> dict:
Expand Down
3 changes: 1 addition & 2 deletions scripts/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
set -euo pipefail
cd "$(dirname "$0")/.."

# Compose defaults OLLAMA_BASE_URL to the optional ollama profile service; smoke
# uses deterministic hash embeddings unless callers override these explicitly.
# Compose defaults hash embeddings (no Ollama required). Override OPENAI/OLLAMA as needed.
export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
export OLLAMA_BASE_URL="${OLLAMA_BASE_URL:-}"
export SYNTHORA_EMBEDDINGS="${SYNTHORA_EMBEDDINGS:-hash}"
Expand Down
35 changes: 35 additions & 0 deletions tests/test_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,41 @@ def recv(self):
assert events[-1]["type"] == "done"


@pytest.mark.asyncio
async def test_async_iter_run_events(monkeypatch):
from synthora.sdk.async_client import AsyncSynthoraClient
from websockets.exceptions import ConnectionClosedOK

payloads = [
{"type": "status", "message": "queued"},
{"type": "done", "message": "completed"},
]

class _FakeWS:
def __init__(self):
self._queue = list(payloads)

async def __aenter__(self):
return self

async def __aexit__(self, *_args):
return False

async def recv(self):
if self._queue:
return json.dumps(self._queue.pop(0))
raise ConnectionClosedOK(None, None)

monkeypatch.setattr(
"websockets.connect",
lambda *_a, **_k: _FakeWS(),
)
client = AsyncSynthoraClient("http://localhost:8000")
events = [event async for event in client.iter_run_events("run-1")]
assert events[0]["type"] == "status"
assert events[-1]["type"] == "done"


@pytest.mark.asyncio
async def test_async_sdk_health_and_mcp(platform):
from httpx import ASGITransport
Expand Down
Loading