File size: 3,056 Bytes
e661e91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""Collaborative Discussion — the original CCAI structure.

This is a thin wrapper over the existing Phase 1-4 implementation in
`app.services.orchestrator`. We keep the function bodies in
orchestrator.py for now (less code churn during the plugin refactor)
and the plugin just dispatches to them in the historical order:

  Phase 1: Initial opinions (each participant speaks once with no
           transcript, so first opinions are independent).
  Phase 2: Critique rounds (1-4, configurable). Each participant
           critiques / agrees with / builds on what others said.
  Phase 3: Status assessment + targeted follow-ups (orchestrator
           surfaces open questions; max iterations from limits).
  Phase 4: Finalization (each participant states a final opinion,
           which is what we hand off to the decision phase).

When this structure finishes, `session.final_opinions[pid]` holds
each AI participant's last word. We marshal that into a
DecisionInput so any DecisionMethod can take over.
"""
from __future__ import annotations

from typing import AsyncIterator, TYPE_CHECKING

from app.services.conversation.structures.base import ConversationStructure
from app.services.conversation.types import DecisionInput

if TYPE_CHECKING:
    pass


class CollaborativeDiscussion(ConversationStructure):
    NAME = "Collaborative Discussion"
    DESCRIPTION = (
        "Initial opinions, then critique rounds, then a status check "
        "for unanswered questions, then a final opinion from each "
        "participant."
    )

    async def run(self) -> AsyncIterator[str]:
        # Lazy import so this module can be loaded without dragging
        # in the whole orchestrator (and so orchestrator.py can in
        # turn import the structures registry without a cycle).
        from app.services.orchestrator import (
            _phase_initial_opinions,
            _phase_critique,
            _phase_status_assessment,
            _phase_finalization,
        )

        async for chunk in _phase_initial_opinions(self.session):
            yield chunk

        for round_n in range(1, self.session.limits.critique_rounds + 1):
            async for chunk in _phase_critique(self.session, round_n):
                yield chunk

        async for chunk in _phase_status_assessment(self.session):
            yield chunk

        async for chunk in _phase_finalization(self.session):
            yield chunk

    def build_decision_input(self) -> DecisionInput:
        # Filter out disabled participants here — the decision phase
        # always speaks about the *active* roster, not whoever started
        # the chat. (Auto-disable in Phase 1/2 means some original
        # participants may have been removed.)
        actives = [p for p in self.session.participants if p.enabled]
        return DecisionInput(
            question=self.session.question,
            participants=actives,
            transcript_messages=list(self.session.messages),
            finalized_positions=dict(self.session.final_opinions),
        )