feat(06-01): add consensus generation to orchestrator and service
- Add CONSENSUS_PROMPT constant for AI consensus analysis - Add generate_consensus() function that builds context and calls AI - Add save_consensus() and get_consensus() to discussion service - Import json module and Consensus model Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ddb0de0757
commit
8242de5289
2 changed files with 143 additions and 0 deletions
|
|
@ -5,6 +5,7 @@ across multiple models, building context, and managing discussion flow.
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
|
||||
from moai.core.ai_client import get_ai_client
|
||||
|
|
@ -25,6 +26,33 @@ Guidelines:
|
|||
- Focus on practical, actionable insights
|
||||
- If you reach agreement with others, state it clearly"""
|
||||
|
||||
# Prompt for generating consensus summary
|
||||
CONSENSUS_PROMPT = """Analyze the discussion above and provide a JSON summary with:
|
||||
1. "agreements" - A list of strings, each being a point all participants agreed on
|
||||
2. "disagreements" - A list of objects, each with:
|
||||
- "topic": The topic of disagreement
|
||||
- "positions": An object mapping model names to their positions
|
||||
|
||||
Only include items where there was clear agreement or disagreement.
|
||||
Respond with ONLY valid JSON, no markdown formatting or explanation.
|
||||
|
||||
Example format:
|
||||
{
|
||||
"agreements": [
|
||||
"Python is a great language for beginners",
|
||||
"Documentation is important"
|
||||
],
|
||||
"disagreements": [
|
||||
{
|
||||
"topic": "Best web framework",
|
||||
"positions": {
|
||||
"claude": "FastAPI for its modern async support",
|
||||
"gpt": "Django for its batteries-included approach"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
async def query_models_parallel(
|
||||
models: list[str],
|
||||
|
|
@ -227,3 +255,71 @@ async def run_discussion_round(
|
|||
context_messages.append({"role": "user", "content": formatted})
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
async def generate_consensus(discussion: Discussion, model: str = "claude") -> dict:
|
||||
"""Generate a consensus summary from a discussion.
|
||||
|
||||
Analyzes all rounds and messages in the discussion to identify
|
||||
agreements and disagreements among the participating AI models.
|
||||
|
||||
Args:
|
||||
discussion: Discussion object with eager-loaded rounds and messages.
|
||||
model: Model short name to use for generating the consensus (default: "claude").
|
||||
|
||||
Returns:
|
||||
Dict with "agreements" (list of strings) and "disagreements"
|
||||
(list of {topic, positions} objects).
|
||||
"""
|
||||
client = get_ai_client()
|
||||
|
||||
# Build context from the discussion
|
||||
context_messages = build_context(discussion)
|
||||
|
||||
# Add the consensus prompt as the final user message
|
||||
context_messages.append({"role": "user", "content": CONSENSUS_PROMPT})
|
||||
|
||||
# System prompt for consensus generation
|
||||
system_prompt = """You are an impartial analyst summarizing a multi-model AI discussion.
|
||||
Your task is to identify clear agreements and disagreements from the conversation.
|
||||
Be precise and only include items where there was genuine consensus or difference of opinion.
|
||||
Respond with valid JSON only."""
|
||||
|
||||
try:
|
||||
response = await client.complete(
|
||||
model=model,
|
||||
messages=context_messages,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
logger.info("Consensus generated successfully by %s", model)
|
||||
|
||||
# Parse JSON response, handling potential markdown code blocks
|
||||
json_str = response.strip()
|
||||
if json_str.startswith("```"):
|
||||
# Strip markdown code block
|
||||
lines = json_str.split("\n")
|
||||
json_str = "\n".join(lines[1:-1]) if lines[-1] == "```" else "\n".join(lines[1:])
|
||||
json_str = json_str.strip()
|
||||
|
||||
result = json.loads(json_str)
|
||||
|
||||
# Ensure required keys exist with defaults
|
||||
return {
|
||||
"agreements": result.get("agreements", []),
|
||||
"disagreements": result.get("disagreements", []),
|
||||
}
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("Failed to parse consensus JSON: %s", e)
|
||||
return {
|
||||
"agreements": [],
|
||||
"disagreements": [],
|
||||
"error": f"Failed to parse AI response: {e}",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Consensus generation failed: %s", e)
|
||||
return {
|
||||
"agreements": [],
|
||||
"disagreements": [],
|
||||
"error": str(e),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from sqlalchemy.orm import selectinload
|
|||
|
||||
from moai.core.database import get_session
|
||||
from moai.core.models import (
|
||||
Consensus,
|
||||
Discussion,
|
||||
DiscussionStatus,
|
||||
DiscussionType,
|
||||
|
|
@ -217,3 +218,49 @@ async def get_round_messages(round_id: str) -> list[Message]:
|
|||
select(Message).where(Message.round_id == round_id).order_by(Message.timestamp)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def save_consensus(
|
||||
discussion_id: str,
|
||||
agreements: list,
|
||||
disagreements: list,
|
||||
generated_by: str,
|
||||
) -> Consensus:
|
||||
"""Save a consensus summary for a discussion.
|
||||
|
||||
Args:
|
||||
discussion_id: The discussion's UUID.
|
||||
agreements: List of agreement strings.
|
||||
disagreements: List of disagreement dicts with topic and positions.
|
||||
generated_by: The model that generated the consensus.
|
||||
|
||||
Returns:
|
||||
The created Consensus object.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
consensus = Consensus(
|
||||
discussion_id=discussion_id,
|
||||
agreements=agreements,
|
||||
disagreements=disagreements,
|
||||
generated_by=generated_by,
|
||||
)
|
||||
session.add(consensus)
|
||||
await session.flush()
|
||||
await session.refresh(consensus)
|
||||
return consensus
|
||||
|
||||
|
||||
async def get_consensus(discussion_id: str) -> Consensus | None:
|
||||
"""Get the consensus for a discussion if it exists.
|
||||
|
||||
Args:
|
||||
discussion_id: The discussion's UUID.
|
||||
|
||||
Returns:
|
||||
The Consensus object if found, None otherwise.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(
|
||||
select(Consensus).where(Consensus.discussion_id == discussion_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue