atlasbot: add quick/smart bot config

This commit is contained in:
Brad Stein 2026-01-28 12:57:50 -03:00
parent fcca4107db
commit 9adc25ad86
5 changed files with 56 additions and 16 deletions

View File

@ -31,6 +31,7 @@ class Settings:
room_alias: str room_alias: str
server_name: str server_name: str
bot_mentions: tuple[str, ...] bot_mentions: tuple[str, ...]
matrix_bots: tuple["MatrixBotConfig", ...]
ollama_url: str ollama_url: str
ollama_model: str ollama_model: str
@ -64,6 +65,37 @@ class Settings:
smart_max_candidates: int smart_max_candidates: int
@dataclass(frozen=True)
class MatrixBotConfig:
username: str
password: str
mentions: tuple[str, ...]
mode: str
def _load_matrix_bots(bot_mentions: tuple[str, ...]) -> tuple[MatrixBotConfig, ...]:
bots: list[MatrixBotConfig] = []
quick_user = os.getenv("BOT_USER_QUICK", "").strip()
quick_pass = os.getenv("BOT_PASS_QUICK", "").strip()
if quick_user and quick_pass:
bots.append(MatrixBotConfig(quick_user, quick_pass, (quick_user,), "quick"))
smart_user = os.getenv("BOT_USER_SMART", "").strip()
smart_pass = os.getenv("BOT_PASS_SMART", "").strip()
if smart_user and smart_pass:
bots.append(MatrixBotConfig(smart_user, smart_pass, (smart_user,), "smart"))
if bots:
return tuple(bots)
legacy_user = os.getenv("BOT_USER", "").strip()
legacy_pass = os.getenv("BOT_PASS", "").strip()
if legacy_user and legacy_pass:
mentions = bot_mentions or (legacy_user,)
bots.append(MatrixBotConfig(legacy_user, legacy_pass, mentions, ""))
return tuple(bots)
def load_settings() -> Settings: def load_settings() -> Settings:
bot_mentions = tuple( bot_mentions = tuple(
@ -73,6 +105,7 @@ def load_settings() -> Settings:
if item.strip() if item.strip()
] ]
) )
matrix_bots = _load_matrix_bots(bot_mentions)
return Settings( return Settings(
matrix_base=os.getenv("MATRIX_BASE", "http://othrys-synapse-matrix-synapse:8008"), matrix_base=os.getenv("MATRIX_BASE", "http://othrys-synapse-matrix-synapse:8008"),
auth_base=os.getenv("AUTH_BASE", "http://matrix-authentication-service:8080"), auth_base=os.getenv("AUTH_BASE", "http://matrix-authentication-service:8080"),
@ -81,6 +114,7 @@ def load_settings() -> Settings:
room_alias=os.getenv("ROOM_ALIAS", "#othrys:live.bstein.dev"), room_alias=os.getenv("ROOM_ALIAS", "#othrys:live.bstein.dev"),
server_name=os.getenv("MATRIX_SERVER_NAME", "live.bstein.dev"), server_name=os.getenv("MATRIX_SERVER_NAME", "live.bstein.dev"),
bot_mentions=bot_mentions, bot_mentions=bot_mentions,
matrix_bots=matrix_bots,
ollama_url=os.getenv("OLLAMA_URL", "http://ollama.ai.svc.cluster.local:11434"), ollama_url=os.getenv("OLLAMA_URL", "http://ollama.ai.svc.cluster.local:11434"),
ollama_model=os.getenv("OLLAMA_MODEL", "qwen2.5:14b-instruct"), ollama_model=os.getenv("OLLAMA_MODEL", "qwen2.5:14b-instruct"),
ollama_model_fast=os.getenv("ATLASBOT_MODEL_FAST", "qwen2.5:14b-instruct"), ollama_model_fast=os.getenv("ATLASBOT_MODEL_FAST", "qwen2.5:14b-instruct"),

View File

@ -33,6 +33,7 @@ SYNTHESIZE_PROMPT = (
"Synthesize a final response from the best candidates. " "Synthesize a final response from the best candidates. "
"Use a natural, helpful tone with light reasoning. " "Use a natural, helpful tone with light reasoning. "
"Avoid lists unless the user asked for lists. " "Avoid lists unless the user asked for lists. "
"Do not include confidence scores or evaluation metadata."
) )
STOCK_SYSTEM = ( STOCK_SYSTEM = (

View File

@ -47,8 +47,8 @@ async def main() -> None:
server = uvicorn.Server(uvicorn.Config(api.app, host="0.0.0.0", port=settings.http_port, log_level="info")) server = uvicorn.Server(uvicorn.Config(api.app, host="0.0.0.0", port=settings.http_port, log_level="info"))
tasks = [] tasks = []
if settings.bot_user and settings.bot_pass: for bot in settings.matrix_bots:
tasks.append(asyncio.create_task(MatrixBot(settings, engine, answer_handler).run())) tasks.append(asyncio.create_task(MatrixBot(settings, bot, engine, answer_handler).run()))
tasks.append(asyncio.create_task(server.serve())) tasks.append(asyncio.create_task(server.serve()))
await asyncio.gather(*tasks) await asyncio.gather(*tasks)

View File

@ -6,7 +6,7 @@ from urllib.parse import quote
import httpx import httpx
from atlasbot.config import Settings from atlasbot.config import MatrixBotConfig, Settings
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from atlasbot.engine.answerer import AnswerEngine, AnswerResult from atlasbot.engine.answerer import AnswerEngine, AnswerResult
@ -15,14 +15,15 @@ log = logging.getLogger(__name__)
class MatrixClient: class MatrixClient:
def __init__(self, settings: Settings) -> None: def __init__(self, settings: Settings, bot: MatrixBotConfig) -> None:
self._settings = settings self._settings = settings
self._bot = bot
async def login(self) -> str: async def login(self) -> str:
payload = { payload = {
"type": "m.login.password", "type": "m.login.password",
"identifier": {"type": "m.id.user", "user": self._settings.bot_user}, "identifier": {"type": "m.id.user", "user": self._bot.username},
"password": self._settings.bot_pass, "password": self._bot.password,
} }
url = f"{self._settings.auth_base}/_matrix/client/v3/login" url = f"{self._settings.auth_base}/_matrix/client/v3/login"
async with httpx.AsyncClient(timeout=15.0) as client: async with httpx.AsyncClient(timeout=15.0) as client:
@ -77,12 +78,14 @@ class MatrixBot:
def __init__( def __init__(
self, self,
settings: Settings, settings: Settings,
bot: MatrixBotConfig,
engine: AnswerEngine, engine: AnswerEngine,
answer_handler: Callable[[str, str, Callable[[str, str], None] | None], Awaitable[AnswerResult]] | None = None, answer_handler: Callable[[str, str, Callable[[str, str], None] | None], Awaitable[AnswerResult]] | None = None,
) -> None: ) -> None:
self._settings = settings self._settings = settings
self._bot = bot
self._engine = engine self._engine = engine
self._client = MatrixClient(settings) self._client = MatrixClient(settings, bot)
self._answer_handler = answer_handler self._answer_handler = answer_handler
async def run(self) -> None: async def run(self) -> None:
@ -121,9 +124,9 @@ class MatrixBot:
content = event.get("content") or {} content = event.get("content") or {}
body = content.get("body") or "" body = content.get("body") or ""
sender = event.get("sender") or "" sender = event.get("sender") or ""
if sender.endswith(f"/{self._settings.bot_user}") or sender == self._settings.bot_user: if sender.endswith(f"/{self._bot.username}") or sender == self._bot.username:
continue continue
mode, question = _extract_mode(body, self._settings.bot_mentions) mode, question = _extract_mode(body, self._bot.mentions, self._bot.mode)
if not question: if not question:
continue continue
await self._client.send_message(token, room_id, "Thinking…") await self._client.send_message(token, room_id, "Thinking…")
@ -169,11 +172,12 @@ class MatrixBot:
task.cancel() task.cancel()
def _extract_mode(body: str, mentions: tuple[str, ...]) -> tuple[str, str]: def _extract_mode(body: str, mentions: tuple[str, ...], default_mode: str) -> tuple[str, str]:
lower = body.lower() lower = body.lower()
for mention in mentions: for mention in mentions:
if mention and mention.lower() in lower: if mention and mention.lower() in lower:
mode = "quick" mode = default_mode or "quick"
if not default_mode:
if "atlas-smart" in lower or "smart" in lower: if "atlas-smart" in lower or "smart" in lower:
mode = "smart" mode = "smart"
if "atlas-quick" in lower or "quick" in lower: if "atlas-quick" in lower or "quick" in lower:

View File

@ -25,6 +25,7 @@ def _settings() -> Settings:
room_alias="", room_alias="",
server_name="", server_name="",
bot_mentions=(), bot_mentions=(),
matrix_bots=(),
ollama_url="", ollama_url="",
ollama_model="base", ollama_model="base",
ollama_model_fast="fast", ollama_model_fast="fast",