| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- """Run the assistant process: subscribe to the STT bus and serve the control socket."""
- import argparse
- import logging
- import signal
- import threading
- from ..bus import EventSubscriber
- from ..config import load_config
- from .audit import AuditLog
- from .control import ControlServer
- from .executor import LlmExecutor, NullExecutor
- from .llm import ChatSession, LlmClient
- from .service import AssistantService
- from .tools import default_registry
- def build_triage(cfg, client, on_result=None):
- """Build (analyzer, lane, extract_fn); (None, None, None) when disabled."""
- if not cfg.triage.enabled:
- return (None, None, None)
- from .laya_lane import LayaLane
- from .triage import TriageAnalyzer
- analyzer = TriageAnalyzer(cfg.triage)
- lane = LayaLane(
- cfg.triage,
- on_state=lambda state, reason: analyzer.refresh(state, reason),
- on_result=on_result,
- )
- extract = None
- if client is not None:
- def _extract(prompt):
- resp = client.chat(
- [
- {"role": "system", "content": cfg.llm.triage_system_prompt},
- {"role": "user", "content": prompt},
- ],
- max_tokens=cfg.triage.extract_max_tokens,
- timeout_s=cfg.triage.extract_timeout_s,
- )
- return resp.content
- extract = _extract
- return (analyzer, lane, extract)
- def main() -> int:
- """Entry point for the `stt-assistant` process."""
- parser = argparse.ArgumentParser(
- description="Assistant controller: subscribe to STT events and serve the control socket"
- )
- parser.add_argument("--config", type=str, default=None, help="Path to config.toml")
- parser.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
- args = parser.parse_args()
- logging.basicConfig(
- level=logging.DEBUG if args.verbose else logging.INFO,
- format="[%(levelname)s] %(message)s",
- )
- cfg = load_config(args.config)
- subscriber = EventSubscriber(cfg.events.socket or None)
- audit = AuditLog(enabled=cfg.audit.enabled, directory=cfg.audit.dir)
- executor = NullExecutor()
- chat_fn = None
- chat_reset = None
- session = None
- client = None
- if cfg.llm.enabled:
- client = LlmClient(cfg.llm)
- session = ChatSession(cfg.llm, client)
- executor = LlmExecutor(cfg.llm, client, default_registry())
- chat_fn = session.send
- chat_reset = session.reset
- holder = {}
- triage, lane, extract_fn = build_triage(
- cfg,
- client if cfg.llm.enabled else None,
- on_result=lambda gen, payload: holder["svc"]._on_triage_result(gen, payload),
- )
- service = AssistantService(
- cfg,
- subscriber,
- audit=audit,
- executor=executor,
- chat_fn=chat_fn,
- chat_reset=chat_reset,
- triage=triage,
- laya_lane=lane,
- triage_extract_fn=extract_fn,
- )
- holder["svc"] = service
- if session is not None:
- session.notify = service.chat_notify
- control = ControlServer(cfg, service)
- service.set_sinks(emit=control.broadcast, relay=control.relay)
- control.start()
- service.start()
- if lane is not None:
- lane.start()
- logging.info("Assistant ready. Control socket: %s", control.path)
- stop = threading.Event()
- def handle_signal(_signum, _frame):
- stop.set()
- signal.signal(signal.SIGINT, handle_signal)
- signal.signal(signal.SIGTERM, handle_signal)
- stop.wait()
- service.stop()
- if lane is not None:
- lane.stop()
- control.close()
- return 0
|