Skip to content
Open
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
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ ENGINE_PORT=8088
ORACLE_TEMPERATURE=0.5 # 0=cautious, 1=imaginative
ORACLE_TIMEOUT_SEC=180

# ── Swarm persona models (optional) ──
# Override the LLM model used for individual swarm personas during deliberation.
# Each persona can use a different model suited to its lens. When unset, the
# persona falls back to the main LLM_MODEL. Useful for running a reasoning
# model on the Skeptic while keeping faster models on other personas.
# SWARM_MODEL_STRATEGIST=qwen2.5:32b
# SWARM_MODEL_ECONOMIST=qwen2.5:32b
# SWARM_MODEL_NATURALIST=gemma3:12b
# SWARM_MODEL_SKEPTIC=deepseek-r1:14b

# ── Predictions ──
HORIZONS=24h,week,month,year # time horizons to forecast
PREDICTIONS_PER_HORIZON=3
Expand Down
10 changes: 10 additions & 0 deletions engine/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ class Config:

# ── Swarm (a council of LLM personas deliberates each forecast) ──
swarm_enabled: bool = field(default_factory=lambda: _b("SWARM_ENABLED", True))
swarm_persona_models: dict[str, str] = field(default_factory=lambda: {
name: model
for name, model in [
("Strategist", os.environ.get("SWARM_MODEL_STRATEGIST", "")),
("Economist", os.environ.get("SWARM_MODEL_ECONOMIST", "")),
("Naturalist", os.environ.get("SWARM_MODEL_NATURALIST", "")),
("Skeptic", os.environ.get("SWARM_MODEL_SKEPTIC", "")),
]
if model
})

def summary(self) -> dict:
return {
Expand Down
4 changes: 2 additions & 2 deletions engine/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ async def predict(self, brief: WorldBrief, on_stage: StageCB = None) -> list[Pre
async def _chat(self, user: str) -> str:
return await self._complete([{"role": "system", "content": SYSTEM}, {"role": "user", "content": user}], 1400)

async def _complete(self, messages: list[dict], max_tokens: int = 900) -> str:
body = {"model": self.model, "messages": messages, "temperature": CONFIG.temperature, "max_tokens": max_tokens}
async def _complete(self, messages: list[dict], max_tokens: int = 900, model: str | None = None) -> str:
body = {"model": model or self.model, "messages": messages, "temperature": CONFIG.temperature, "max_tokens": max_tokens}
async with httpx.AsyncClient(verify=HTTPX_VERIFY, timeout=CONFIG.request_timeout) as c:
r = await c.post(f"{self.base}/chat/completions", json=body,
headers={"Authorization": f"Bearer {self.key}"})
Expand Down
4 changes: 3 additions & 1 deletion engine/swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import json
import logging

from .config import CONFIG
from .models import AgentView, Prediction, WorldBrief

log = logging.getLogger("pythia.swarm")
Expand Down Expand Up @@ -50,8 +51,9 @@ def _persona_messages(name: str, lens: str, brief_text: str, preds: list[Predict
async def _ask(oracle, name: str, lens: str, brief_text: str,
preds: list[Prediction]) -> tuple[str, dict[int, tuple[float, str]]]:
"""Run one persona; return its {prediction_index: (probability, note)} map."""
persona_model = CONFIG.swarm_persona_models.get(name)
try:
text = await oracle._complete(_persona_messages(name, lens, brief_text, preds), max_tokens=1300)
text = await oracle._complete(_persona_messages(name, lens, brief_text, preds), max_tokens=1300, model=persona_model)
except Exception as e: # noqa: BLE001
log.warning("swarm persona %s failed: %s", name, e)
return name, {}
Expand Down