Multi-agent discussion framework: - Custom/AutoGen/CrewAI backends - Multi-provider API (role→model binding) - Tools system (files, web, commands) - Markdown scenarios with YAML frontmatter - Workspace init (meeting-room init) - Session save (--save)
172 lines
5.8 KiB
Python
172 lines
5.8 KiB
Python
"""Custom backend — round-robin agent discussion with tool support."""
|
||
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
import yaml
|
||
|
||
from meeting_room.api_client import chat, chat_stream, init_providers
|
||
from meeting_room.scenario_loader import load_scenario
|
||
from meeting_room.tools import set_workdir, get_tool_schemas, execute_tool
|
||
|
||
# ANSI colors
|
||
COLORS = {
|
||
"moderator": "\033[96m",
|
||
"skeptic": "\033[91m",
|
||
"idea_generator": "\033[92m",
|
||
"analyst": "\033[93m",
|
||
}
|
||
TOOL_COLOR = "\033[90m"
|
||
RESET = "\033[0m"
|
||
BOLD = "\033[1m"
|
||
DIM = "\033[2m"
|
||
|
||
MAX_TOOL_ITERATIONS = 5
|
||
|
||
|
||
def _format_conversation(conversation: list[dict]) -> str:
|
||
return "\n\n".join(f"[{msg['name']}]: {msg['content']}" for msg in conversation)
|
||
|
||
|
||
def _run_agent_turn(role_config, problem, conversation, is_first_message):
|
||
"""Run one agent turn with tool support. Returns final text response."""
|
||
system_msg = {"role": "system", "content": role_config["system_prompt"].strip()}
|
||
|
||
if is_first_message:
|
||
context_msg = {
|
||
"role": "user",
|
||
"content": (
|
||
f"Проблема для обсуждения:\n{problem}\n\n"
|
||
"Обсуди её с другими участниками. Выскажи свою позицию. "
|
||
"При необходимости используй инструменты."
|
||
),
|
||
}
|
||
else:
|
||
context_msg = {
|
||
"role": "user",
|
||
"content": (
|
||
f"Вот что уже обсудили:\n\n{_format_conversation(conversation)}\n\n"
|
||
"Продолжи обсуждение. Ответь на аргументы, предложи своё. "
|
||
"При необходимости используй инструменты."
|
||
),
|
||
}
|
||
|
||
messages = [system_msg, context_msg]
|
||
tool_schemas = get_tool_schemas(role_config.get("tools", "none"))
|
||
has_tools = len(tool_schemas) > 0
|
||
|
||
for _ in range(MAX_TOOL_ITERATIONS):
|
||
if has_tools:
|
||
result = chat(
|
||
provider=role_config["provider"],
|
||
model=role_config["model"],
|
||
messages=messages,
|
||
temperature=role_config.get("temperature", 0.7),
|
||
tools=tool_schemas,
|
||
)
|
||
else:
|
||
content = chat_stream(
|
||
provider=role_config["provider"],
|
||
model=role_config["model"],
|
||
messages=messages,
|
||
temperature=role_config.get("temperature", 0.7),
|
||
)
|
||
return content
|
||
|
||
if result["tool_calls"]:
|
||
for tc in result["tool_calls"]:
|
||
print(f"\n{TOOL_COLOR} -> {tc['name']}({tc['arguments']}){RESET}")
|
||
|
||
messages.append({
|
||
"role": "assistant",
|
||
"content": result["content"] or None,
|
||
"tool_calls": [
|
||
{
|
||
"id": tc["id"],
|
||
"type": "function",
|
||
"function": {"name": tc["name"], "arguments": tc["arguments"]},
|
||
}
|
||
for tc in result["tool_calls"]
|
||
],
|
||
})
|
||
|
||
for tc in result["tool_calls"]:
|
||
try:
|
||
args = json.loads(tc["arguments"]) if isinstance(tc["arguments"], str) else tc["arguments"]
|
||
except json.JSONDecodeError:
|
||
args = {}
|
||
tool_result = execute_tool(tc["name"], args)
|
||
preview = tool_result[:200].replace("\n", " ")
|
||
print(f"{TOOL_COLOR} <- {preview}{'...' if len(tool_result) > 200 else ''}{RESET}")
|
||
messages.append({
|
||
"role": "tool",
|
||
"tool_call_id": tc["id"],
|
||
"content": tool_result,
|
||
})
|
||
continue
|
||
|
||
if result["content"]:
|
||
print(result["content"])
|
||
return result["content"]
|
||
|
||
return result.get("content", "")
|
||
|
||
|
||
def run(scenario_path: str, config: dict):
|
||
init_providers(config)
|
||
scenario = load_scenario(scenario_path)
|
||
roles_config = config["roles"]
|
||
|
||
problem = scenario["problem"]
|
||
participants = scenario.get("participants", list(roles_config.keys()))
|
||
max_rounds = scenario.get("max_rounds", config["defaults"]["max_rounds"])
|
||
|
||
set_workdir(config["defaults"].get("workdir", "."))
|
||
|
||
conversation = []
|
||
|
||
print(f"\n{BOLD}{'='*60}")
|
||
print(f" MEETING ROOM — {scenario['name']}")
|
||
print(f"{'='*60}{RESET}\n")
|
||
print(f"{BOLD}Problem:{RESET}")
|
||
for line in problem.split("\n")[:5]:
|
||
print(f" {line}")
|
||
if problem.count("\n") > 5:
|
||
print(" ...")
|
||
print()
|
||
print(f"{BOLD}Participants:{RESET}")
|
||
for rid in participants:
|
||
r = roles_config[rid]
|
||
print(f" {r['name']} — {r['provider']}/{r['model']} [tools: {r.get('tools', 'none')}]")
|
||
print(f"\n{BOLD}Rounds:{RESET} {max_rounds}")
|
||
print(f"{DIM}{'─'*60}{RESET}\n")
|
||
|
||
for round_num in range(1, max_rounds + 1):
|
||
print(f"{BOLD}[Round {round_num}/{max_rounds}]{RESET}\n")
|
||
|
||
for role_id in participants:
|
||
role = roles_config[role_id]
|
||
color = COLORS.get(role_id, "")
|
||
name = role["name"]
|
||
is_first = len(conversation) == 0
|
||
|
||
print(f"{color}{BOLD} {name}:{RESET}", end=" ")
|
||
|
||
try:
|
||
content = _run_agent_turn(role, problem, conversation, is_first)
|
||
except Exception as e:
|
||
content = f"[Error: {e}]"
|
||
print(content)
|
||
|
||
conversation.append({"role_id": role_id, "name": name, "content": content})
|
||
print()
|
||
|
||
print(f"{DIM}{'─'*60}{RESET}\n")
|
||
|
||
print(f"\n{BOLD}{'='*60}")
|
||
print(f" DISCUSSION COMPLETE — {len(conversation)} messages")
|
||
print(f"{'='*60}{RESET}\n")
|
||
|
||
return conversation
|