NeonClary
feat(conversation): pluggable structures + decision methods (Robert's Rules, ranked-choice, majority)
e661e91
Raw
History Blame Contribute Delete
2.02 kB
"""Base class every ConversationStructure plugin inherits.
A structure drives the *discussion* phase of a chat: how participants
present their views, debate, refine, and finalize positions. It does
NOT decide the verdict — that's the DecisionMethod's job. The two are
joined by `DecisionInput` (see `..types`).
Subclasses should:
1. Override `NAME` and `DESCRIPTION` so the frontend picker can
present a sensible label.
2. Implement `run()` as an async generator that yields SSE chunks.
Use the orchestrator's `_sse(...)` helper (imported lazily so
this module doesn't pull in the whole orchestrator) — see
existing structures for the pattern.
3. Implement `build_decision_input()` to return a `DecisionInput`
populated from the session state your `run()` produced.
Plugins live in this directory and are registered in
`app/services/conversation/__init__.py`.
"""
from __future__ import annotations
from typing import AsyncIterator, TYPE_CHECKING
if TYPE_CHECKING:
from app.services.conversation.types import DecisionInput
from app.services.models import Session
class ConversationStructure:
"""Abstract base; subclass and override `NAME`, `DESCRIPTION`,
`run()`, and `build_decision_input()`."""
#: Short label shown in the Settings menu.
NAME: str = "Conversation"
#: One-line description for the picker tooltip.
DESCRIPTION: str = ""
def __init__(self, session: "Session") -> None:
self.session = session
async def run(self) -> AsyncIterator[str]: # noqa: D401
"""Drive the discussion. Yields SSE chunks."""
raise NotImplementedError
yield # pragma: no cover # keeps mypy happy that this is a generator
def build_decision_input(self) -> "DecisionInput":
"""Produce the standardized hand-off for the decision phase.
Called once `run()` completes. The result is passed verbatim
to the chosen DecisionMethod's constructor.
"""
raise NotImplementedError