249 lines
13 KiB
Python
249 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
稷下学宫AI辩论系统主入口
|
||
提供命令行界面来运行不同的辩论模式
|
||
"""
|
||
|
||
import argparse
|
||
import asyncio
|
||
import sys
|
||
import os
|
||
from typing import Dict, Any, List, Tuple
|
||
|
||
# 将 src 目录添加到 Python 路径,以便能正确导入模块
|
||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, os.path.join(project_root, 'src'))
|
||
|
||
from config.settings import validate_config, get_database_config
|
||
from google.adk import Agent, Runner
|
||
from google.adk.sessions import InMemorySessionService, Session
|
||
from google.genai import types
|
||
import pymongo
|
||
from datetime import datetime
|
||
|
||
|
||
def check_environment(mode: str = "hybrid"):
|
||
"""检查并验证运行环境"""
|
||
print("🔧 检查运行环境...")
|
||
if not validate_config(mode=mode):
|
||
print("❌ 环境配置验证失败")
|
||
return False
|
||
print("✅ 环境检查通过")
|
||
return True
|
||
|
||
|
||
async def _get_llm_reply(runner: Runner, prompt: str) -> str:
|
||
"""一个辅助函数,用于调用Runner并获取纯文本回复,同时流式输出到控制台"""
|
||
# 每个调用创建一个新的会话
|
||
session = await runner.session_service.create_session(state={}, app_name=runner.app_name, user_id="debate_user")
|
||
|
||
content = types.Content(role='user', parts=[types.Part(text=prompt)])
|
||
response = runner.run_async(
|
||
user_id=session.user_id,
|
||
session_id=session.id,
|
||
new_message=content
|
||
)
|
||
reply = ""
|
||
async for event in response:
|
||
chunk = ""
|
||
if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'):
|
||
for part in event.content.parts:
|
||
if hasattr(part, 'text') and part.text:
|
||
chunk = str(part.text)
|
||
elif hasattr(event, 'text') and event.text:
|
||
chunk = str(event.text)
|
||
|
||
if chunk:
|
||
print(chunk, end="", flush=True)
|
||
reply += chunk
|
||
|
||
return reply.strip()
|
||
|
||
|
||
async def run_adk_turn_based_debate(topic: str, rounds: int = 2):
|
||
"""运行由太上老君主持的,基于八卦对立和顺序的辩论"""
|
||
try:
|
||
print(f"🚀 启动ADK八仙论道 (太上老君主持)...")
|
||
print(f"📋 辩论主题: {topic}")
|
||
print(f"🔄 辩论总轮数: {rounds}")
|
||
|
||
# 1. 初始化记忆银行
|
||
print("🧠 初始化记忆银行...")
|
||
from src.jixia.memory.factory import get_memory_backend
|
||
memory_bank = get_memory_backend()
|
||
print("✅ 记忆银行准备就绪。")
|
||
|
||
character_configs = {
|
||
"太上老君": {"name": "太上老君", "model": "gemini-2.5-flash", "instruction": "你是太上老君,天道化身,辩论的主持人。你的言辞沉稳、公正、充满智慧。你的任务是:1. 对辩论主题进行开场介绍。2. 在每轮或每场对决前进行引导。3. 在辩论结束后,对所有观点进行全面、客观的总结。保持中立,不偏袒任何一方。"},
|
||
"吕洞宾": {"name": "吕洞宾", "model": "gemini-2.5-flash", "instruction": "你是吕洞宾(乾卦),男性代表,善于理性分析,逻辑性强,推理严密。"},
|
||
"何仙姑": {"name": "何仙姑", "model": "gemini-2.5-flash", "instruction": "你是何仙姑(坤卦),女性代表,注重平衡与和谐,善于创新思维。"},
|
||
"张果老": {"name": "张果老", "model": "gemini-2.5-flash", "instruction": "你是张果老(兑卦),老者代表,具传统智慧,发言厚重沉稳,经验导向。"},
|
||
"韩湘子": {"name": "韩湘子", "model": "gemini-2.5-flash", "instruction": "你是韩湘子(艮卦),少年代表,具创新思维,发言活泼灵动,具前瞻性。"},
|
||
"汉钟离": {"name": "汉钟离", "model": "gemini-2.5-flash", "instruction": "你是汉钟离(离卦),富者代表,有权威意识,发言威严庄重,逻辑清晰。"},
|
||
"蓝采和": {"name": "蓝采和", "model": "gemini-2.5-flash", "instruction": "你是蓝采和(坎卦),贫者代表,关注公平,发言平易近人。"},
|
||
"曹国舅": {"name": "曹国舅", "model": "gemini-2.5-flash", "instruction": "你是曹国舅(震卦),贵者代表,具商业思维,发言精明务实,效率优先。"},
|
||
"铁拐李": {"name": "铁拐李", "model": "gemini-2.5-flash", "instruction": "你是铁拐李(巽卦),贱者代表,具草根智慧,发言朴实直接,实用至上。"}
|
||
}
|
||
|
||
# 为每个Runner创建独立的SessionService
|
||
runners: Dict[str, Runner] = {
|
||
name: Runner(
|
||
app_name="稷下学宫八仙论道系统",
|
||
agent=Agent(name=config["name"], model=config["model"], instruction=config["instruction"]),
|
||
session_service=InMemorySessionService()
|
||
) for name, config in character_configs.items()
|
||
}
|
||
host_runner = runners["太上老君"]
|
||
debate_history = []
|
||
|
||
print("\n" + "="*20 + " 辩论开始 " + "="*20)
|
||
print(f"\n👑 太上老君: ", end="", flush=True)
|
||
opening_prompt = f"请为本次关于“{topic}”的辩论,发表一段公正、深刻的开场白,并宣布辩论开始。"
|
||
opening_statement = await _get_llm_reply(host_runner, opening_prompt)
|
||
print() # Newline after streaming
|
||
|
||
# --- 第一轮:核心对立辩论 ---
|
||
if rounds >= 1:
|
||
print(f"\n👑 太上老君: ", end="", flush=True)
|
||
round1_intro = await _get_llm_reply(host_runner, "请为第一轮核心对立辩论进行引导。")
|
||
print() # Newline after streaming
|
||
|
||
duel_pairs: List[Tuple[str, str, str]] = [
|
||
("乾坤对立 (男女)", "吕洞宾", "何仙姑"),
|
||
("兑艮对立 (老少)", "张果老", "韩湘子"),
|
||
("离坎对立 (富贫)", "汉钟离", "蓝采和"),
|
||
("震巽对立 (贵贱)", "曹国舅", "铁拐李")
|
||
]
|
||
|
||
for title, p1, p2 in duel_pairs:
|
||
print(f"\n--- {title} ---")
|
||
print(f"👑 太上老君: ", end="", flush=True)
|
||
duel_intro = await _get_llm_reply(host_runner, f"现在开始“{title}”的对决,请{p1}和{p2}准备。")
|
||
print() # Newline after streaming
|
||
|
||
print(f"🗣️ {p1}: ", end="", flush=True)
|
||
s1 = await _get_llm_reply(runners[p1], f"主题:{topic}。作为开场,请从你的角度阐述观点。")
|
||
print(); debate_history.append(f"{p1}: {s1}")
|
||
await memory_bank.add_memory(agent_name=p1, content=s1, memory_type="statement", debate_topic=topic)
|
||
|
||
print(f"🗣️ {p2}: ", end="", flush=True)
|
||
s2 = await _get_llm_reply(runners[p2], f"主题:{topic}。对于刚才{p1}的观点“{s1[:50]}...”,请进行回应。")
|
||
print(); debate_history.append(f"{p2}: {s2}")
|
||
await memory_bank.add_memory(agent_name=p2, content=s2, memory_type="statement", debate_topic=topic)
|
||
|
||
print(f"🗣️ {p1}: ", end="", flush=True)
|
||
s3 = await _get_llm_reply(runners[p1], f"主题:{topic}。对于{p2}的回应“{s2[:50]}...”,请进行反驳。")
|
||
print(); debate_history.append(f"{p1}: {s3}")
|
||
await memory_bank.add_memory(agent_name=p1, content=s3, memory_type="statement", debate_topic=topic)
|
||
|
||
print(f"🗣️ {p2}: ", end="", flush=True)
|
||
s4 = await _get_llm_reply(runners[p2], f"主题:{topic}。针对{p1}的反驳“{s3[:50]}...”,请为本场对决做总结。")
|
||
print(); debate_history.append(f"{p2}: {s4}")
|
||
await memory_bank.add_memory(agent_name=p2, content=s4, memory_type="statement", debate_topic=topic)
|
||
await asyncio.sleep(1)
|
||
|
||
# --- 第二轮:先天八卦顺序发言 (集成记忆银行) ---
|
||
if rounds >= 2:
|
||
print(f"\n👑 太上老君: ", end="", flush=True)
|
||
round2_intro = await _get_llm_reply(host_runner, "请为第二轮,也就是结合场上观点的综合发言,进行引导。")
|
||
print() # Newline after streaming
|
||
|
||
baxi_sequence = ["吕洞宾", "张果老", "汉钟离", "曹国舅", "铁拐李", "蓝采和", "韩湘子", "何仙姑"]
|
||
for name in baxi_sequence:
|
||
print(f"\n--- {name}的回合 ---")
|
||
context = await memory_bank.get_agent_context(name, topic)
|
||
prompt = f"这是你关于“{topic}”的记忆上下文,请参考并对其他人的观点进行回应:\n{context}\n\n现在请从你的角色特点出发,继续发表你的看法。"
|
||
|
||
print(f"🗣️ {name}: ", end="", flush=True)
|
||
reply = await _get_llm_reply(runners[name], prompt)
|
||
print(); debate_history.append(f"{name}: {reply}")
|
||
await memory_bank.add_memory(agent_name=name, content=reply, memory_type="statement", debate_topic=topic)
|
||
await asyncio.sleep(1)
|
||
|
||
print("\n" + "="*20 + " 辩论结束 " + "="*20)
|
||
|
||
# 4. 保存辩论会话到记忆银行
|
||
print("\n💾 正在保存辩论会话记录到记忆银行...")
|
||
await memory_bank.save_debate_session(
|
||
debate_topic=topic,
|
||
participants=[name for name in character_configs.keys() if name != "太上老君"],
|
||
conversation_history=[{"agent": h.split(": ")[0], "content": ": ".join(h.split(": ")[1:])} for h in debate_history if ": " in h],
|
||
outcomes={}
|
||
)
|
||
print("✅ 辩论会话已保存到记忆银行。")
|
||
|
||
# 5. 保存辩论过程资产到MongoDB
|
||
db_config = get_database_config()
|
||
if db_config.get("mongodb_url"):
|
||
print("\n💾 正在保存辩论过程资产到 MongoDB...")
|
||
try:
|
||
client = pymongo.MongoClient(db_config["mongodb_url"])
|
||
db = client.get_database("jixia_academy")
|
||
collection = db.get_collection("debates")
|
||
|
||
summary_prompt = f"辩论已结束。以下是完整的辩论记录:\n\n{' '.join(debate_history)}\n\n请对本次辩论进行全面、公正、深刻的总结。"
|
||
print(f"\n👑 太上老君: ", end="", flush=True)
|
||
summary = await _get_llm_reply(host_runner, summary_prompt)
|
||
print() # Newline after streaming
|
||
|
||
debate_document = {
|
||
"topic": topic,
|
||
"rounds": rounds,
|
||
"timestamp": datetime.utcnow(),
|
||
"participants": [name for name in character_configs.keys() if name != "太上老君"],
|
||
"conversation": [{"agent": h.split(": ")[0], "content": ": ".join(h.split(": ")[1:])} for h in debate_history if ": " in h],
|
||
"summary": summary
|
||
}
|
||
|
||
collection.insert_one(debate_document)
|
||
print("✅ 辩论过程资产已成功保存到 MongoDB。")
|
||
client.close()
|
||
except Exception as e:
|
||
print(f"❌ 保存到 MongoDB 失败: {e}")
|
||
else:
|
||
print("⚠️ 未配置 MONGODB_URL,跳过保存到 MongoDB。")
|
||
print(f"\n👑 太上老君: ", end="", flush=True)
|
||
summary_prompt = f"辩论已结束。以下是完整的辩论记录:\n\n{' '.join(debate_history)}\n\n请对本次辩论进行全面、公正、深刻的总结。"
|
||
summary = await _get_llm_reply(host_runner, summary_prompt)
|
||
print() # Newline after streaming
|
||
|
||
|
||
for runner in runners.values(): await runner.close()
|
||
print(f"\n🎉 ADK八仙轮流辩论完成!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 运行ADK八仙轮流辩论失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
|
||
|
||
|
||
|
||
async def main_async(args):
|
||
if not check_environment(mode="google_adk"): return 1
|
||
await run_adk_turn_based_debate(args.topic, args.rounds)
|
||
return 0
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="稷下学宫AI辩论系统 (ADK版)")
|
||
parser.add_argument("--topic", "-t", default="AI是否应该拥有创造力?", help="辩论主题")
|
||
parser.add_argument("--rounds", "-r", type=int, default=2, choices=[1, 2], help="辩论轮数 (1: 核心对立, 2: 对立+顺序发言)")
|
||
args = parser.parse_args()
|
||
|
||
try:
|
||
sys.exit(asyncio.run(main_async(args)))
|
||
except KeyboardInterrupt:
|
||
print("\n\n👋 用户中断,退出程序")
|
||
sys.exit(0)
|
||
except Exception as e:
|
||
print(f"\n\n💥 程序运行出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
sys.exit(1)
|
||
|
||
if __name__ == "__main__":
|
||
main() |