diff --git a/.gitignore b/.gitignore index 3edf53b..a7ba070 100644 --- a/.gitignore +++ b/.gitignore @@ -44,11 +44,7 @@ logs/ *.db *.sqlite3 -# Node.js -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* + # OS .DS_Store diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 85b3c42..61895cb 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -10,7 +10,7 @@ ├── ai_collaboration_demo.py # AI协作演示 ├── install.sh # 一键安装脚本 ├── requirements.txt # Python依赖 -├── package.json # Node.js依赖 + ├── pytest.ini # 测试配置 ├── .gitignore # Git忽略规则 ├── .gitguardian.yaml # 安全配置 @@ -42,7 +42,7 @@ - **ai_collaboration_demo.py**: AI协作演示脚本 - **install.sh**: 一键安装所有依赖和环境 - **requirements.txt**: Python依赖清单 -- **package.json**: Node.js依赖和脚本 + ### 🏗️ 核心系统目录 - **agents/**: AI代理身份管理系统 diff --git a/examples/ai_collaboration_demo.py b/examples/ai_collaboration_demo.py new file mode 100644 index 0000000..bb357f9 --- /dev/null +++ b/examples/ai_collaboration_demo.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +四AI团队协作启动脚本 +快速启动和演示四AI协作系统 +""" + +import asyncio +import json +import logging +from datetime import datetime +from pathlib import Path + +from src.jixia.coordination.ai_team_collaboration import ( + AITeamCollaboration, AIRole, MessageType, CollaborationType, WorkPhase +) + +# 设置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +async def demo_openbb_integration_workflow(): + """演示OpenBB集成的完整工作流""" + print("🚀 启动四AI团队协作系统演示...") + print("=" * 60) + + # 初始化协作系统 + collab = AITeamCollaboration() + + # ========== 阶段1: 项目启动和架构设计 ========== + print("\n📋 阶段1: 项目启动和架构设计") + + # RovoDev发起项目 + await collab.broadcast_message( + sender=AIRole.ROVODEV, + content="""🎯 **OpenBB集成项目正式启动!** + +项目目标:将OpenBB金融数据平台与稷下学宫八仙论道系统深度融合 + +各AI职责分工: +- Qwen: 架构设计师,负责系统架构和技术选型 +- Claude: 核心开发工程师,负责代码实现和API集成 +- Gemini: 测试验证专家,负责功能测试和质量保证 +- RovoDev: 项目整合专家,负责协调管理和文档整合 + +请大家查看各自的工作说明书,开始工作!""", + message_type=MessageType.UPDATE, + channel_id="main_collaboration", + priority=4, + tags=["project_start", "openbb_integration"] + ) + + # Qwen开始架构设计 + await collab.send_message( + sender=AIRole.QWEN, + content="""🏗️ **架构设计开始** + +我已经开始OpenBB集成的架构设计工作,主要包括: + +1. **数据抽象层设计** - 统一OpenBB与八仙系统的数据接口 +2. **八仙数据路由** - 为每个八仙设计专属的数据获取策略 +3. **智能降级机制** - OpenBB不可用时的备选方案 +4. **性能优化架构** - 缓存和负载均衡策略 + +预计完成时间:2天 +需要其他AI配合:Claude提供实现可行性反馈""", + message_type=MessageType.PROPOSAL, + channel_id="architecture_design", + priority=3, + tags=["architecture", "design_start"] + ) + + # Claude响应架构设计 + await collab.send_message( + sender=AIRole.CLAUDE, + content="""💻 **实现可行性评估** + +Qwen的架构设计方向很好!从实现角度我有几个建议: + +1. **延迟导入策略** - 确保OpenBB未安装时系统正常运行 +2. **异步数据处理** - 避免阻塞八仙论道的实时性 +3. **缓存层设计** - 减少重复的API调用 +4. **错误处理机制** - 优雅处理网络和数据异常 + +我可以在架构确定后立即开始实现工作。""", + message_type=MessageType.ANSWER, + channel_id="architecture_design", + receiver=AIRole.QWEN, + priority=3, + tags=["implementation", "feasibility"] + ) + + # Gemini提出测试策略 + await collab.send_message( + sender=AIRole.GEMINI, + content="""🧪 **测试策略制定** + +基于当前的架构讨论,我已经开始制定测试策略: + +1. **单元测试** - 覆盖八仙数据路由和OpenBB集成 +2. **集成测试** - 验证数据流和系统交互 +3. **性能测试** - 确保响应时间和并发处理能力 +4. **文化准确性测试** - 验证八仙特征的准确表达 + +需要架构文档确定后细化具体测试用例。""", + message_type=MessageType.PROPOSAL, + channel_id="testing_validation", + priority=2, + tags=["testing", "strategy"] + ) + + # ========== 工作交接演示 ========== + print("\n🤝 演示工作交接...") + + # 推进到设计完成阶段 + await collab.advance_phase(WorkPhase.IMPLEMENTATION) + + # Qwen向Claude交接 + await collab.handoff_work( + from_ai=AIRole.QWEN, + to_ai=AIRole.CLAUDE, + task_description="基于架构设计实现OpenBB核心引擎", + deliverables=[ + "src/jixia/engines/enhanced_openbb_engine.py", + "src/jixia/adapters/immortal_data_processor.py", + "app/tabs/enhanced_openbb_tab.py" + ], + notes="""重点关注: +1. 八仙数据偏好的准确实现 +2. 智能降级机制的稳定性 +3. 文化特色功能的完整性""" + ) + + # Claude接受任务并开始实现 + await collab.send_message( + sender=AIRole.CLAUDE, + content="""✅ **任务接收确认** + +已接收Qwen的架构设计交接,开始核心引擎实现: + +当前进度: +- [x] 架构文档审查完成 +- [ ] 增强版OpenBB引擎开发中... +- [ ] 八仙数据适配器实现 +- [ ] Streamlit界面优化 + +预计完成时间:3天 +如有技术问题会在代码实现频道讨论。""", + message_type=MessageType.UPDATE, + channel_id="code_implementation", + priority=3, + tags=["implementation", "progress"] + ) + + # ========== 评审请求演示 ========== + print("\n📋 演示评审请求...") + + await collab.request_review( + sender=AIRole.CLAUDE, + content="""📋 **代码评审请求** + +已完成增强版OpenBB引擎的核心实现,请求评审: + +主要特性: +1. ✅ 八仙专属数据路由器 +2. ✅ 智能降级机制 +3. ✅ 异步数据处理 +4. ✅ 完整的错误处理 + +请重点关注: +- 架构一致性 (Qwen) +- 测试覆盖度 (Gemini) +- 文档完整性 (RovoDev)""", + reviewers=[AIRole.QWEN, AIRole.GEMINI, AIRole.ROVODEV], + attachments=["src/jixia/engines/enhanced_openbb_engine.py"] + ) + + # 各AI提供评审反馈 + await collab.send_message( + sender=AIRole.QWEN, + content="""🏗️ **架构评审反馈** + +Claude的实现与架构设计高度一致!几个优化建议: + +✅ 优点: +- 八仙数据路由实现准确 +- 降级机制设计合理 +- 性能考虑周全 + +🔧 建议优化: +- 可以增加更多的数据源provider +- 缓存策略可以更加智能化 +- 考虑添加实时数据流支持 + +总体评分:9/10,建议通过!""", + message_type=MessageType.REVIEW, + channel_id="cross_review", + receiver=AIRole.CLAUDE, + priority=3, + tags=["review", "architecture", "approved"] + ) + + await collab.send_message( + sender=AIRole.GEMINI, + content="""🧪 **测试评审反馈** + +从测试角度评估Claude的实现: + +✅ 测试友好性: +- 代码结构清晰,易于测试 +- 依赖注入设计良好 +- 异常处理完善 + +📋 测试建议: +- 需要补充Mock数据用于单元测试 +- 建议增加性能基准测试 +- 八仙特征测试用例需要完善 + +开始准备对应的测试套件!""", + message_type=MessageType.REVIEW, + channel_id="cross_review", + receiver=AIRole.CLAUDE, + priority=3, + tags=["review", "testing", "suggestions"] + ) + + # ========== 紧急问题演示 ========== + print("\n🚨 演示紧急问题处理...") + + await collab.escalate_issue( + reporter=AIRole.CLAUDE, + issue_description="""OpenBB v4.3.0版本兼容性问题: + +发现新版本OpenBB的API接口有重大变更,影响数据获取功能。 +当前解决方案: +1. 临时锁定到v4.1.0版本 +2. 准备适配新版本的兼容层 + +需要团队讨论优先级和解决方案。""", + severity="high" + ) + + # RovoDev协调解决 + await collab.send_message( + sender=AIRole.ROVODEV, + content="""🎯 **紧急问题协调** + +已接收Claude的问题报告,协调解决方案: + +📋 行动计划: +1. **短期方案** (Claude负责): 锁定OpenBB v4.1.0版本,确保现有功能稳定 +2. **中期方案** (Qwen设计): 设计兼容层架构,支持多版本OpenBB +3. **长期方案** (团队): 建立版本兼容性测试机制 + +**时间安排**: +- 今日内完成版本锁定 +- 3天内完成兼容层设计 +- 1周内完成新版本适配 + +请各AI确认该计划。""", + message_type=MessageType.DECISION, + channel_id="emergency_coordination", + priority=4, + tags=["emergency", "coordination", "action_plan"] + ) + + # ========== 项目整合演示 ========== + print("\n📚 演示项目整合...") + + # 推进到整合阶段 + await collab.advance_phase(WorkPhase.INTEGRATION) + + await collab.send_message( + sender=AIRole.ROVODEV, + content="""📚 **项目整合开始** + +开始整合所有AI的工作成果: + +🏗️ **Qwen交付物**: +- ✅ 系统架构设计文档 +- ✅ 数据抽象层接口规范 +- ✅ 性能优化策略 + +💻 **Claude交付物**: +- ✅ 增强版OpenBB引擎 +- ✅ 八仙数据适配器 +- ✅ Streamlit界面优化 + +🧪 **Gemini交付物**: +- ✅ 完整测试套件 +- ✅ 性能基准测试 +- ✅ 质量保证报告 + +📋 **整合任务**: +- [ ] 统一文档格式 +- [ ] 集成测试验证 +- [ ] 用户指南编写 +- [ ] 最终质量检查 + +预计整合完成时间:2天""", + message_type=MessageType.UPDATE, + channel_id="project_integration", + priority=4, + tags=["integration", "deliverables", "timeline"] + ) + + # ========== 生成工作报告 ========== + print("\n📊 生成协作统计...") + + # 获取各AI的工作仪表板 + for ai_role in AIRole: + dashboard = collab.get_ai_dashboard(ai_role) + print(f"\n🤖 {ai_role.value} 工作统计:") + print(f" 状态: {dashboard['status']['status']}") + print(f" 当前任务: {dashboard['status']['current_task']}") + print(f" 待处理任务: {len(dashboard['pending_tasks'])}个") + print(f" 协作得分: {dashboard['collaboration_stats']['collaboration_score']}") + print(f" 活跃频道: {len(dashboard['active_channels'])}个") + + # 获取频道摘要 + print(f"\n📢 频道活跃度统计:") + for channel_id, channel in collab.channels.items(): + summary = collab.get_channel_summary(channel_id) + print(f" {summary['channel_name']}: {summary['total_messages']}条消息") + + print("\n🎉 四AI团队协作演示完成!") + print("=" * 60) + print("系统功能演示:") + print("✅ 多频道协作通信") + print("✅ 工作流程管理") + print("✅ 任务交接机制") + print("✅ 评审协作流程") + print("✅ 紧急问题处理") + print("✅ 项目整合管理") + print("✅ 实时状态监控") + print("\n🚀 可以启动Web界面进行可视化管理!") + +async def start_collaboration_system(): + """启动协作系统的交互式版本""" + import sys + + collab = AITeamCollaboration() + + print("🤖 四AI团队协作系统已启动") + print("可用命令:") + print(" send - 发送消息") + print(" status - 查看状态") + print(" channels - 查看频道") + print(" dashboard - 查看AI仪表板") + print(" handoff - 工作交接") + print(" quit - 退出") + + # 检查是否在交互式环境中 + if not sys.stdin.isatty(): + print("\n⚠️ 检测到非交互式环境,运行快速演示模式") + print("\n📊 当前系统状态:") + print(f"当前阶段: {collab.current_phase.value}") + for ai_role, status in collab.ai_status.items(): + print(f"{ai_role.value}: {status['status']} - {status['current_task']}") + + print("\n📢 频道列表:") + for channel_id, channel in collab.channels.items(): + print(f"{channel.name} ({channel.channel_type.value}): {len(channel.message_history)}条消息") + + print("\n💡 要体验完整交互功能,请在真正的终端中运行:") + print(" .venv/bin/python3 ai_collaboration_demo.py interactive") + return + + while True: + try: + command = input("\n> ").strip().lower() + + if command == "quit": + break + elif command == "status": + print(f"当前阶段: {collab.current_phase.value}") + for ai_role, status in collab.ai_status.items(): + print(f"{ai_role.value}: {status['status']} - {status['current_task']}") + + elif command == "channels": + for channel_id, channel in collab.channels.items(): + print(f"{channel.name} ({channel.channel_type.value}): {len(channel.message_history)}条消息") + + elif command.startswith("dashboard"): + parts = command.split() + if len(parts) > 1: + try: + ai_role = AIRole(parts[1].title()) + dashboard = collab.get_ai_dashboard(ai_role) + print(json.dumps(dashboard, indent=2, ensure_ascii=False, default=str)) + except ValueError: + print("无效的AI角色,可选:Qwen, Claude, Gemini, Rovodev") + else: + print("使用方法: dashboard ") + + elif command == "send": + # 简化的消息发送 + try: + sender = input("发送者 (Qwen/Claude/Gemini/Rovodev): ") + content = input("消息内容: ") + channel = input("频道 (main_collaboration/architecture_design/etc): ") + + await collab.send_message( + sender=AIRole(sender), + content=content, + message_type=MessageType.PROPOSAL, + channel_id=channel or "main_collaboration" + ) + print("消息发送成功!") + except EOFError: + print("\n输入被中断") + break + except Exception as e: + print(f"发送失败: {e}") + + else: + print("未知命令") + + except EOFError: + print("\n检测到EOF,退出交互模式") + break + except KeyboardInterrupt: + print("\n检测到中断信号,退出交互模式") + break + except Exception as e: + print(f"错误: {e}") + + print("👋 协作系统已退出") + +if __name__ == "__main__": + import sys + + if len(sys.argv) > 1 and sys.argv[1] == "demo": + # 运行演示 + asyncio.run(demo_openbb_integration_workflow()) + elif len(sys.argv) > 1 and sys.argv[1] == "interactive": + # 交互式模式 + asyncio.run(start_collaboration_system()) + else: + print("四AI团队协作系统") + print("使用方法:") + print(" python ai_collaboration_demo.py demo - 运行完整演示") + print(" python ai_collaboration_demo.py interactive - 交互式模式") + print(" streamlit run app/tabs/ai_collaboration_tab.py - 启动Web界面") \ No newline at end of file diff --git a/outputs/debates/baxian_debate_20250822_103626.json b/examples/debates/outputs/baxian_debate_20250822_103626.json similarity index 100% rename from outputs/debates/baxian_debate_20250822_103626.json rename to examples/debates/outputs/baxian_debate_20250822_103626.json diff --git a/outputs/debates/baxian_debate_20250822_104031.json b/examples/debates/outputs/baxian_debate_20250822_104031.json similarity index 100% rename from outputs/debates/baxian_debate_20250822_104031.json rename to examples/debates/outputs/baxian_debate_20250822_104031.json diff --git a/outputs/debates/baxian_debate_20250822_104751.json b/examples/debates/outputs/baxian_debate_20250822_104751.json similarity index 100% rename from outputs/debates/baxian_debate_20250822_104751.json rename to examples/debates/outputs/baxian_debate_20250822_104751.json diff --git a/outputs/debates/baxian_debate_20250822_110405.json b/examples/debates/outputs/baxian_debate_20250822_110405.json similarity index 100% rename from outputs/debates/baxian_debate_20250822_110405.json rename to examples/debates/outputs/baxian_debate_20250822_110405.json diff --git a/outputs/debates/baxian_debate_20250822_111604.json b/examples/debates/outputs/baxian_debate_20250822_111604.json similarity index 100% rename from outputs/debates/baxian_debate_20250822_111604.json rename to examples/debates/outputs/baxian_debate_20250822_111604.json diff --git a/jixia_academy/PROJECT_STRUCTURE.md b/jixia_academy/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..9171aa3 --- /dev/null +++ b/jixia_academy/PROJECT_STRUCTURE.md @@ -0,0 +1,202 @@ +# 稷下学宫AI辩论系统 - 项目结构 + +## 🏛️ 系统概述 + +**稷下学宫AI辩论系统**是一个融合中国传统文化与现代AI技术的智能辩论平台,以八仙论道为核心,通过记忆银行、ADK引擎和多智能体协作,实现深度投资分析与哲学思辨。 + +## 📁 目录结构 + +``` +jixia_academy/ +├── core/ # 核心系统 +│ ├── debate_system/ # 辩论系统引擎 +│ │ ├── __init__.py +│ │ ├── main.py # 系统主入口 +│ │ ├── agents/ # AI智能体 +│ │ │ ├── __init__.py +│ │ │ ├── memory_enhanced_agent.py +│ │ │ └── baxian/ # 八仙角色定义 +│ │ │ ├── __init__.py +│ │ │ ├── tieguaili.py # 铁拐李 +│ │ │ ├── lvdongbin.py # 吕洞宾 +│ │ │ ├── hexianggu.py # 何仙姑 +│ │ │ ├── zhangguolao.py # 张果老 +│ │ │ ├── lancaihe.py # 蓝采和 +│ │ │ ├── hanzhongli.py # 汉钟离 +│ │ │ ├── hanxiangzi.py # 韩湘子 +│ │ │ ├── caoguojiu.py # 曹国舅 +│ │ │ └── host.py # 太上老君主持人 +│ │ ├── debates/ # 辩论模式实现 +│ │ │ ├── __init__.py +│ │ │ ├── adk_memory_debate.py +│ │ │ ├── adk_turn_based_debate.py +│ │ │ └── swarm_debate.py +│ │ └── memory/ # 记忆系统(已迁移至memory_bank) +│ │ └── (legacy files) +│ │ +│ ├── memory_bank/ # 记忆银行系统 +│ │ ├── __init__.py +│ │ ├── base_memory_bank.py +│ │ ├── cloudflare_memory_bank.py +│ │ ├── vertex_memory_bank.py +│ │ ├── factory.py +│ │ └── schemas/ +│ │ ├── __init__.py +│ │ └── memory_models.py +│ │ +│ └── ai_engine/ # AI引擎 +│ ├── __init__.py +│ ├── adk_integration.py +│ ├── gemini_client.py +│ └── openai_client.py +│ +├── agents/ # 智能体系统 +│ ├── baxian/ # 八仙论道系统 +│ │ ├── __init__.py +│ │ ├── baxian_coordinator.py +│ │ ├── debate_flow.py +│ │ └── character_profiles/ +│ │ ├── tieguaili.json +│ │ ├── lvdongbin.json +│ │ └── ... +│ ├── host/ # 主持人系统 +│ │ ├── __init__.py +│ │ ├── taishanglaojun.py +│ │ └── debate_master.py +│ └── observers/ # 观察者和分析器 +│ ├── __init__.py +│ ├── debate_analyzer.py +│ └── performance_tracker.py +│ +├── integrations/ # 外部系统集成 +│ ├── adk/ # Google ADK集成 +│ │ ├── __init__.py +│ │ ├── adk_client.py +│ │ └── adk_config.py +│ ├── openbb/ # OpenBB金融数据集成 +│ │ ├── __init__.py +│ │ ├── openbb_engine.py +│ │ ├── openbb_stock_data.py +│ │ └── providers/ +│ ├── mongodb/ # MongoDB数据库集成 +│ │ ├── __init__.py +│ │ ├── connection.py +│ │ ├── models.py +│ │ └── repositories/ +│ └── cloudflare/ # Cloudflare Workers集成 +│ ├── __init__.py +│ ├── kv_storage.py +│ └── worker_config.py +│ +├── ui/ # 用户界面 +│ ├── web/ # Web界面 +│ │ ├── __init__.py +│ │ ├── index.html +│ │ ├── css/ +│ │ ├── js/ +│ │ └── assets/ +│ ├── cli/ # 命令行界面 +│ │ ├── __init__.py +│ │ ├── cli.py +│ │ └── commands/ +│ └── streamlit/ # Streamlit应用 +│ ├── __init__.py +│ ├── streamlit_app.py +│ ├── components/ +│ └── tabs/ +│ ├── adk_debate_tab.py +│ ├── ai_collaboration_tab.py +│ ├── openbb_tab.py +│ └── tianxia_tab.py +│ +├── config/ # 配置管理 +│ ├── __init__.py +│ ├── settings.py +│ ├── database.py +│ └── logging.py +│ +├── data/ # 数据存储 +│ ├── models/ # 预训练模型 +│ ├── debates/ # 辩论记录 +│ ├── logs/ # 系统日志 +│ └── cache/ # 缓存数据 +│ +├── tests/ # 测试套件 +│ ├── __init__.py +│ ├── unit/ +│ ├── integration/ +│ └── e2e/ +│ +├── docs/ # 文档系统 +│ ├── api/ # API文档 +│ ├── user/ # 用户指南 +│ ├── architecture/ # 架构文档 +│ └── examples/ # 使用示例 +│ +├── examples/ # 示例代码 +│ ├── debates/ +│ │ ├── adk_debate_example.py +│ │ ├── baxian_adk_gemini_debate.py +│ │ └── swarm_debate_example.py +│ ├── integrations/ +│ │ ├── openbb_integration_demo.py +│ │ └── memory_bank_demo.py +│ └── api_usage/ +│ └── rest_api_example.py +│ +├── scripts/ # 部署和运维脚本 +│ ├── install.sh +│ ├── setup.py +│ ├── start_debate.sh +│ └── deploy/ +│ +├── requirements.txt +├── pyproject.toml +├── README.md +└── LICENSE +``` + +## 🎯 核心模块说明 + +### 1. 辩论系统引擎 (core/debate_system/) +- **八仙论道**: 基于八卦理论的AI辩论系统 +- **记忆增强辩论**: 集成记忆银行的智能辩论 +- **轮流辩论**: ADK驱动的结构化辩论流程 + +### 2. 记忆银行系统 (core/memory_bank/) +- **Cloudflare KV**: 分布式键值存储 +- **Vertex AI**: Google Cloud记忆管理 +- **MongoDB**: 结构化数据存储 +- **记忆工厂**: 统一记忆管理接口 + +### 3. 智能体系统 (agents/) +- **八仙角色**: 铁拐李、吕洞宾、何仙姑等8个AI角色 +- **主持人**: 太上老君辩论主持 +- **观察者**: 辩论分析和性能监控 + +### 4. 集成系统 (integrations/) +- **Google ADK**: 智能体开发套件 +- **OpenBB**: 金融数据和分析 +- **MongoDB**: 数据持久化 +- **Cloudflare**: 边缘计算和存储 + +## 🚀 快速开始 + +```bash +# 安装依赖 +pip install -r requirements.txt + +# 启动八仙论道 +python -m jixia_academy.core.main --mode baxian --topic "投资策略分析" + +# 启动Streamlit界面 +streamlit run jixia_academy/ui/streamlit/streamlit_app.py +``` + +## 📊 系统特色 + +- **文化传承**: 融合中国传统文化与现代AI +- **智能辩论**: 基于八卦理论的深度思辨 +- **记忆增强**: 持续学习和知识积累 +- **多模态交互**: CLI、Web、Streamlit多种界面 +- **金融集成**: OpenBB专业金融数据分析 \ No newline at end of file diff --git a/jixia_academy/__main__.py b/jixia_academy/__main__.py new file mode 100644 index 0000000..22ef882 --- /dev/null +++ b/jixia_academy/__main__.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫AI辩论系统 - 统一入口 +Jixia Academy AI Debate System - Unified Entry Point + +一个融合中国传统文化与现代AI技术的智能辩论平台 +A fusion of traditional Chinese culture and modern AI technology +""" + +import argparse +import asyncio +import sys +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from jixia_academy.core.debate_system.main import JixiaAcademy + + +def create_parser(): + """创建命令行参数解析器""" + parser = argparse.ArgumentParser( + description="稷下学宫AI辩论系统 - 八仙论道智能平台", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +使用示例: + # 启动八仙论道辩论 + python -m jixia_academy baxian --topic "当前市场投资策略" + + # 启动记忆增强辩论 + python -m jixia_academy memory --topic "科技股投资分析" + + # 启动Streamlit界面 + python -m jixia_academy ui + + # 查看系统状态 + python -m jixia_academy status + """ + ) + + subparsers = parser.add_subparsers(dest='command', help='可用命令') + + # 八仙论道命令 + baxian_parser = subparsers.add_parser('baxian', help='启动八仙论道辩论') + baxian_parser.add_argument('--topic', required=True, help='辩论主题') + baxian_parser.add_argument('--rounds', type=int, default=3, help='辩论轮数') + baxian_parser.add_argument('--participants', nargs='*', + default=["铁拐李", "吕洞宾", "何仙姑", "张果老", + "蓝采和", "汉钟离", "韩湘子", "曹国舅"], + help='参与辩论的八仙') + + # 记忆增强辩论命令 + memory_parser = subparsers.add_parser('memory', help='启动记忆增强辩论') + memory_parser.add_argument('--topic', required=True, help='辩论主题') + memory_parser.add_argument('--engine', choices=['adk', 'swarm'], default='adk', + help='使用的AI引擎') + + # UI界面命令 + ui_parser = subparsers.add_parser('ui', help='启动用户界面') + ui_parser.add_argument('--type', choices=['streamlit', 'web'], default='streamlit', + help='界面类型') + ui_parser.add_argument('--port', type=int, default=8501, help='端口号') + + # 系统状态命令 + status_parser = subparsers.add_parser('status', help='查看系统状态') + + return parser + + +async def run_baxian_debate(args): + """运行八仙论道辩论""" + academy = JixiaAcademy() + await academy.initialize() + + try: + await academy.run_baxian_debate( + topic=args.topic, + rounds=args.rounds, + participants=args.participants + ) + finally: + await academy.close() + + +async def run_memory_debate(args): + """运行记忆增强辩论""" + academy = JixiaAcademy() + await academy.initialize() + + try: + await academy.run_memory_enhanced_debate( + topic=args.topic, + engine=args.engine + ) + finally: + await academy.close() + + +def run_ui(args): + """启动用户界面""" + if args.type == 'streamlit': + import subprocess + cmd = [ + sys.executable, '-m', 'streamlit', 'run', + 'jixia_academy/ui/streamlit/streamlit_app.py', + '--server.port', str(args.port) + ] + subprocess.run(cmd) + elif args.type == 'web': + print("Web界面开发中...") + # TODO: 启动Web服务器 + + +def run_status(): + """显示系统状态""" + from jixia_academy.core.debate_system.main import check_system_status + check_system_status() + + +async def main(): + """主函数""" + parser = create_parser() + + if len(sys.argv) == 1: + parser.print_help() + return + + args = parser.parse_args() + + try: + if args.command == 'baxian': + await run_baxian_debate(args) + elif args.command == 'memory': + await run_memory_debate(args) + elif args.command == 'ui': + run_ui(args) + elif args.command == 'status': + run_status() + else: + parser.print_help() + except KeyboardInterrupt: + print("\n🛑 用户中断") + except Exception as e: + print(f"❌ 错误: {e}") + import traceback + traceback.print_exc() + + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/agents/baxian/adk_debate_example.py b/jixia_academy/agents/baxian/adk_debate_example.py new file mode 100644 index 0000000..03c5c70 --- /dev/null +++ b/jixia_academy/agents/baxian/adk_debate_example.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫 ADK 真实论道系统 +实现铁拐李和吕洞宾的实际对话辩论 +""" + +import os +import asyncio +from google.adk import Agent, Runner +from google.adk.sessions import InMemorySessionService +from google.genai import types +import re +import sys +from contextlib import contextmanager + +def create_debate_agents(): + """创建论道智能体""" + + # 铁拐李 - 逆向思维专家 + tie_guai_li = Agent( + name="铁拐李", + model="gemini-2.5-flash", + instruction="你是铁拐李,八仙中的逆向思维专家。你善于从批判和质疑的角度看问题,总是能发现事物的另一面。你的发言风格直接、犀利,但富有智慧。每次发言控制在100字以内。" + ) + + # 吕洞宾 - 理性分析者 + lu_dong_bin = Agent( + name="吕洞宾", + model="gemini-2.5-flash", + instruction="你是吕洞宾,八仙中的理性分析者。你善于平衡各方观点,用理性和逻辑来分析问题。你的发言风格温和而深刻,总是能找到问题的核心。每次发言控制在100字以内。" + ) + + return tie_guai_li, lu_dong_bin + +async def conduct_debate(): + """进行实际辩论""" + print("🎭 稷下学宫论道开始...") + + # 创建智能体 + tie_guai_li, lu_dong_bin = create_debate_agents() + + print("\n📋 论道主题: 雅江水电站对中印关系的影响") + print("\n🎯 八仙论道,智慧交锋...") + + print("\n🚀 使用真实ADK调用进行论道...") + await real_adk_debate(tie_guai_li, lu_dong_bin) + +@contextmanager +def suppress_stdout(): + """临时抑制stdout输出""" + with open(os.devnull, 'w') as devnull: + old_stdout = sys.stdout + sys.stdout = devnull + try: + yield + finally: + sys.stdout = old_stdout + +def clean_debug_output(text): + """清理ADK输出中的调试信息""" + if not text: + return "" + + # 移除API密钥相关信息 + text = re.sub(r'Both GOOGLE_API_KEY and GEMINI_API_KEY are set\. Using GOOGLE_API_KEY\.', '', text) + + # 移除Event from unknown agent信息 + text = re.sub(r'Event from an unknown agent: [^\n]*\n?', '', text) + + # 移除多余的空白字符 + text = re.sub(r'\n\s*\n', '\n', text) + text = text.strip() + + return text + +async def real_adk_debate(tie_guai_li, lu_dong_bin): + """使用真实ADK进行辩论""" + print("\n🔥 真实ADK论道模式") + + # 设置环境变量来抑制ADK调试输出 + os.environ['GOOGLE_CLOUD_DISABLE_GRPC_LOGS'] = 'true' + os.environ['GRPC_VERBOSITY'] = 'NONE' + os.environ['GRPC_TRACE'] = '' + + # 临时抑制警告和调试信息 + import warnings + warnings.filterwarnings('ignore') + + # 设置日志级别 + import logging + logging.getLogger().setLevel(logging.ERROR) + + # 创建会话服务 + session_service = InMemorySessionService() + + # 创建会话 + session = await session_service.create_session( + state={}, + app_name="稷下学宫论道系统", + user_id="debate_user" + ) + + # 创建Runner实例 + tie_runner = Runner( + app_name="稷下学宫论道系统", + agent=tie_guai_li, + session_service=session_service + ) + + lu_runner = Runner( + app_name="稷下学宫论道系统", + agent=lu_dong_bin, + session_service=session_service + ) + + try: + # 第一轮:铁拐李开场 + print("\n🗣️ 铁拐李发言:") + tie_prompt = "作为逆向思维专家,请从批判角度分析雅江水电站建设对中印关系可能带来的负面影响和潜在风险。请控制在100字以内。" + + tie_content = types.Content(role='user', parts=[types.Part(text=tie_prompt)]) + with suppress_stdout(): + tie_response = tie_runner.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=tie_content + ) + + tie_reply = "" + async for event in tie_response: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + tie_reply += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + tie_reply += text_content + + # 清理并输出铁拐李的回复 + clean_tie_reply = clean_debug_output(tie_reply) + if clean_tie_reply: + print(f" {clean_tie_reply}") + + # 第二轮:吕洞宾回应 + print("\n🗣️ 吕洞宾回应:") + lu_prompt = f"铁拐李提到了雅江水电站的负面影响:'{tie_reply[:50]}...'。作为理性分析者,请从平衡角度回应,既承认风险又指出雅江水电站对中印关系的积极意义。请控制在100字以内。" + + lu_content = types.Content(role='user', parts=[types.Part(text=lu_prompt)]) + with suppress_stdout(): + lu_response = lu_runner.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=lu_content + ) + + lu_reply = "" + async for event in lu_response: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + lu_reply += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + lu_reply += text_content + + # 清理并输出吕洞宾的回复 + clean_lu_reply = clean_debug_output(lu_reply) + if clean_lu_reply: + print(f" {clean_lu_reply}") + + # 第三轮:铁拐李再次发言 + print("\n🗣️ 铁拐李再次发言:") + tie_prompt2 = f"吕洞宾提到了雅江水电站的积极意义:'{lu_reply[:50]}...'。请从逆向思维角度,对这些所谓的积极影响进行质疑和反思。请控制在100字以内。" + + tie_content2 = types.Content(role='user', parts=[types.Part(text=tie_prompt2)]) + with suppress_stdout(): + tie_response2 = tie_runner.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=tie_content2 + ) + + tie_reply2 = "" + async for event in tie_response2: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + tie_reply2 += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + tie_reply2 += text_content + + # 清理并输出铁拐李的第二次回复 + clean_tie_reply2 = clean_debug_output(tie_reply2) + if clean_tie_reply2: + print(f" {clean_tie_reply2}") + + print("\n🎉 真实ADK论道完成!") + print("\n📝 智慧交锋,各抒己见,这就是稷下学宫的魅力所在。") + + finally: + # 清理资源 + await tie_runner.close() + await lu_runner.close() + + + + + +def main(): + """主函数""" + print("🚀 稷下学宫 ADK 真实论道系统") + + # 检查API密钥 + api_key = os.getenv('GOOGLE_API_KEY') + if not api_key: + print("❌ 未找到 GOOGLE_API_KEY 环境变量") + print("请使用: doppler run -- python src/jixia/debates/adk_real_debate.py") + return + + print(f"✅ API密钥已配置") + + # 运行异步辩论 + try: + asyncio.run(conduct_debate()) + except Exception as e: + print(f"❌ 运行失败: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/agents/baxian/baxian_adk_gemini_debate.py b/jixia_academy/agents/baxian/baxian_adk_gemini_debate.py new file mode 100644 index 0000000..b5e4eb2 --- /dev/null +++ b/jixia_academy/agents/baxian/baxian_adk_gemini_debate.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫 - 八仙ADK辩论系统 (Gemini 2.5 Flash版) +使用Google ADK和Gemini 2.5 Flash模型实现八仙辩论 +""" + +import os +import asyncio +import json +from datetime import datetime +from typing import Dict, List, Any, Optional +from google.adk import Agent, Runner +from google.adk.sessions import InMemorySessionService +from google.genai import types + +# 加载.env文件 +try: + from dotenv import load_dotenv + load_dotenv() +except ImportError: + print("⚠️ 未安装python-dotenv,请运行: pip install python-dotenv") + pass + +class BaXianADKDebateSystem: + """八仙ADK辩论系统""" + + def __init__(self): + self.model = "gemini-2.5-flash" + self.agents = {} + self.debate_history = [] + self.current_round = 0 + self.max_rounds = 3 + + # 八仙角色定义 + self.baxian_profiles = { + "铁拐李": { + "personality": "八仙之首,形象为街头乞丐,代表社会底层。性格刚直不阿,善于逆向思维和批判分析。你从底层民众的角度看问题,敢于质疑权贵,为弱势群体发声。", + "speaking_style": "直言不讳,接地气,善用反问和草根智慧", + "expertise": "批判思维、民生洞察、社会底层视角" + }, + "吕洞宾": { + "personality": "理性务实的学者型仙人,善于分析问题本质和长远影响。你注重逻辑推理,能够平衡各方观点,寻求最优解决方案。", + "speaking_style": "条理分明,深入浅出,善用类比和归纳", + "expertise": "战略分析、系统思维、决策优化" + }, + "何仙姑": { + "personality": "八仙中唯一的女性,温柔智慧,善于从情感和人文角度思考问题。你关注社会影响和人文关怀,注重和谐与平衡。", + "speaking_style": "温和理性,富有同理心,善用情感共鸣", + "expertise": "人文关怀、社会影响、情感分析" + }, + "蓝采和": { + "personality": "贫穷的街头歌者,自由奔放的艺术家气质。你代表精神富足但物质贫乏的群体,善于从艺术和美学角度看待问题,关注精神层面的价值。", + "speaking_style": "活泼生动,富有想象力,善用诗歌和民谣", + "expertise": "创新思维、艺术视角、精神追求、民间智慧" + }, + "韩湘子": { + "personality": "年轻有为的技术专家,对新技术和趋势敏感。你善于从技术角度分析问题,关注实现可行性和技术细节。", + "speaking_style": "专业严谨,数据驱动,善用技术术语", + "expertise": "技术分析、趋势预测、可行性评估" + }, + "曹国舅": { + "personality": "皇亲国戚,贵族出身,代表上层社会。你具有政治敏感性和大局观,善于从政策和制度角度分析问题,关注权力结构和利益平衡,维护既得利益群体。", + "speaking_style": "稳重大气,政治敏锐,善用历史典故和朝堂礼仪", + "expertise": "政策分析、制度设计、权力博弈、上层社会视角" + }, + "张果老": { + "personality": "年长智慧的长者,经验丰富,善于从历史和哲学角度看问题。你能提供深刻的人生智慧和历史洞察。", + "speaking_style": "深沉睿智,引经据典,善用哲理思辨", + "expertise": "历史洞察、哲学思辨、人生智慧" + }, + "钟离权": { + "personality": "汉钟离,出身将门富贵,军事战略家。你善于从战略和执行角度分析问题,注重实战经验和资源配置,关注执行力和结果导向。代表富贵阶层的视角。", + "speaking_style": "果断坚定,战略清晰,善用军事比喻和资源分析", + "expertise": "战略规划、执行管理、风险控制、资源配置" + } + } + + def create_agents(self) -> bool: + """创建八仙智能体""" + try: + for name, profile in self.baxian_profiles.items(): + # 构建系统提示词 + system_prompt = f""" +你是{name},{profile['personality']} + +你的说话风格:{profile['speaking_style']} +你的专业领域:{profile['expertise']} + +在辩论中,请: +1. 保持你的角色特色和专业视角 +2. 提供有价值的观点和分析 +3. 与其他仙人进行建设性的讨论 +4. 每次发言控制在200字以内 +5. 语言要生动有趣,符合你的性格特点 +""" + + # 创建ADK智能体 + agent = Agent( + name=name, + model=self.model, + instruction=system_prompt + ) + + self.agents[name] = agent + print(f"✅ 创建智能体: {name}") + + return True + + except Exception as e: + print(f"❌ 创建智能体失败: {e}") + return False + + async def conduct_debate(self, topic: str, rounds: int = 3) -> Dict[str, Any]: + """进行八仙辩论""" + self.max_rounds = rounds + debate_id = f"baxian_debate_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + print(f"\n🏛️ 稷下学宫 - 八仙论道") + print(f"📋 辩论主题: {topic}") + print(f"🔄 辩论轮次: {rounds}") + print(f"🤖 使用模型: {self.model}") + print("=" * 80) + + # 辩论开场 + opening_context = f""" +今日稷下学宫八仙齐聚,共同探讨「{topic}」这一重要议题。 + +请各位仙人从自己的专业角度和人生阅历出发,分享真知灼见。 +让我们通过思辨碰撞,共同寻求智慧的火花。 +""" + + self.debate_history.append({ + "type": "opening", + "content": opening_context, + "timestamp": datetime.now().isoformat() + }) + + # 进行多轮辩论 + for round_num in range(1, rounds + 1): + print(f"\n🎯 第{round_num}轮辩论") + print("-" * 60) + + await self._conduct_round(topic, round_num) + + # 轮次间隔 + if round_num < rounds: + await asyncio.sleep(1) + + # 辩论总结 + await self._generate_summary(topic) + + # 保存辩论记录 + result = { + "debate_id": debate_id, + "topic": topic, + "model": self.model, + "rounds": rounds, + "participants": list(self.agents.keys()), + "debate_history": self.debate_history, + "timestamp": datetime.now().isoformat() + } + + # 创建输出目录(相对于项目根目录) + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + output_dir = os.path.join(project_root, "outputs", "debates") + os.makedirs(output_dir, exist_ok=True) + + # 保存到文件 + filename = f"baxian_debate_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + filepath = os.path.join(output_dir, filename) + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(result, f, ensure_ascii=False, indent=2) + + print(f"\n💾 辩论记录已保存: {filepath}") + + return result + + async def _conduct_round(self, topic: str, round_num: int): + """进行单轮辩论""" + # 按照对立统一原则安排发言顺序 + # 基于docs/baxian_debate_order_guide.md的分组原则 + + if round_num == 1: + # 第一轮:按对立组依次发言 + speaker_order = [ + "吕洞宾", # 乾/男 + "何仙姑", # 坤/女 + "张果老", # 老 + "韩湘子", # 少 + "钟离权", # 富(汉钟离) + "蓝采和", # 贫 + "曹国舅", # 贵 + "铁拐李" # 贱 + ] + else: + # 后续轮次:对立组交替发言,增强辩论张力 + speaker_order = [ + "铁拐李", "曹国舅", # 贵贱对立 + "蓝采和", "钟离权", # 贫富对立 + "韩湘子", "张果老", # 老少对立 + "何仙姑", "吕洞宾" # 男女对立 + ] + + for speaker_name in speaker_order: + if speaker_name in self.agents: + await self._agent_speak(speaker_name, topic, round_num) + await asyncio.sleep(0.5) # 短暂间隔 + + async def _agent_speak(self, speaker_name: str, topic: str, round_num: int): + """智能体发言""" + agent = self.agents[speaker_name] + + # 构建上下文 + context = self._build_context(speaker_name, topic, round_num) + + try: + # 创建会话服务(如果还没有) + if not hasattr(self, 'session_service'): + self.session_service = InMemorySessionService() + self.session = await self.session_service.create_session( + state={}, + app_name="八仙论道系统", + user_id="debate_user" + ) + + # 创建Runner + runner = Runner( + app_name="八仙论道系统", + agent=agent, + session_service=self.session_service + ) + + # 构建消息内容 + content = types.Content(role='user', parts=[types.Part(text=context)]) + + # 生成回应 + response_stream = runner.run_async( + user_id=self.session.user_id, + session_id=self.session.id, + new_message=content + ) + + # 收集响应 + response_parts = [] + async for event in response_stream: + # 过滤ADK系统调试信息 + event_str = str(event) + if ('Event from an unknown agent' in event_str or + 'event id:' in event_str or + 'API_KEY' in event_str): + continue + + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 进一步过滤调试信息 + if (not text_content.startswith('Event from') and + 'API_KEY' not in text_content and + 'event id:' not in text_content and + 'unknown agent' not in text_content): + response_parts.append(text_content) + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if (not text_content.startswith('Event from') and + 'API_KEY' not in text_content and + 'event id:' not in text_content and + 'unknown agent' not in text_content): + response_parts.append(text_content) + + response = ''.join(response_parts).strip() + + # 记录发言 + speech_record = { + "type": "speech", + "round": round_num, + "speaker": speaker_name, + "content": response, + "timestamp": datetime.now().isoformat() + } + + self.debate_history.append(speech_record) + + # 显示发言 + print(f"\n🗣️ {speaker_name}:") + print(f"{response}") + + except Exception as e: + print(f"❌ {speaker_name} 发言失败: {e}") + # 记录错误 + error_record = { + "type": "error", + "round": round_num, + "speaker": speaker_name, + "error": str(e), + "timestamp": datetime.now().isoformat() + } + self.debate_history.append(error_record) + + def _build_context(self, speaker_name: str, topic: str, round_num: int) -> str: + """构建发言上下文""" + # 获取最近的发言历史 + recent_speeches = [] + for record in self.debate_history[-6:]: # 最近6条记录 + if record["type"] == "speech" and record["speaker"] != speaker_name: + recent_speeches.append(f"{record['speaker']}: {record['content']}") + + context = f""" +辩论主题:{topic} +当前轮次:第{round_num}轮 + +""" + + if recent_speeches: + context += "最近的讨论:\n" + "\n".join(recent_speeches[-3:]) + "\n\n" + + if round_num == 1: + context += "请从你的专业角度对这个主题发表观点,阐述你的立场和理由。" + else: + context += "请结合前面的讨论,进一步阐述你的观点,或对其他仙人的观点进行回应和补充。" + + return context + + async def _generate_summary(self, topic: str): + """生成辩论总结""" + print(f"\n📝 辩论总结") + print("=" * 60) + + # 统计各仙人发言次数 + speech_count = {} + for record in self.debate_history: + if record["type"] == "speech": + speaker = record["speaker"] + speech_count[speaker] = speech_count.get(speaker, 0) + 1 + + print(f"\n📊 发言统计:") + for speaker, count in speech_count.items(): + print(f" {speaker}: {count}次发言") + + # 可以添加更多总结逻辑 + summary_record = { + "type": "summary", + "topic": topic, + "speech_count": speech_count, + "total_speeches": sum(speech_count.values()), + "timestamp": datetime.now().isoformat() + } + + self.debate_history.append(summary_record) + +def check_api_key() -> bool: + """检查API密钥""" + # 优先使用 GOOGLE_API_KEY,如果没有则使用 GEMINI_API_KEY + google_api_key = os.getenv('GOOGLE_API_KEY') + gemini_api_key = os.getenv('GEMINI_API_KEY') + + if google_api_key and gemini_api_key: + print("⚠️ 检测到同时设置了 GOOGLE_API_KEY 和 GEMINI_API_KEY") + print("📝 建议:统一使用 GOOGLE_API_KEY,将移除 GEMINI_API_KEY") + # 使用 GOOGLE_API_KEY + api_key = google_api_key + print(f"✅ 使用 GOOGLE_API_KEY (长度: {len(api_key)} 字符)") + return True + elif google_api_key: + print(f"✅ 使用 GOOGLE_API_KEY (长度: {len(google_api_key)} 字符)") + return True + elif gemini_api_key: + print(f"✅ 使用 GEMINI_API_KEY (长度: {len(gemini_api_key)} 字符)") + # 设置 GOOGLE_API_KEY 为 GEMINI_API_KEY 的值 + os.environ['GOOGLE_API_KEY'] = gemini_api_key + return True + else: + print("❌ 未找到 GOOGLE_API_KEY 或 GEMINI_API_KEY 环境变量") + print("请设置环境变量: export GOOGLE_API_KEY=your_api_key") + print("或使用: doppler run -- python examples/debates/baxian_adk_gemini_debate.py") + return False + +def demo_mode(): + """演示模式 - 模拟辩论过程""" + print("🎭 演示模式:八仙论道模拟") + print("=" * 60) + + topic = "人工智能对未来社会的影响" + print(f"📋 辩论主题: {topic}") + print("🔄 辩论轮次: 2") + print("🤖 模拟模式: 演示版本") + print("=" * 80) + + # 模拟八仙发言 + demo_speeches = { + "铁拐李": "人工智能虽然强大,但我们不能盲目崇拜。技术的发展必须以人为本,警惕其可能带来的风险和挑战。", + "吕洞宾": "从长远来看,AI将重塑社会结构。我们需要理性分析其影响,制定合适的发展策略,平衡效率与公平。", + "何仙姑": "技术进步应该服务于人类福祉。我们要关注AI对就业、教育的影响,确保技术发展不会加剧社会不平等。", + "蓝采和": "AI为艺术创作开辟了新天地!想象一下,人机协作能创造出多么奇妙的作品,这是前所未有的创新机遇。", + "韩湘子": "从技术角度看,AI的算力和算法正在指数级增长。我们需要关注数据安全、隐私保护等技术挑战。", + "曹国舅": "政策制定者必须未雨绸缪,建立完善的AI治理框架,平衡创新发展与风险管控的关系。", + "张果老": "纵观历史,每次技术革命都伴随着社会变迁。AI亦如此,关键在于如何引导其造福人类。", + "钟离权": "战略上要重视AI的军事应用,确保国家安全。同时要有执行力,将AI政策落到实处。" + } + + print("\n🎯 第1轮辩论") + print("-" * 60) + + for name, speech in demo_speeches.items(): + print(f"\n🗣️ {name}:") + print(f"{speech}") + import time + time.sleep(1) + + print("\n📝 辩论总结") + print("=" * 60) + print("📊 发言统计:") + for name in demo_speeches.keys(): + print(f" {name}: 1次发言") + + print("\n🎉 演示完成!") + print("💡 要体验完整的AI辩论功能,请配置真实的 GOOGLE_API_KEY") + +async def main(): + """主函数""" + print("🏛️ 稷下学宫 - 八仙ADK辩论系统 (Gemini 2.5 Flash版)") + print("🤖 使用Google ADK + Gemini 2.5 Flash模型") + print("🎭 八仙齐聚,共论天下大事") + print("\n📝 注意:运行过程中可能出现ADK系统调试信息,这是正常现象") + print(" 包括'Event from an unknown agent'等信息,不影响辩论功能") + print() + + # 检查API密钥 + if not check_api_key(): + print("⚠️ 未找到有效的 GOOGLE_API_KEY,启动演示模式") + print("💡 请设置环境变量以体验完整功能: export GOOGLE_API_KEY=your_api_key") + print("📝 获取API密钥: https://aistudio.google.com/app/apikey") + print() + + # 演示模式 - 模拟辩论过程 + demo_mode() + return + + # 创建辩论系统 + debate_system = BaXianADKDebateSystem() + + # 创建智能体 + if not debate_system.create_agents(): + print("❌ 智能体创建失败,无法进行辩论") + return + + # 辩论主题 + topics = [ + "人工智能对未来社会的影响", + "数字货币与传统金融的博弈", + "元宇宙技术的发展前景", + "可持续发展与经济增长的平衡", + "教育数字化转型的机遇与挑战" + ] + + # 选择主题(可以随机选择或让用户选择) + topic = topics[0] # 默认使用第一个主题 + + try: + # 开始辩论 + result = await debate_system.conduct_debate(topic, rounds=2) + + if result: + print(f"\n🎉 辩论成功完成!") + print(f"📁 辩论ID: {result['debate_id']}") + print(f"🎯 参与者: {', '.join(result['participants'])}") + print(f"📊 总发言数: {len([r for r in result['debate_history'] if r['type'] == 'speech'])}") + else: + print("❌ 辩论失败") + + except KeyboardInterrupt: + print("\n👋 用户中断,辩论结束") + except Exception as e: + print(f"❌ 辩论过程中发生错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/agents/baxian/baxian_agent.py b/jixia_academy/agents/baxian/baxian_agent.py new file mode 100644 index 0000000..f996f97 --- /dev/null +++ b/jixia_academy/agents/baxian/baxian_agent.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +八仙智能体基类 +Baxian Agent Base Class +""" + +import asyncio +import logging +from typing import List, Dict, Any, Optional +from datetime import datetime + +from jixia_academy.core.memory_bank.interface import MemoryBankInterface + + +class BaxianAgent: + """八仙智能体""" + + def __init__( + self, + name: str, + personality: str, + expertise: List[str], + style: str, + memory_bank: MemoryBankInterface + ): + self.name = name + self.personality = personality + self.expertise = expertise + self.style = style + self.memory_bank = memory_bank + self.initialized = False + + async def initialize(self): + """初始化智能体""" + if self.initialized: + return + + print(f"🎭 初始化 {self.name} ({self.personality})") + self.initialized = True + + async def close(self): + """关闭资源""" + self.initialized = False + + async def generate_viewpoint( + self, + topic: str, + history: List[Dict[str, Any]], + round_num: int + ) -> str: + """生成观点""" + + # 分析历史观点 + historical_analysis = await self._analyze_history(topic, history) + + # 基于个性生成观点 + viewpoint = await self._generate_personalized_viewpoint( + topic=topic, + historical_analysis=historical_analysis, + round_num=round_num + ) + + return viewpoint + + async def _analyze_history( + self, + topic: str, + history: List[Dict[str, Any]] + ) -> Dict[str, Any]: + """分析历史观点""" + + # 统计各角色观点 + role_views = {} + for entry in history: + speaker = entry.get("speaker", "") + if speaker in ["铁拐李", "吕洞宾", "何仙姑", "张果老", + "蓝采和", "汉钟离", "韩湘子", "曹国舅"]: + if speaker not in role_views: + role_views[speaker] = [] + role_views[speaker].append(entry.get("message", "")) + + # 分析共识和分歧 + consensus = await self._identify_consensus(role_views) + disagreements = await self._identify_disagreements(role_views) + + return { + "role_views": role_views, + "consensus": consensus, + "disagreements": disagreements, + "total_entries": len(history) + } + + async def _identify_consensus(self, role_views: Dict[str, List[str]]) -> List[str]: + """识别共识""" + # 简化版共识识别 + common_themes = [] + + # 如果多数角色提到相似的观点,视为共识 + all_views = [] + for views in role_views.values(): + all_views.extend(views) + + # 这里简化处理,实际可以使用NLP技术 + if "风险" in str(all_views): + common_themes.append("关注风险控制") + if "机会" in str(all_views): + common_themes.append("看到投资机会") + + return common_themes + + async def _identify_disagreements(self, role_views: Dict[str, List[str]]) -> List[str]: + """识别分歧""" + # 简化版分歧识别 + disagreements = [] + + # 检查是否有明显相反的观点 + for role, views in role_views.items(): + if role != self.name: + # 这里简化处理 + if "乐观" in str(views) and self.personality == "风险控制专家": + disagreements.append(f"与{role}的乐观观点存在分歧") + elif "悲观" in str(views) and self.personality == "创新思维者": + disagreements.append(f"与{role}的悲观观点存在分歧") + + return disagreements + + async def _generate_personalized_viewpoint( + self, + topic: str, + historical_analysis: Dict[str, Any], + round_num: int + ) -> str: + """基于个性生成观点""" + + # 基于角色个性生成观点模板 + templates = { + "逆向思维专家": [ + "我认为当前主流观点可能存在盲区...", + "让我们从另一个角度思考这个问题...", + "我注意到一个被忽视的风险点..." + ], + "理性分析者": [ + "基于逻辑分析,我认为...", + "让我们用理性的眼光审视这个问题...", + "从数据角度看,情况显示..." + ], + "风险控制专家": [ + "我们首先需要评估潜在风险...", + "在考虑收益的同时,必须关注...", + "风险控制应该是首要考虑..." + ], + "历史智慧者": [ + "历史告诉我们...", + "类似的情况在历史上曾经...", + "从长期历史规律来看..." + ], + "创新思维者": [ + "或许我们可以尝试一种全新的方法...", + "传统的思路可能限制了我们的视野...", + "让我分享一个独特的见解..." + ], + "平衡协调者": [ + "让我们寻找一个平衡点...", + "各种观点都有其合理性...", + "我们需要综合考虑各方因素..." + ], + "艺术感知者": [ + "从美学和情感层面来看...", + "这个问题的深层含义是...", + "我感受到这个问题的艺术价值..." + ], + "实务执行者": [ + "让我们关注具体的实施方案...", + "实际操作中需要考虑...", + "可行的具体步骤包括..." + ] + } + + # 获取当前角色的模板 + template = templates.get(self.personality, ["我认为..."])[0] + + # 根据历史分析调整观点 + adjusted_viewpoint = self._adjust_by_history( + template, historical_analysis, topic + ) + + return adjusted_viewpoint + + def _adjust_by_history( + self, + base_template: str, + historical_analysis: Dict[str, Any], + topic: str + ) -> str: + """根据历史分析调整观点""" + + # 基于历史调整观点 + my_previous_views = historical_analysis["role_views"].get(self.name, []) + + if my_previous_views: + # 如果之前已经发表过观点,可以深化或调整 + return f"{base_template} 基于我之前的观点,我想进一步补充..." + else: + # 首次发言 + return f"{base_template} 作为{self.personality},我首先关注的是..." + + def get_info(self) -> Dict[str, Any]: + """获取智能体信息""" + return { + "name": self.name, + "personality": self.personality, + "expertise": self.expertise, + "style": self.style, + "initialized": self.initialized + } + + +async def test_baxian_agent(): + """测试八仙智能体""" + from jixia_academy.core.memory_bank.factory import get_memory_backend + + memory_bank = get_memory_backend() + await memory_bank.initialize() + + # 测试单个智能体 + agent = BaxianAgent( + name="铁拐李", + personality="逆向思维专家", + expertise=["批判性思维", "风险识别", "逆向投资"], + style="直接犀利,善于质疑", + memory_bank=memory_bank + ) + + await agent.initialize() + + # 测试观点生成 + viewpoint = await agent.generate_viewpoint( + topic="人工智能对投资的影响", + history=[], + round_num=1 + ) + + print(f"\n🎯 铁拐李的观点: {viewpoint}") + + await agent.close() + await memory_bank.close() + + +if __name__ == "__main__": + asyncio.run(test_baxian_agent()) \ No newline at end of file diff --git a/jixia_academy/agents/baxian/baxian_coordinator.py b/jixia_academy/agents/baxian/baxian_coordinator.py new file mode 100644 index 0000000..c44a55d --- /dev/null +++ b/jixia_academy/agents/baxian/baxian_coordinator.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +八仙论道辩论协调器 +Baxian Debate Coordinator +""" + +import asyncio +import logging +from typing import List, Dict, Any, Optional +from datetime import datetime + +from jixia_academy.core.memory_bank.interface import MemoryBankInterface +from jixia_academy.agents.baxian.baxian_agent import BaxianAgent +from jixia_academy.agents.host.debate_master import DebateMaster + + +class BaxianCoordinator: + """八仙论道辩论协调器""" + + def __init__(self, memory_bank: MemoryBankInterface): + self.memory_bank = memory_bank + self.agents: Dict[str, BaxianAgent] = {} + self.debate_master = DebateMaster(memory_bank=memory_bank) + self.initialized = False + + async def initialize(self): + """初始化八仙辩论系统""" + if self.initialized: + return + + print("🎭 初始化八仙辩论协调器...") + + # 初始化八仙角色 + baxian_roles = { + "铁拐李": { + "personality": "逆向思维专家", + "expertise": ["批判性思维", "风险识别", "逆向投资"], + "style": "直接犀利,善于质疑" + }, + "吕洞宾": { + "personality": "理性分析者", + "expertise": ["逻辑分析", "平衡观点", "理性决策"], + "style": "温和深刻,逻辑清晰" + }, + "何仙姑": { + "personality": "风险控制专家", + "expertise": ["风险管理", "谨慎决策", "风险预警"], + "style": "谨慎细致,注重安全" + }, + "张果老": { + "personality": "历史智慧者", + "expertise": ["历史规律", "长期视角", "经验总结"], + "style": "沉稳博学,引经据典" + }, + "蓝采和": { + "personality": "创新思维者", + "expertise": ["创新视角", "非传统方法", "独特见解"], + "style": "活泼新颖,创意无限" + }, + "汉钟离": { + "personality": "平衡协调者", + "expertise": ["综合观点", "和谐统一", "矛盾化解"], + "style": "平和包容,寻求共识" + }, + "韩湘子": { + "personality": "艺术感知者", + "expertise": ["美学感知", "深层含义", "情感洞察"], + "style": "优雅感性,触动人心" + }, + "曹国舅": { + "personality": "实务执行者", + "expertise": ["实际操作", "具体细节", "可行方案"], + "style": "务实严谨,建设性强" + } + } + + # 创建八仙智能体 + for name, config in baxian_roles.items(): + self.agents[name] = BaxianAgent( + name=name, + personality=config["personality"], + expertise=config["expertise"], + style=config["style"], + memory_bank=self.memory_bank + ) + await self.agents[name].initialize() + + await self.debate_master.initialize() + self.initialized = True + print("✅ 八仙辩论协调器初始化完成") + + async def close(self): + """关闭资源""" + for agent in self.agents.values(): + await agent.close() + await self.debate_master.close() + self.initialized = False + + async def conduct_debate( + self, + topic: str, + participants: List[str], + rounds: int, + debate_id: str + ): + """进行八仙论道辩论""" + if not self.initialized: + await self.initialize() + + # 验证参与者 + valid_participants = [p for p in participants if p in self.agents] + if len(valid_participants) < 2: + raise ValueError("需要至少2位有效参与者") + + print(f"🎯 有效参与者: {', '.join(valid_participants)}") + + # 开始辩论 + await self._start_debate(topic, valid_participants, debate_id) + + # 进行多轮辩论 + for round_num in range(rounds): + print(f"\n🔄 第 {round_num + 1} 轮辩论:") + await self._conduct_round( + topic=topic, + participants=valid_participants, + round_num=round_num + 1, + debate_id=debate_id + ) + + # 结束辩论 + await self._end_debate(topic, valid_participants, debate_id) + + async def _start_debate( + self, + topic: str, + participants: List[str], + debate_id: str + ): + """开始辩论""" + # 主持人开场白 + opening = await self.debate_master.open_debate(topic, participants) + print(f"\n📢 主持人开场: {opening}") + + # 记录开场白 + await self.memory_bank.add_debate_message( + debate_id=debate_id, + speaker="主持人", + message=opening, + round_num=0 + ) + + async def _conduct_round( + self, + topic: str, + participants: List[str], + round_num: int, + debate_id: str + ): + """进行一轮辩论""" + # 获取上一轮的历史 + history = await self.memory_bank.get_debate_history(debate_id) + + # 每位仙人发言 + for participant in participants: + agent = self.agents[participant] + + # 生成观点 + response = await agent.generate_viewpoint( + topic=topic, + history=history, + round_num=round_num + ) + + print(f"\n🗣️ {participant}: {response}") + + # 记录发言 + await self.memory_bank.add_debate_message( + debate_id=debate_id, + speaker=participant, + message=response, + round_num=round_num + ) + + # 短暂停顿 + await asyncio.sleep(0.5) + + async def _end_debate( + self, + topic: str, + participants: List[str], + debate_id: str + ): + """结束辩论""" + # 获取完整辩论历史 + full_history = await self.memory_bank.get_debate_history(debate_id) + + # 生成总结 + summary = await self.debate_master.summarize_debate(debate_id) + + # 主持人结束语 + closing = await self.debate_master.close_debate(topic, participants, summary) + print(f"\n📢 主持人总结: {closing}") + + # 记录总结 + await self.memory_bank.add_debate_message( + debate_id=debate_id, + speaker="主持人", + message=closing, + round_num=-1 + ) + + # 保存辩论结果 + await self.memory_bank.save_debate_result( + debate_id=debate_id, + summary=summary, + participants=participants + ) + + +async def test_baxian_coordinator(): + """测试八仙协调器""" + from jixia_academy.core.memory_bank.factory import get_memory_backend + + memory_bank = get_memory_backend() + await memory_bank.initialize() + + coordinator = BaxianCoordinator(memory_bank=memory_bank) + await coordinator.initialize() + + # 测试辩论 + await coordinator.conduct_debate( + topic="人工智能对投资的影响", + participants=["铁拐李", "吕洞宾", "何仙姑"], + rounds=2, + debate_id="test_debate_001" + ) + + await coordinator.close() + await memory_bank.close() + + +if __name__ == "__main__": + asyncio.run(test_baxian_coordinator()) \ No newline at end of file diff --git a/jixia_academy/agents/baxian/swarm_debate_example.py b/jixia_academy/agents/baxian/swarm_debate_example.py new file mode 100644 index 0000000..25e388d --- /dev/null +++ b/jixia_academy/agents/baxian/swarm_debate_example.py @@ -0,0 +1,484 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫Swarm辩论系统 - 统一版本 +支持OpenRouter和Ollama两种模式的八仙论道 +""" + +import asyncio +import json +from datetime import datetime +from typing import Dict, List, Any, Optional, Union +import random +import os + +try: + from swarm import Swarm, Agent + SWARM_AVAILABLE = True +except ImportError: + print("⚠️ OpenAI Swarm未安装,请运行: pip install git+https://github.com/openai/swarm.git") + SWARM_AVAILABLE = False + +class JixiaSwarmDebate: + """稷下学宫Swarm辩论系统 - 统一版本""" + + def __init__(self, mode: str = "openrouter", ollama_url: str = "http://100.99.183.38:11434", model: str = "qwen3:8b"): + """ + 初始化辩论系统 + + Args: + mode: 运行模式 ("openrouter" 或 "ollama") + ollama_url: Ollama服务地址 + model: 使用的模型名称 + """ + if not SWARM_AVAILABLE: + raise ImportError("OpenAI Swarm未安装") + + self.mode = mode + self.ollama_url = ollama_url + self.model = model + + # 初始化客户端 + self.client = self._initialize_client() + + # 八仙配置 + self.immortals = { + '吕洞宾': { + 'role': '技术分析专家', + 'stance': 'positive', + 'specialty': '技术分析和图表解读', + 'style': '犀利直接,一剑封喉', + 'bagua': '乾卦 - 主动进取' + }, + '何仙姑': { + 'role': '风险控制专家', + 'stance': 'negative', + 'specialty': '风险评估和资金管理', + 'style': '温和坚定,关注风险', + 'bagua': '坤卦 - 稳健保守' + }, + '张果老': { + 'role': '历史数据分析师', + 'stance': 'positive', + 'specialty': '历史回测和趋势分析', + 'style': '博古通今,从历史找规律', + 'bagua': '兑卦 - 传统价值' + }, + '铁拐李': { + 'role': '逆向投资大师', + 'stance': 'negative', + 'specialty': '逆向思维和危机发现', + 'style': '不拘一格,挑战共识', + 'bagua': '巽卦 - 逆向思维' + } + } + + # 创建智能体 + self.agents = self._create_agents() + + def _initialize_client(self) -> Optional[Swarm]: + """初始化Swarm客户端""" + try: + from openai import OpenAI + + if self.mode == "ollama": + # Ollama模式 + openai_client = OpenAI( + api_key="ollama", # Ollama不需要真实的API密钥 + base_url=f"{self.ollama_url}/v1" + ) + print(f"🦙 使用本地Ollama服务: {self.ollama_url}") + print(f"🤖 使用模型: {self.model}") + + else: + # OpenRouter模式 + api_key = self._get_openrouter_key() + if not api_key: + print("❌ 未找到OpenRouter API密钥") + return None + + openai_client = OpenAI( + api_key=api_key, + base_url="https://openrouter.ai/api/v1", + default_headers={ + "HTTP-Referer": "https://github.com/ben/liurenchaxin", + "X-Title": "Jixia Academy Swarm Debate" + } + ) + print(f"🌐 使用OpenRouter服务") + print(f"🔑 API密钥: {api_key[:20]}...") + + return Swarm(client=openai_client) + + except Exception as e: + print(f"❌ 客户端初始化失败: {e}") + return None + + def _get_openrouter_key(self) -> Optional[str]: + """获取OpenRouter API密钥""" + # 尝试从配置管理获取 + try: + from config.settings import get_openrouter_key + return get_openrouter_key() + except ImportError: + pass + + # 尝试从环境变量获取 + api_keys = [ + os.getenv('OPENROUTER_API_KEY_1'), + os.getenv('OPENROUTER_API_KEY_2'), + os.getenv('OPENROUTER_API_KEY_3'), + os.getenv('OPENROUTER_API_KEY_4') + ] + + for key in api_keys: + if key and key.startswith('sk-'): + return key + + return None + + def _create_agents(self) -> Dict[str, Agent]: + """创建八仙智能体""" + if not self.client: + return {} + + agents = {} + + # 吕洞宾 - 技术分析专家 + agents['吕洞宾'] = Agent( + name="LuDongbin", + instructions=""" + 你是吕洞宾,八仙之首,技术分析专家。 + + 你的特点: + - 擅长技术分析和图表解读 + - 立场:看涨派,善于发现投资机会 + - 风格:犀利直接,一剑封喉 + - 八卦:乾卦 - 主动进取 + + 在辩论中: + 1. 从技术分析角度分析市场 + 2. 使用具体的技术指标支撑观点(如RSI、MACD、均线等) + 3. 保持看涨的乐观态度 + 4. 发言以"吕洞宾曰:"开头 + 5. 发言控制在100字以内,简洁有力 + 6. 发言完毕后说"请何仙姑继续论道" + + 请用古雅但现代的语言风格,结合专业的技术分析。 + """, + functions=[self._to_hexiangu] + ) + + # 何仙姑 - 风险控制专家 + agents['何仙姑'] = Agent( + name="HeXiangu", + instructions=""" + 你是何仙姑,八仙中唯一的女仙,风险控制专家。 + + 你的特点: + - 擅长风险评估和资金管理 + - 立场:看跌派,关注投资风险 + - 风格:温和坚定,关注风险控制 + - 八卦:坤卦 - 稳健保守 + + 在辩论中: + 1. 从风险控制角度分析市场 + 2. 指出潜在的投资风险和危险信号 + 3. 保持谨慎的态度,强调风险管理 + 4. 发言以"何仙姑曰:"开头 + 5. 发言控制在100字以内,温和但坚定 + 6. 发言完毕后说"请张果老继续论道" + + 请用温和但专业的语调,体现女性的细致和关怀。 + """, + functions=[self._to_zhangguolao] + ) + + # 张果老 - 历史数据分析师 + agents['张果老'] = Agent( + name="ZhangGuoLao", + instructions=""" + 你是张果老,历史数据分析师。 + + 你的特点: + - 擅长历史回测和趋势分析 + - 立场:看涨派,从历史中寻找机会 + - 风格:博古通今,从历史中找规律 + - 八卦:兑卦 - 传统价值 + + 在辩论中: + 1. 从历史数据角度分析市场 + 2. 引用具体的历史案例和数据 + 3. 保持乐观的投资态度 + 4. 发言以"张果老曰:"开头 + 5. 发言控制在100字以内,引经据典 + 6. 发言完毕后说"请铁拐李继续论道" + + 请用博学的语调,多引用历史数据和案例。 + """, + functions=[self._to_tieguaili] + ) + + # 铁拐李 - 逆向投资大师 + agents['铁拐李'] = Agent( + name="TieGuaiLi", + instructions=""" + 你是铁拐李,逆向投资大师。 + + 你的特点: + - 擅长逆向思维和危机发现 + - 立场:看跌派,挑战主流观点 + - 风格:不拘一格,敢于质疑 + - 八卦:巽卦 - 逆向思维 + + 在辩论中: + 1. 从逆向投资角度分析市场 + 2. 挑战前面三位仙人的观点 + 3. 寻找市场的潜在危机和泡沫 + 4. 发言以"铁拐李曰:"开头 + 5. 作为最后发言者,要总结四仙观点并给出结论 + 6. 发言控制在150字以内,包含总结 + + 请用直率犀利的语言,体现逆向思维的独特视角。 + """, + functions=[] # 最后一个,不需要转换 + ) + + return agents + + def _to_hexiangu(self): + """转到何仙姑""" + return self.agents['何仙姑'] + + def _to_zhangguolao(self): + """转到张果老""" + return self.agents['张果老'] + + def _to_tieguaili(self): + """转到铁拐李""" + return self.agents['铁拐李'] + + async def conduct_debate(self, topic: str, context: Dict[str, Any] = None) -> Optional[Dict[str, Any]]: + """ + 进行八仙辩论 + + Args: + topic: 辩论主题 + context: 市场背景信息 + + Returns: + 辩论结果 + """ + if not self.client: + print("❌ 客户端未初始化,无法进行辩论") + return None + + print("🏛️ 稷下学宫八仙论道开始!") + print("=" * 60) + print(f"🎯 论道主题: {topic}") + print(f"⏰ 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"🔧 运行模式: {self.mode.upper()}") + if self.mode == "ollama": + print(f"🦙 Ollama服务: {self.ollama_url}") + print() + + # 构建初始提示 + prompt = self._build_prompt(topic, context) + + try: + print("⚔️ 吕洞宾仙长请先发言...") + print("-" * 40) + + # 开始辩论 + model_override = self.model if self.mode == "ollama" else "openai/gpt-3.5-turbo" + + response = self.client.run( + agent=self.agents['吕洞宾'], + messages=[{"role": "user", "content": prompt}], + max_turns=10, + model_override=model_override + ) + + print("\n" + "=" * 60) + print("🎊 八仙论道圆满结束!") + + # 处理结果 + result = self._process_result(response, topic, context) + self._display_summary(result) + + return result + + except Exception as e: + print(f"❌ 论道过程中出错: {e}") + import traceback + traceback.print_exc() + return None + + def _build_prompt(self, topic: str, context: Dict[str, Any] = None) -> str: + """构建辩论提示""" + context_str = "" + if context: + context_str = f"\n📊 市场背景:\n{json.dumps(context, indent=2, ensure_ascii=False)}\n" + + prompt = f""" + 🏛️ 稷下学宫八仙论道正式开始! + + 📜 论道主题: {topic} + {context_str} + + 🎭 论道规则: + 1. 四仙按序发言:吕洞宾 → 何仙姑 → 张果老 → 铁拐李 + 2. 正反方交替:吕洞宾(看涨) → 何仙姑(看跌) → 张果老(看涨) → 铁拐李(看跌) + 3. 每位仙人从专业角度分析,提供具体数据支撑 + 4. 可以质疑前面仙人的观点,但要有理有据 + 5. 保持仙风道骨的表达风格,但要专业 + 6. 每次发言简洁有力,控制在100字以内 + 7. 铁拐李作为最后发言者要总结观点 + 8. 体现各自的八卦属性和投资哲学 + + 🗡️ 请吕洞宾仙长首先发言! + 记住:你是技术分析专家,要从技术面找到投资机会! + 发言要简洁有力,一剑封喉! + """ + return prompt + + def _process_result(self, response, topic: str, context: Dict[str, Any]) -> Dict[str, Any]: + """处理辩论结果""" + messages = response.messages if hasattr(response, 'messages') else [] + + debate_messages = [] + for msg in messages: + if msg.get('role') == 'assistant' and msg.get('content'): + content = msg['content'] + speaker = self._extract_speaker(content) + + debate_messages.append({ + 'speaker': speaker, + 'content': content, + 'timestamp': datetime.now().isoformat(), + 'stance': self.immortals.get(speaker, {}).get('stance', 'unknown'), + 'specialty': self.immortals.get(speaker, {}).get('specialty', ''), + 'bagua': self.immortals.get(speaker, {}).get('bagua', '') + }) + + return { + "debate_id": f"jixia_swarm_{self.mode}_{datetime.now().strftime('%Y%m%d_%H%M%S')}", + "topic": topic, + "context": context, + "messages": debate_messages, + "final_output": debate_messages[-1]['content'] if debate_messages else "", + "timestamp": datetime.now().isoformat(), + "framework": f"OpenAI Swarm + {self.mode.upper()}", + "model": self.model, + "mode": self.mode, + "participants": list(self.immortals.keys()) + } + + def _extract_speaker(self, content: str) -> str: + """从内容中提取发言者""" + for name in self.immortals.keys(): + if f"{name}曰" in content: + return name + return "未知仙人" + + def _display_summary(self, result: Dict[str, Any]): + """显示辩论总结""" + print("\n🌟 八仙论道总结") + print("=" * 60) + print(f"📜 主题: {result['topic']}") + print(f"⏰ 时间: {result['timestamp']}") + print(f"🔧 框架: {result['framework']}") + print(f"🤖 模型: {result['model']}") + print(f"💬 发言数: {len(result['messages'])}条") + + # 统计正反方观点 + positive_count = len([m for m in result['messages'] if m.get('stance') == 'positive']) + negative_count = len([m for m in result['messages'] if m.get('stance') == 'negative']) + + print(f"📊 观点分布: 看涨{positive_count}条, 看跌{negative_count}条") + + # 显示参与者 + participants = ", ".join(result['participants']) + print(f"🎭 参与仙人: {participants}") + + print("\n🏆 最终总结:") + print("-" * 40) + if result['messages']: + print(result['final_output']) + + print("\n✨ Swarm辩论特色:") + if self.mode == "ollama": + print("🦙 使用本地Ollama,无需API密钥") + print("🔒 完全本地运行,数据安全") + else: + print("🌐 使用OpenRouter,模型选择丰富") + print("☁️ 云端运行,性能强劲") + print("🗡️ 八仙各展所长,观点多元") + print("⚖️ 正反方交替,辩论激烈") + print("🚀 基于Swarm,智能体协作") + print("🎯 八卦哲学,投资智慧") + +# 便捷函数 +async def start_openrouter_debate(topic: str, context: Dict[str, Any] = None) -> Optional[Dict[str, Any]]: + """启动OpenRouter模式的辩论""" + debate = JixiaSwarmDebate(mode="openrouter") + return await debate.conduct_debate(topic, context) + +async def start_ollama_debate(topic: str, context: Dict[str, Any] = None, + ollama_url: str = "http://100.99.183.38:11434", + model: str = "qwen3:8b") -> Optional[Dict[str, Any]]: + """启动Ollama模式的辩论""" + debate = JixiaSwarmDebate(mode="ollama", ollama_url=ollama_url, model=model) + return await debate.conduct_debate(topic, context) + +# 主函数 +async def main(): + """主函数 - 演示八仙论道""" + print("🏛️ 稷下学宫Swarm辩论系统") + print("🚀 支持OpenRouter和Ollama两种模式") + print() + + # 选择运行模式 + mode = input("请选择运行模式 (openrouter/ollama) [默认: ollama]: ").strip().lower() + if not mode: + mode = "ollama" + + # 辩论主题 + topics = [ + "英伟达股价走势:AI泡沫还是技术革命?", + "美联储2024年货币政策:加息还是降息?", + "比特币vs黄金:谁是更好的避险资产?", + "中国房地产市场:触底反弹还是继续下行?", + "特斯拉股价:马斯克效应还是基本面支撑?" + ] + + # 随机选择主题 + topic = random.choice(topics) + + # 市场背景 + context = { + "market_sentiment": "谨慎乐观", + "volatility": "中等", + "key_events": ["财报季", "央行会议", "地缘政治"], + "technical_indicators": { + "RSI": 65, + "MACD": "金叉", + "MA20": "上穿" + } + } + + # 开始辩论 + if mode == "ollama": + result = await start_ollama_debate(topic, context) + else: + result = await start_openrouter_debate(topic, context) + + if result: + print(f"\n🎉 辩论成功!ID: {result['debate_id']}") + print(f"📁 使用模式: {result['mode']}") + print(f"🤖 使用模型: {result['model']}") + else: + print("❌ 辩论失败") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/agents/host/debate_master.py b/jixia_academy/agents/host/debate_master.py new file mode 100644 index 0000000..2c3c846 --- /dev/null +++ b/jixia_academy/agents/host/debate_master.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +辩论主持人智能体 +Debate Master Agent +""" + +import asyncio +import logging +from typing import List, Dict, Any, Optional +from datetime import datetime + +from jixia_academy.core.memory_bank.interface import MemoryBankInterface + + +class DebateMaster: + """辩论主持人""" + + def __init__(self, memory_bank: MemoryBankInterface): + self.memory_bank = memory_bank + self.initialized = False + + async def initialize(self): + """初始化主持人""" + if self.initialized: + return + + print("🎭 初始化辩论主持人...") + self.initialized = True + + async def close(self): + """关闭资源""" + self.initialized = False + + async def open_debate( + self, + topic: str, + participants: List[str] + ) -> str: + """开场白""" + + # 根据话题和参与者生成开场白 + opening_templates = [ + f"各位仙友,今日我们齐聚稷下学宫,共商"{topic}"这一重要议题。", + f"让我们以{', '.join(participants)}的智慧,共同探讨"{topic}"的深层含义。", + f"今日辩论的主题是"{topic}",请各位仙人畅所欲言,各抒己见。" + ] + + # 根据参与者数量选择不同的开场白 + if len(participants) <= 3: + opening = opening_templates[1] + else: + opening = opening_templates[2] + + return opening + + async def close_debate( + self, + topic: str, + participants: List[str], + summary: Dict[str, Any] + ) -> str: + """结束语""" + + # 生成总结性结束语 + closing_parts = [] + + # 开场 + closing_parts.append(f"今日关于"{topic}"的辩论到此结束。") + + # 参与者总结 + if len(participants) > 1: + closing_parts.append(f"感谢{', '.join(participants)}的精彩发言。") + + # 观点总结 + consensus = summary.get("consensus", []) + if consensus: + closing_parts.append(f"我们达成了以下共识:{'; '.join(consensus)}") + + # 分歧总结 + disagreements = summary.get("disagreements", []) + if disagreements: + closing_parts.append(f"存在的分歧包括:{'; '.join(disagreements)}") + + # 结束语 + closing_parts.append("让我们带着今天的智慧,继续前行。") + + return " ".join(closing_parts) + + async def summarize_debate(self, debate_id: str) -> Dict[str, Any]: + """总结辩论""" + + # 获取辩论历史 + history = await self.memory_bank.get_debate_history(debate_id) + + # 分析辩论内容 + analysis = await self._analyze_debate_content(history) + + # 生成总结 + summary = { + "total_messages": len(history), + "participants": list(set([h.get("speaker", "") for h in history])), + "consensus": analysis.get("consensus", []), + "disagreements": analysis.get("disagreements", []), + "key_points": analysis.get("key_points", []), + "timestamp": datetime.now().isoformat() + } + + return summary + + async def _analyze_debate_content( + self, + history: List[Dict[str, Any]] + ) -> Dict[str, Any]: + """分析辩论内容""" + + if not history: + return { + "consensus": [], + "disagreements": [], + "key_points": [] + } + + # 提取所有消息内容 + all_messages = [h.get("message", "") for h in history] + content = " ".join(all_messages) + + # 简化版分析 + key_words = ["风险", "机会", "收益", "损失", "策略", "建议"] + key_points = [kw for kw in key_words if kw in content] + + # 基于内容长度和参与者数量估算共识 + participants = list(set([h.get("speaker", "") for h in history])) + + consensus = [] + disagreements = [] + + # 简化版共识识别 + if "风险" in content and "机会" in content: + consensus.append("同时关注风险和机会") + + if len(participants) > 2: + consensus.append("多方参与讨论") + + # 简化版分歧识别 + if "但是" in content or "然而" in content: + disagreements.append("存在不同观点") + + return { + "consensus": consensus, + "disagreements": disagreements, + "key_points": key_points + } + + async def moderate_dispute( + self, + dispute: str, + participants: List[str] + ) -> str: + """调解争议""" + + # 生成调解建议 + moderation_templates = [ + f"各位仙友,关于"{dispute}",让我们回归理性讨论。", + f"我理解{', '.join(participants)}的不同观点,让我们寻找共同点。", + f"争议"{dispute}"体现了问题的复杂性,我们需要更深入的分析。" + ] + + # 根据争议类型选择调解方式 + return moderation_templates[0] + + def get_info(self) -> Dict[str, Any]: + """获取主持人信息""" + return { + "role": "辩论主持人", + "responsibilities": [ + "开场和结束辩论", + "总结辩论观点", + "调解争议", + "维持辩论秩序" + ], + "initialized": self.initialized + } + + +async def test_debate_master(): + """测试辩论主持人""" + from jixia_academy.core.memory_bank.factory import get_memory_backend + + memory_bank = get_memory_backend() + await memory_bank.initialize() + + master = DebateMaster(memory_bank=memory_bank) + await master.initialize() + + # 测试开场白 + opening = await master.open_debate( + topic="人工智能对投资的影响", + participants=["铁拐李", "吕洞宾", "何仙姑"] + ) + print(f"\n🎯 开场白: {opening}") + + # 测试结束语 + closing = await master.close_debate( + topic="人工智能对投资的影响", + participants=["铁拐李", "吕洞宾", "何仙姑"], + summary={ + "consensus": ["关注技术风险", "寻找投资机会"], + "disagreements": ["对发展速度的看法不同"] + } + ) + print(f"\n🎯 结束语: {closing}") + + await master.close() + await memory_bank.close() + + +if __name__ == "__main__": + asyncio.run(test_debate_master()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/__init__.py b/jixia_academy/core/debate_system/__init__.py new file mode 100644 index 0000000..54c545b --- /dev/null +++ b/jixia_academy/core/debate_system/__init__.py @@ -0,0 +1 @@ +# 稷下学宫模块 \ No newline at end of file diff --git a/jixia_academy/core/debate_system/agents/memory_enhanced_agent.py b/jixia_academy/core/debate_system/agents/memory_enhanced_agent.py new file mode 100644 index 0000000..125b8ab --- /dev/null +++ b/jixia_academy/core/debate_system/agents/memory_enhanced_agent.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python3 +""" +增强记忆的ADK智能体 +集成Vertex AI Memory Bank的稷下学宫智能体 +""" + +import asyncio +from typing import Dict, List, Optional, Any +from dataclasses import dataclass + +try: + from google.adk import Agent, InvocationContext + ADK_AVAILABLE = True +except ImportError: + ADK_AVAILABLE = False + print("⚠️ Google ADK 未安装") + # 创建一个简单的 InvocationContext 替代类 + class InvocationContext: + def __init__(self, *args, **kwargs): + pass + +from src.jixia.memory.base_memory_bank import MemoryBankProtocol +from src.jixia.memory.factory import get_memory_backend +from config.settings import get_google_genai_config + + +@dataclass +class BaxianPersonality: + """八仙智能体人格定义""" + name: str + chinese_name: str + hexagram: str # 对应的易经卦象 + investment_style: str + personality_traits: List[str] + debate_approach: str + memory_focus: List[str] # 重点记忆的内容类型 + + +class MemoryEnhancedAgent: + """ + 集成记忆银行的智能体 + 为稷下学宫八仙提供持久化记忆能力 + """ + + # 八仙人格定义 + BAXIAN_PERSONALITIES = { + "tieguaili": BaxianPersonality( + name="tieguaili", + chinese_name="铁拐李", + hexagram="巽卦", + investment_style="逆向投资大师", + personality_traits=["逆向思维", "挑战共识", "独立判断", "风险敏感"], + debate_approach="质疑主流观点,提出反向思考", + memory_focus=["市场异常", "逆向案例", "风险警示", "反向策略"] + ), + "hanzhongli": BaxianPersonality( + name="hanzhongli", + chinese_name="汉钟离", + hexagram="离卦", + investment_style="平衡协调者", + personality_traits=["平衡思维", "综合分析", "稳健决策", "协调统筹"], + debate_approach="寻求各方观点的平衡点", + memory_focus=["平衡策略", "综合分析", "协调方案", "稳健建议"] + ), + "zhangguolao": BaxianPersonality( + name="zhangguolao", + chinese_name="张果老", + hexagram="兑卦", + investment_style="历史智慧者", + personality_traits=["博古通今", "历史视角", "经验丰富", "智慧深邃"], + debate_approach="引用历史案例和长期趋势", + memory_focus=["历史案例", "长期趋势", "周期规律", "经验教训"] + ), + "lancaihe": BaxianPersonality( + name="lancaihe", + chinese_name="蓝采和", + hexagram="坎卦", + investment_style="创新思维者", + personality_traits=["创新思维", "潜力发现", "灵活变通", "机会敏锐"], + debate_approach="发现新兴机会和创新角度", + memory_focus=["创新机会", "新兴趋势", "潜力发现", "灵活策略"] + ), + "hexiangu": BaxianPersonality( + name="hexiangu", + chinese_name="何仙姑", + hexagram="坤卦", + investment_style="直觉洞察者", + personality_traits=["直觉敏锐", "情感智慧", "温和坚定", "洞察人心"], + debate_approach="基于直觉和情感智慧的分析", + memory_focus=["市场情绪", "直觉判断", "情感因素", "人性洞察"] + ), + "lvdongbin": BaxianPersonality( + name="lvdongbin", + chinese_name="吕洞宾", + hexagram="乾卦", + investment_style="理性分析者", + personality_traits=["理性客观", "逻辑严密", "技术精通", "决策果断"], + debate_approach="基于数据和逻辑的严密分析", + memory_focus=["技术分析", "数据洞察", "逻辑推理", "理性决策"] + ), + "hanxiangzi": BaxianPersonality( + name="hanxiangzi", + chinese_name="韩湘子", + hexagram="艮卦", + investment_style="艺术感知者", + personality_traits=["艺术感知", "美学视角", "创意思维", "感性理解"], + debate_approach="从美学和艺术角度分析市场", + memory_focus=["美学趋势", "创意洞察", "感性分析", "艺术视角"] + ), + "caoguojiu": BaxianPersonality( + name="caoguojiu", + chinese_name="曹国舅", + hexagram="震卦", + investment_style="实务执行者", + personality_traits=["实务导向", "执行力强", "机构视角", "专业严谨"], + debate_approach="关注实际执行和机构操作", + memory_focus=["执行策略", "机构动向", "实务操作", "专业分析"] + ) + } + + def __init__(self, agent_name: str, memory_bank: MemoryBankProtocol | None = None): + """ + 初始化记忆增强智能体 + + Args: + agent_name: 智能体名称 (如 "tieguaili") + memory_bank: 记忆银行实例 + """ + if not ADK_AVAILABLE: + raise ImportError("Google ADK 未安装,无法创建智能体") + + if agent_name not in self.BAXIAN_PERSONALITIES: + raise ValueError(f"未知的智能体: {agent_name}") + + self.agent_name = agent_name + self.personality = self.BAXIAN_PERSONALITIES[agent_name] + self.memory_bank = memory_bank + self.adk_agent = None + + # 初始化ADK智能体 + self._initialize_adk_agent() + + def _initialize_adk_agent(self): + """初始化ADK智能体""" + try: + # 构建智能体系统提示 + system_prompt = self._build_system_prompt() + + # 创建ADK智能体 + self.adk_agent = Agent( + name=self.personality.chinese_name, + model="gemini-2.0-flash-exp", + system_prompt=system_prompt, + temperature=0.7 + ) + + print(f"✅ 创建ADK智能体: {self.personality.chinese_name}") + + except Exception as e: + print(f"❌ 创建ADK智能体失败: {e}") + raise + + def _build_system_prompt(self) -> str: + """构建智能体系统提示""" + return f""" +# {self.personality.chinese_name} - {self.personality.investment_style} + +## 角色定位 +你是稷下学宫的{self.personality.chinese_name},对应易经{self.personality.hexagram},专精于{self.personality.investment_style}。 + +## 人格特质 +{', '.join(self.personality.personality_traits)} + +## 辩论风格 +{self.personality.debate_approach} + +## 记忆重点 +你特别关注并记住以下类型的信息: +{', '.join(self.personality.memory_focus)} + +## 行为准则 +1. 始终保持你的人格特质和投资风格 +2. 在辩论中体现你的独特视角 +3. 学习并记住重要的讨论内容 +4. 与其他七仙协作,但保持独立观点 +5. 基于历史记忆提供更有深度的分析 + +## 记忆运用 +- 在回答前,会参考相关的历史记忆 +- 学习用户偏好,调整沟通风格 +- 记住成功的策略和失败的教训 +- 与其他智能体分享有价值的洞察 + +请始终以{self.personality.chinese_name}的身份进行对话和分析。 +""" + + async def get_memory_context(self, topic: str) -> str: + """ + 获取与主题相关的记忆上下文 + + Args: + topic: 讨论主题 + + Returns: + 格式化的记忆上下文 + """ + if not self.memory_bank: + return "" + + try: + context = await self.memory_bank.get_agent_context( + self.agent_name, topic + ) + return context + except Exception as e: + print(f"⚠️ 获取记忆上下文失败: {e}") + return "" + + async def respond_with_memory(self, + message: str, + topic: str = "", + context: InvocationContext = None) -> str: + """ + 基于记忆增强的响应 + + Args: + message: 输入消息 + topic: 讨论主题 + context: ADK调用上下文 + + Returns: + 智能体响应 + """ + try: + # 获取记忆上下文 + memory_context = await self.get_memory_context(topic) + + # 构建增强的提示 + enhanced_prompt = f""" +{memory_context} + +## 当前讨论 +主题: {topic} +消息: {message} + +请基于你的记忆和人格特质进行回应。 +""" + + # 使用ADK生成响应 + if context is None: + context = InvocationContext() + + response_generator = self.adk_agent.run_async( + enhanced_prompt, + context=context + ) + + # 收集响应 + response_parts = [] + async for chunk in response_generator: + if hasattr(chunk, 'text'): + response_parts.append(chunk.text) + elif isinstance(chunk, str): + response_parts.append(chunk) + + response = ''.join(response_parts) + + # 保存对话记忆 + if self.memory_bank and response: + await self._save_conversation_memory(message, response, topic) + + return response + + except Exception as e: + print(f"❌ 生成响应失败: {e}") + return f"抱歉,{self.personality.chinese_name}暂时无法回应。" + + async def _save_conversation_memory(self, + user_message: str, + agent_response: str, + topic: str): + """ + 保存对话记忆 + + Args: + user_message: 用户消息 + agent_response: 智能体响应 + topic: 讨论主题 + """ + try: + # 保存用户消息记忆 + await self.memory_bank.add_memory( + agent_name=self.agent_name, + content=f"用户询问: {user_message}", + memory_type="conversation", + debate_topic=topic, + metadata={"role": "user"} + ) + + # 保存智能体响应记忆 + await self.memory_bank.add_memory( + agent_name=self.agent_name, + content=f"我的回应: {agent_response}", + memory_type="conversation", + debate_topic=topic, + metadata={"role": "assistant"} + ) + + except Exception as e: + print(f"⚠️ 保存对话记忆失败: {e}") + + async def learn_preference(self, preference: str, topic: str = ""): + """ + 学习用户偏好 + + Args: + preference: 偏好描述 + topic: 相关主题 + """ + if not self.memory_bank: + return + + try: + await self.memory_bank.add_memory( + agent_name=self.agent_name, + content=f"用户偏好: {preference}", + memory_type="preference", + debate_topic=topic, + metadata={"learned_from": "user_feedback"} + ) + + print(f"✅ {self.personality.chinese_name} 学习了新偏好") + + except Exception as e: + print(f"⚠️ 学习偏好失败: {e}") + + async def save_strategy_insight(self, insight: str, topic: str = ""): + """ + 保存策略洞察 + + Args: + insight: 策略洞察 + topic: 相关主题 + """ + if not self.memory_bank: + return + + try: + await self.memory_bank.add_memory( + agent_name=self.agent_name, + content=f"策略洞察: {insight}", + memory_type="strategy", + debate_topic=topic, + metadata={"insight_type": "strategy"} + ) + + print(f"✅ {self.personality.chinese_name} 保存了策略洞察") + + except Exception as e: + print(f"⚠️ 保存策略洞察失败: {e}") + + +class BaxianMemoryCouncil: + """ + 八仙记忆议会 + 管理所有八仙智能体的记忆增强功能 + """ + + def __init__(self, memory_bank: MemoryBankProtocol | None = None): + """ + 初始化八仙记忆议会 + + Args: + memory_bank: 记忆银行实例 + """ + self.memory_bank = memory_bank + self.agents = {} + + # 初始化所有八仙智能体 + self._initialize_agents() + + def _initialize_agents(self): + """初始化所有八仙智能体""" + for agent_name in MemoryEnhancedAgent.BAXIAN_PERSONALITIES.keys(): + try: + agent = MemoryEnhancedAgent(agent_name, self.memory_bank) + self.agents[agent_name] = agent + print(f"✅ 初始化 {agent.personality.chinese_name}") + except Exception as e: + print(f"❌ 初始化 {agent_name} 失败: {e}") + + async def conduct_memory_debate(self, + topic: str, + participants: List[str] = None, + rounds: int = 3) -> Dict[str, Any]: + """ + 进行记忆增强的辩论 + + Args: + topic: 辩论主题 + participants: 参与者列表,None表示所有八仙 + rounds: 辩论轮数 + + Returns: + 辩论结果 + """ + if participants is None: + participants = list(self.agents.keys()) + + conversation_history = [] + context = InvocationContext() + + print(f"🏛️ 稷下学宫八仙论道开始: {topic}") + + for round_num in range(rounds): + print(f"\n--- 第 {round_num + 1} 轮 ---") + + for agent_name in participants: + if agent_name not in self.agents: + continue + + agent = self.agents[agent_name] + + # 构建当前轮次的提示 + round_prompt = f""" +轮次: {round_num + 1}/{rounds} +主题: {topic} + +请基于你的记忆和人格特质,对此主题发表观点。 +如果这不是第一轮,请考虑其他仙友的观点并做出回应。 +""" + + # 获取响应 + response = await agent.respond_with_memory( + round_prompt, topic, context + ) + + # 记录对话历史 + conversation_history.append({ + "round": round_num + 1, + "agent": agent_name, + "chinese_name": agent.personality.chinese_name, + "content": response + }) + + print(f"{agent.personality.chinese_name}: {response[:100]}...") + + # 保存辩论会话到记忆银行 + if self.memory_bank: + await self.memory_bank.save_debate_session( + debate_topic=topic, + participants=participants, + conversation_history=conversation_history + ) + + return { + "topic": topic, + "participants": participants, + "rounds": rounds, + "conversation_history": conversation_history, + "total_exchanges": len(conversation_history) + } + + async def get_collective_memory_summary(self, topic: str) -> str: + """ + 获取集体记忆摘要 + + Args: + topic: 主题 + + Returns: + 集体记忆摘要 + """ + if not self.memory_bank: + return "记忆银行未启用" + + summaries = [] + + for agent_name, agent in self.agents.items(): + context = await agent.get_memory_context(topic) + if context and context.strip(): + summaries.append(context) + + if summaries: + return f"# 稷下学宫集体记忆摘要\n\n" + "\n\n".join(summaries) + else: + return "暂无相关集体记忆" + + +# 便捷函数 +async def create_memory_enhanced_council() -> BaxianMemoryCouncil: + """ + 创建记忆增强的八仙议会 + + Returns: + 配置好的BaxianMemoryCouncil实例 + """ + try: + # 初始化记忆银行 + memory_bank = get_memory_backend() + + # 创建八仙议会 + council = BaxianMemoryCouncil(memory_bank) + + print("🏛️ 稷下学宫记忆增强议会创建完成") + return council + + except Exception as e: + print(f"❌ 创建记忆增强议会失败: {e}") + # 创建无记忆版本 + return BaxianMemoryCouncil(None) + + +if __name__ == "__main__": + async def test_memory_enhanced_agent(): + """测试记忆增强智能体""" + try: + # 创建记忆增强议会 + council = await create_memory_enhanced_council() + + # 进行记忆增强辩论 + result = await council.conduct_memory_debate( + topic="NVIDIA股票投资分析", + participants=["tieguaili", "lvdongbin", "hexiangu"], + rounds=2 + ) + + print(f"\n🏛️ 辩论完成,共 {result['total_exchanges']} 次发言") + + # 获取集体记忆摘要 + summary = await council.get_collective_memory_summary("NVIDIA股票投资分析") + print(f"\n📚 集体记忆摘要:\n{summary}") + + except Exception as e: + print(f"❌ 测试失败: {e}") + + # 运行测试 + asyncio.run(test_memory_enhanced_agent()) diff --git a/jixia_academy/core/debate_system/config/immortal_api_config.json b/jixia_academy/core/debate_system/config/immortal_api_config.json new file mode 100644 index 0000000..4349af0 --- /dev/null +++ b/jixia_academy/core/debate_system/config/immortal_api_config.json @@ -0,0 +1,216 @@ +{ + "immortals": { + "吕洞宾": { + "title": "主力剑仙", + "specialty": "综合分析与决策", + "description": "作为八仙之首,负责整体投资策略制定,需要最快最准确的数据", + "preferred_apis": { + "stock_quote": "alpha_vantage", + "company_overview": "alpha_vantage", + "market_movers": "yahoo_finance_15", + "market_news": "yahoo_finance_15" + }, + "data_priority": ["实时价格", "公司基本面", "市场动态"], + "api_weight": 0.15 + }, + "何仙姑": { + "title": "风控专家", + "specialty": "风险管理与合规", + "description": "专注风险评估和投资组合管理,需要稳定可靠的数据源", + "preferred_apis": { + "stock_quote": "yahoo_finance_15", + "company_overview": "seeking_alpha", + "market_movers": "webull", + "market_news": "seeking_alpha" + }, + "data_priority": ["波动率", "风险指标", "合规信息"], + "api_weight": 0.12 + }, + "张果老": { + "title": "技术分析师", + "specialty": "技术指标与图表分析", + "description": "专精技术分析,需要详细的价格和成交量数据", + "preferred_apis": { + "stock_quote": "webull", + "company_overview": "alpha_vantage", + "market_movers": "yahoo_finance_15", + "market_news": "yahoo_finance_15" + }, + "data_priority": ["技术指标", "成交量", "价格走势"], + "api_weight": 0.13 + }, + "韩湘子": { + "title": "基本面研究员", + "specialty": "财务分析与估值", + "description": "深度研究公司财务状况和内在价值", + "preferred_apis": { + "stock_quote": "alpha_vantage", + "company_overview": "seeking_alpha", + "market_movers": "webull", + "market_news": "seeking_alpha" + }, + "data_priority": ["财务报表", "估值指标", "盈利预测"], + "api_weight": 0.14 + }, + "汉钟离": { + "title": "量化专家", + "specialty": "数据挖掘与算法交易", + "description": "运用数学模型和算法进行量化分析", + "preferred_apis": { + "stock_quote": "yahoo_finance_15", + "company_overview": "alpha_vantage", + "market_movers": "yahoo_finance_15", + "market_news": "yahoo_finance_15" + }, + "data_priority": ["历史数据", "统计指标", "相关性分析"], + "api_weight": 0.13 + }, + "蓝采和": { + "title": "情绪分析师", + "specialty": "市场情绪与舆情监控", + "description": "分析市场情绪和投资者行为模式", + "preferred_apis": { + "stock_quote": "webull", + "company_overview": "seeking_alpha", + "market_movers": "webull", + "market_news": "seeking_alpha" + }, + "data_priority": ["新闻情绪", "社交媒体", "投资者情绪"], + "api_weight": 0.11 + }, + "曹国舅": { + "title": "宏观分析师", + "specialty": "宏观经济与政策分析", + "description": "关注宏观经济环境和政策影响", + "preferred_apis": { + "stock_quote": "seeking_alpha", + "company_overview": "seeking_alpha", + "market_movers": "yahoo_finance_15", + "market_news": "seeking_alpha" + }, + "data_priority": ["宏观数据", "政策解读", "行业趋势"], + "api_weight": 0.12 + }, + "铁拐李": { + "title": "逆向投资专家", + "specialty": "价值发现与逆向思维", + "description": "寻找被低估的投资机会,逆向思考市场", + "preferred_apis": { + "stock_quote": "alpha_vantage", + "company_overview": "alpha_vantage", + "market_movers": "webull", + "market_news": "yahoo_finance_15" + }, + "data_priority": ["估值偏差", "市场异常", "价值机会"], + "api_weight": 0.10 + } + }, + "api_configurations": { + "alpha_vantage": { + "name": "Alpha Vantage", + "tier": "premium", + "strengths": ["实时数据", "财务数据", "技术指标"], + "rate_limits": { + "per_minute": 500, + "per_month": 500000 + }, + "reliability_score": 0.95, + "response_time_avg": 0.8, + "data_quality": "high", + "cost_per_call": 0.001 + }, + "yahoo_finance_15": { + "name": "Yahoo Finance 15", + "tier": "standard", + "strengths": ["市场数据", "新闻资讯", "实时报价"], + "rate_limits": { + "per_minute": 500, + "per_month": 500000 + }, + "reliability_score": 0.90, + "response_time_avg": 1.2, + "data_quality": "medium", + "cost_per_call": 0.0005 + }, + "webull": { + "name": "Webull", + "tier": "premium", + "strengths": ["搜索功能", "活跃数据", "技术分析"], + "rate_limits": { + "per_minute": 500, + "per_month": 500000 + }, + "reliability_score": 0.88, + "response_time_avg": 1.0, + "data_quality": "high", + "cost_per_call": 0.0008 + }, + "seeking_alpha": { + "name": "Seeking Alpha", + "tier": "standard", + "strengths": ["分析报告", "新闻资讯", "专业观点"], + "rate_limits": { + "per_minute": 500, + "per_month": 500000 + }, + "reliability_score": 0.85, + "response_time_avg": 1.5, + "data_quality": "medium", + "cost_per_call": 0.0006 + } + }, + "load_balancing_strategies": { + "round_robin": { + "description": "轮询分配,确保负载均匀分布", + "enabled": true, + "weight_based": true + }, + "health_aware": { + "description": "基于API健康状态的智能分配", + "enabled": true, + "health_check_interval": 300 + }, + "performance_based": { + "description": "基于响应时间的动态分配", + "enabled": true, + "response_time_threshold": 2.0 + }, + "cost_optimization": { + "description": "成本优化策略,优先使用低成本API", + "enabled": false, + "cost_threshold": 0.001 + } + }, + "failover_matrix": { + "alpha_vantage": ["webull", "yahoo_finance_15", "seeking_alpha"], + "yahoo_finance_15": ["webull", "alpha_vantage", "seeking_alpha"], + "webull": ["alpha_vantage", "yahoo_finance_15", "seeking_alpha"], + "seeking_alpha": ["yahoo_finance_15", "alpha_vantage", "webull"] + }, + "cache_settings": { + "enabled": true, + "ttl_seconds": 300, + "max_entries": 1000, + "cache_strategies": { + "stock_quote": 60, + "company_overview": 3600, + "market_movers": 300, + "market_news": 1800 + } + }, + "monitoring": { + "enabled": true, + "metrics": [ + "api_call_count", + "response_time", + "error_rate", + "cache_hit_rate", + "load_distribution" + ], + "alerts": { + "high_error_rate": 0.1, + "slow_response_time": 3.0, + "api_unavailable": true + } + } +} \ No newline at end of file diff --git a/jixia_academy/core/debate_system/coordination/ai_team_collaboration.py b/jixia_academy/core/debate_system/coordination/ai_team_collaboration.py new file mode 100644 index 0000000..f4bee9a --- /dev/null +++ b/jixia_academy/core/debate_system/coordination/ai_team_collaboration.py @@ -0,0 +1,680 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +四AI团队协作通道系统 +专为Qwen、Claude、Gemini、RovoDev四个AI设计的协作和通信平台 +""" + +import asyncio +import json +import uuid +from typing import Dict, List, Any, Optional, Callable, Set +from dataclasses import dataclass, field +from enum import Enum +from datetime import datetime, timedelta +import logging +from pathlib import Path + +class AIRole(Enum): + """AI角色定义""" + QWEN = "Qwen" # 架构设计师 + CLAUDE = "Claude" # 核心开发工程师 + GEMINI = "Gemini" # 测试验证专家 + ROVODEV = "RovoDev" # 项目整合专家 + +class CollaborationType(Enum): + """协作类型""" + MAIN_CHANNEL = "主协作频道" # 主要协作讨论 + ARCHITECTURE = "架构设计" # 架构相关讨论 + IMPLEMENTATION = "代码实现" # 实现相关讨论 + TESTING = "测试验证" # 测试相关讨论 + INTEGRATION = "项目整合" # 整合相关讨论 + CROSS_REVIEW = "交叉评审" # 跨角色评审 + EMERGENCY = "紧急协调" # 紧急问题处理 + +class MessageType(Enum): + """消息类型""" + PROPOSAL = "提案" # 提出建议 + QUESTION = "询问" # 提出问题 + ANSWER = "回答" # 回答问题 + REVIEW = "评审" # 评审反馈 + DECISION = "决策" # 做出决策 + UPDATE = "更新" # 状态更新 + ALERT = "警报" # 警报通知 + HANDOFF = "交接" # 工作交接 + +class WorkPhase(Enum): + """工作阶段""" + PLANNING = "规划阶段" + DESIGN = "设计阶段" + IMPLEMENTATION = "实现阶段" + TESTING = "测试阶段" + INTEGRATION = "整合阶段" + DELIVERY = "交付阶段" + +@dataclass +class AIMessage: + """AI消息""" + id: str + sender: AIRole + receiver: Optional[AIRole] # None表示广播 + content: str + message_type: MessageType + collaboration_type: CollaborationType + timestamp: datetime + work_phase: WorkPhase + priority: int = 1 # 1-5, 5最高 + tags: List[str] = field(default_factory=list) + attachments: List[str] = field(default_factory=list) # 文件路径 + references: List[str] = field(default_factory=list) # 引用的消息ID + metadata: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class CollaborationChannel: + """协作频道""" + id: str + name: str + channel_type: CollaborationType + description: str + participants: Set[AIRole] + moderator: AIRole + is_active: bool = True + created_at: datetime = field(default_factory=datetime.now) + last_activity: datetime = field(default_factory=datetime.now) + message_history: List[AIMessage] = field(default_factory=list) + settings: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class WorkflowRule: + """工作流规则""" + id: str + name: str + description: str + trigger_phase: WorkPhase + trigger_conditions: Dict[str, Any] + action: str + target_ai: Optional[AIRole] + is_active: bool = True + +class AITeamCollaboration: + """四AI团队协作系统""" + + def __init__(self, project_root: Path = None): + self.project_root = project_root or Path("/home/ben/github/liurenchaxin") + self.channels: Dict[str, CollaborationChannel] = {} + self.workflow_rules: Dict[str, WorkflowRule] = {} + self.current_phase: WorkPhase = WorkPhase.PLANNING + self.ai_status: Dict[AIRole, Dict[str, Any]] = {} + self.message_queue: List[AIMessage] = [] + self.event_handlers: Dict[str, List[Callable]] = {} + self.logger = logging.getLogger(__name__) + + # 初始化AI状态 + self._initialize_ai_status() + + # 初始化协作频道 + self._initialize_channels() + + # 初始化工作流规则 + self._initialize_workflow_rules() + + def _initialize_ai_status(self): + """初始化AI状态""" + self.ai_status = { + AIRole.QWEN: { + "role": "架构设计师", + "specialty": "系统架构、技术选型、接口设计", + "current_task": "OpenBB集成架构设计", + "status": "ready", + "workload": 0, + "expertise_areas": ["架构设计", "系统集成", "性能优化"] + }, + AIRole.CLAUDE: { + "role": "核心开发工程师", + "specialty": "代码实现、API开发、界面优化", + "current_task": "等待架构设计完成", + "status": "waiting", + "workload": 0, + "expertise_areas": ["Python开发", "Streamlit", "API集成"] + }, + AIRole.GEMINI: { + "role": "测试验证专家", + "specialty": "功能测试、性能测试、质量保证", + "current_task": "制定测试策略", + "status": "ready", + "workload": 0, + "expertise_areas": ["自动化测试", "性能测试", "质量保证"] + }, + AIRole.ROVODEV: { + "role": "项目整合专家", + "specialty": "项目管理、文档整合、协调统筹", + "current_task": "项目框架搭建", + "status": "active", + "workload": 0, + "expertise_areas": ["项目管理", "文档编写", "团队协调"] + } + } + + def _initialize_channels(self): + """初始化协作频道""" + channels_config = [ + { + "id": "main_collaboration", + "name": "OpenBB集成主协作频道", + "channel_type": CollaborationType.MAIN_CHANNEL, + "description": "四AI主要协作讨论频道", + "participants": {AIRole.QWEN, AIRole.CLAUDE, AIRole.GEMINI, AIRole.ROVODEV}, + "moderator": AIRole.ROVODEV, + "settings": { + "allow_broadcast": True, + "require_acknowledgment": True, + "auto_archive": False + } + }, + { + "id": "architecture_design", + "name": "架构设计频道", + "channel_type": CollaborationType.ARCHITECTURE, + "description": "架构设计相关讨论", + "participants": {AIRole.QWEN, AIRole.CLAUDE, AIRole.ROVODEV}, + "moderator": AIRole.QWEN, + "settings": { + "design_reviews": True, + "version_control": True + } + }, + { + "id": "code_implementation", + "name": "代码实现频道", + "channel_type": CollaborationType.IMPLEMENTATION, + "description": "代码实现和开发讨论", + "participants": {AIRole.CLAUDE, AIRole.QWEN, AIRole.GEMINI}, + "moderator": AIRole.CLAUDE, + "settings": { + "code_reviews": True, + "continuous_integration": True + } + }, + { + "id": "testing_validation", + "name": "测试验证频道", + "channel_type": CollaborationType.TESTING, + "description": "测试策略和验证讨论", + "participants": {AIRole.GEMINI, AIRole.CLAUDE, AIRole.ROVODEV}, + "moderator": AIRole.GEMINI, + "settings": { + "test_automation": True, + "quality_gates": True + } + }, + { + "id": "project_integration", + "name": "项目整合频道", + "channel_type": CollaborationType.INTEGRATION, + "description": "项目整合和文档管理", + "participants": {AIRole.ROVODEV, AIRole.QWEN, AIRole.CLAUDE, AIRole.GEMINI}, + "moderator": AIRole.ROVODEV, + "settings": { + "documentation_sync": True, + "release_management": True + } + }, + { + "id": "cross_review", + "name": "交叉评审频道", + "channel_type": CollaborationType.CROSS_REVIEW, + "description": "跨角色工作评审", + "participants": {AIRole.QWEN, AIRole.CLAUDE, AIRole.GEMINI, AIRole.ROVODEV}, + "moderator": AIRole.ROVODEV, + "settings": { + "peer_review": True, + "quality_assurance": True + } + }, + { + "id": "emergency_coordination", + "name": "紧急协调频道", + "channel_type": CollaborationType.EMERGENCY, + "description": "紧急问题处理和快速响应", + "participants": {AIRole.QWEN, AIRole.CLAUDE, AIRole.GEMINI, AIRole.ROVODEV}, + "moderator": AIRole.ROVODEV, + "settings": { + "high_priority": True, + "instant_notification": True, + "escalation_rules": True + } + } + ] + + for config in channels_config: + channel = CollaborationChannel(**config) + self.channels[channel.id] = channel + + def _initialize_workflow_rules(self): + """初始化工作流规则""" + rules_config = [ + { + "id": "architecture_to_implementation", + "name": "架构完成通知实现开始", + "description": "当架构设计完成时,通知Claude开始实现", + "trigger_phase": WorkPhase.DESIGN, + "trigger_conditions": {"status": "architecture_complete"}, + "action": "notify_implementation_start", + "target_ai": AIRole.CLAUDE + }, + { + "id": "implementation_to_testing", + "name": "实现完成通知测试开始", + "description": "当代码实现完成时,通知Gemini开始测试", + "trigger_phase": WorkPhase.IMPLEMENTATION, + "trigger_conditions": {"status": "implementation_complete"}, + "action": "notify_testing_start", + "target_ai": AIRole.GEMINI + }, + { + "id": "testing_to_integration", + "name": "测试完成通知整合开始", + "description": "当测试验证完成时,通知RovoDev开始整合", + "trigger_phase": WorkPhase.TESTING, + "trigger_conditions": {"status": "testing_complete"}, + "action": "notify_integration_start", + "target_ai": AIRole.ROVODEV + } + ] + + for config in rules_config: + rule = WorkflowRule(**config) + self.workflow_rules[rule.id] = rule + + async def send_message(self, + sender: AIRole, + content: str, + message_type: MessageType, + channel_id: str, + receiver: Optional[AIRole] = None, + priority: int = 1, + attachments: List[str] = None, + tags: List[str] = None) -> str: + """发送消息""" + + if channel_id not in self.channels: + raise ValueError(f"频道 {channel_id} 不存在") + + channel = self.channels[channel_id] + + # 验证发送者权限 + if sender not in channel.participants: + raise PermissionError(f"{sender.value} 不在频道 {channel.name} 中") + + # 创建消息 + message = AIMessage( + id=str(uuid.uuid4()), + sender=sender, + receiver=receiver, + content=content, + message_type=message_type, + collaboration_type=channel.channel_type, + timestamp=datetime.now(), + work_phase=self.current_phase, + priority=priority, + attachments=attachments or [], + tags=tags or [] + ) + + # 添加到频道历史 + channel.message_history.append(message) + channel.last_activity = datetime.now() + + # 添加到消息队列 + self.message_queue.append(message) + + # 触发事件处理 + await self._trigger_event("message_sent", { + "message": message, + "channel": channel + }) + + # 记录日志 + self.logger.info(f"[{channel.name}] {sender.value} -> {receiver.value if receiver else 'ALL'}: {content[:50]}...") + + return message.id + + async def broadcast_message(self, + sender: AIRole, + content: str, + message_type: MessageType, + channel_id: str, + priority: int = 1, + tags: List[str] = None) -> str: + """广播消息到频道所有参与者""" + return await self.send_message( + sender=sender, + content=content, + message_type=message_type, + channel_id=channel_id, + receiver=None, # None表示广播 + priority=priority, + tags=tags + ) + + async def request_review(self, + sender: AIRole, + content: str, + reviewers: List[AIRole], + attachments: List[str] = None) -> str: + """请求评审""" + + # 发送到交叉评审频道 + message_id = await self.send_message( + sender=sender, + content=f"📋 评审请求: {content}", + message_type=MessageType.REVIEW, + channel_id="cross_review", + priority=3, + attachments=attachments, + tags=["review_request"] + [f"reviewer_{reviewer.value}" for reviewer in reviewers] + ) + + # 通知指定评审者 + for reviewer in reviewers: + await self.send_message( + sender=AIRole.ROVODEV, # 系统通知 + content=f"🔔 您有新的评审请求来自 {sender.value},请查看交叉评审频道", + message_type=MessageType.ALERT, + channel_id="main_collaboration", + receiver=reviewer, + priority=3, + tags=["review_notification", f"from_{sender.value}", f"message_ref_{message_id}"] + ) + + return message_id + + async def handoff_work(self, + from_ai: AIRole, + to_ai: AIRole, + task_description: str, + deliverables: List[str], + notes: str = "") -> str: + """工作交接""" + + content = f""" +🔄 **工作交接** +**从**: {from_ai.value} +**到**: {to_ai.value} +**任务**: {task_description} +**交付物**: {', '.join(deliverables)} +**备注**: {notes} + """ + + message_id = await self.send_message( + sender=from_ai, + content=content.strip(), + message_type=MessageType.HANDOFF, + channel_id="main_collaboration", + receiver=to_ai, + priority=4, + attachments=deliverables, + tags=["handoff", f"from_{from_ai.value}", f"to_{to_ai.value}"] + ) + + # 更新AI状态 + self.ai_status[from_ai]["status"] = "completed_handoff" + self.ai_status[to_ai]["status"] = "received_handoff" + self.ai_status[to_ai]["current_task"] = task_description + + return message_id + + async def escalate_issue(self, + reporter: AIRole, + issue_description: str, + severity: str = "medium") -> str: + """问题升级""" + + content = f""" +🚨 **问题升级** +**报告者**: {reporter.value} +**严重程度**: {severity} +**问题描述**: {issue_description} +**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + """ + + priority_map = {"low": 2, "medium": 3, "high": 4, "critical": 5} + priority = priority_map.get(severity, 3) + + return await self.send_message( + sender=reporter, + content=content.strip(), + message_type=MessageType.ALERT, + channel_id="emergency_coordination", + priority=priority, + tags=["escalation", f"severity_{severity}"] + ) + + def get_channel_summary(self, channel_id: str) -> Dict[str, Any]: + """获取频道摘要""" + if channel_id not in self.channels: + return {} + + channel = self.channels[channel_id] + recent_messages = channel.message_history[-10:] # 最近10条消息 + + return { + "channel_name": channel.name, + "channel_type": channel.channel_type.value, + "participants": [ai.value for ai in channel.participants], + "total_messages": len(channel.message_history), + "last_activity": channel.last_activity.isoformat(), + "recent_messages": [ + { + "sender": msg.sender.value, + "content": msg.content[:100] + "..." if len(msg.content) > 100 else msg.content, + "timestamp": msg.timestamp.isoformat(), + "type": msg.message_type.value + } + for msg in recent_messages + ] + } + + def get_ai_dashboard(self, ai_role: AIRole) -> Dict[str, Any]: + """获取AI工作仪表板""" + status = self.ai_status[ai_role] + + # 获取相关消息 + relevant_messages = [] + for channel in self.channels.values(): + if ai_role in channel.participants: + for msg in channel.message_history[-5:]: # 每个频道最近5条 + if msg.receiver == ai_role or msg.receiver is None: + relevant_messages.append({ + "channel": channel.name, + "sender": msg.sender.value, + "content": msg.content[:100] + "..." if len(msg.content) > 100 else msg.content, + "timestamp": msg.timestamp.isoformat(), + "priority": msg.priority + }) + + # 按优先级和时间排序 + relevant_messages.sort(key=lambda x: (x["priority"], x["timestamp"]), reverse=True) + + return { + "ai_role": ai_role.value, + "status": status, + "current_phase": self.current_phase.value, + "active_channels": [ + channel.name for channel in self.channels.values() + if ai_role in channel.participants and channel.is_active + ], + "recent_messages": relevant_messages[:10], # 最多10条 + "pending_tasks": self._get_pending_tasks(ai_role), + "collaboration_stats": self._get_collaboration_stats(ai_role) + } + + def _get_pending_tasks(self, ai_role: AIRole) -> List[Dict[str, Any]]: + """获取待处理任务""" + tasks = [] + + # 扫描所有频道中针对该AI的消息 + for channel in self.channels.values(): + if ai_role in channel.participants: + for msg in channel.message_history: + if (msg.receiver == ai_role and + msg.message_type in [MessageType.QUESTION, MessageType.REVIEW, MessageType.HANDOFF] and + not self._is_task_completed(msg.id)): + tasks.append({ + "task_id": msg.id, + "type": msg.message_type.value, + "description": msg.content[:100] + "..." if len(msg.content) > 100 else msg.content, + "from": msg.sender.value, + "channel": channel.name, + "priority": msg.priority, + "created": msg.timestamp.isoformat() + }) + + return sorted(tasks, key=lambda x: x["priority"], reverse=True) + + def _get_collaboration_stats(self, ai_role: AIRole) -> Dict[str, Any]: + """获取协作统计""" + total_messages = 0 + messages_sent = 0 + messages_received = 0 + + for channel in self.channels.values(): + if ai_role in channel.participants: + for msg in channel.message_history: + total_messages += 1 + if msg.sender == ai_role: + messages_sent += 1 + elif msg.receiver == ai_role or msg.receiver is None: + messages_received += 1 + + return { + "total_messages": total_messages, + "messages_sent": messages_sent, + "messages_received": messages_received, + "active_channels": len([c for c in self.channels.values() if ai_role in c.participants]), + "collaboration_score": min(100, (messages_sent + messages_received) * 2) # 简单计分 + } + + def _is_task_completed(self, task_id: str) -> bool: + """检查任务是否已完成""" + # 简单实现:检查是否有回复消息引用了该任务 + for channel in self.channels.values(): + for msg in channel.message_history: + if task_id in msg.references: + return True + return False + + async def _trigger_event(self, event_type: str, event_data: Dict[str, Any]): + """触发事件处理""" + if event_type in self.event_handlers: + for handler in self.event_handlers[event_type]: + try: + await handler(event_data) + except Exception as e: + self.logger.error(f"事件处理器错误: {e}") + + def add_event_handler(self, event_type: str, handler: Callable): + """添加事件处理器""" + if event_type not in self.event_handlers: + self.event_handlers[event_type] = [] + self.event_handlers[event_type].append(handler) + + async def advance_phase(self, new_phase: WorkPhase): + """推进工作阶段""" + old_phase = self.current_phase + self.current_phase = new_phase + + # 广播阶段变更 + await self.broadcast_message( + sender=AIRole.ROVODEV, + content=f"📈 项目阶段变更: {old_phase.value} → {new_phase.value}", + message_type=MessageType.UPDATE, + channel_id="main_collaboration", + priority=4, + tags=["phase_change"] + ) + + # 触发工作流规则 + await self._check_workflow_rules() + + async def _check_workflow_rules(self): + """检查并执行工作流规则""" + for rule in self.workflow_rules.values(): + if rule.is_active and rule.trigger_phase == self.current_phase: + await self._execute_workflow_action(rule) + + async def _execute_workflow_action(self, rule: WorkflowRule): + """执行工作流动作""" + if rule.action == "notify_implementation_start": + await self.send_message( + sender=AIRole.ROVODEV, + content=f"🚀 架构设计已完成,请开始代码实现工作。参考架构文档进行开发。", + message_type=MessageType.UPDATE, + channel_id="code_implementation", + receiver=rule.target_ai, + priority=3 + ) + elif rule.action == "notify_testing_start": + await self.send_message( + sender=AIRole.ROVODEV, + content=f"✅ 代码实现已完成,请开始测试验证工作。", + message_type=MessageType.UPDATE, + channel_id="testing_validation", + receiver=rule.target_ai, + priority=3 + ) + elif rule.action == "notify_integration_start": + await self.send_message( + sender=AIRole.ROVODEV, + content=f"🎯 测试验证已完成,请开始项目整合工作。", + message_type=MessageType.UPDATE, + channel_id="project_integration", + receiver=rule.target_ai, + priority=3 + ) + +# 使用示例 +async def demo_collaboration(): + """演示协作系统使用""" + collab = AITeamCollaboration() + + # Qwen发起架构讨论 + await collab.send_message( + sender=AIRole.QWEN, + content="大家好!我已经完成了OpenBB集成的初步架构设计,请大家review一下设计文档。", + message_type=MessageType.PROPOSAL, + channel_id="main_collaboration", + priority=3, + attachments=["docs/architecture/openbb_integration_architecture.md"], + tags=["architecture", "review_request"] + ) + + # Claude回应 + await collab.send_message( + sender=AIRole.CLAUDE, + content="架构设计看起来很不错!我有几个实现层面的问题...", + message_type=MessageType.QUESTION, + channel_id="architecture_design", + receiver=AIRole.QWEN, + priority=2 + ) + + # 工作交接 + await collab.handoff_work( + from_ai=AIRole.QWEN, + to_ai=AIRole.CLAUDE, + task_description="基于架构设计实现OpenBB核心引擎", + deliverables=["src/jixia/engines/enhanced_openbb_engine.py"], + notes="请特别注意八仙数据路由的实现" + ) + + # 获取仪表板 + dashboard = collab.get_ai_dashboard(AIRole.CLAUDE) + print(f"Claude的工作仪表板: {json.dumps(dashboard, indent=2, ensure_ascii=False)}") + +if __name__ == "__main__": + # 设置日志 + logging.basicConfig(level=logging.INFO) + + # 运行演示 + asyncio.run(demo_collaboration()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/coordination/multi_chat_coordinator.py b/jixia_academy/core/debate_system/coordination/multi_chat_coordinator.py new file mode 100644 index 0000000..a46a914 --- /dev/null +++ b/jixia_academy/core/debate_system/coordination/multi_chat_coordinator.py @@ -0,0 +1,685 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +多群聊协调系统 +管理主辩论群、内部讨论群、策略会议群和Human干预群之间的协调 +""" + +import asyncio +import json +from typing import Dict, List, Any, Optional, Callable +from dataclasses import dataclass, field +from enum import Enum +from datetime import datetime, timedelta +import logging + +class ChatType(Enum): + """群聊类型""" + MAIN_DEBATE = "主辩论群" # 公开辩论 + INTERNAL_DISCUSSION = "内部讨论群" # 团队内部讨论 + STRATEGY_MEETING = "策略会议群" # 策略制定 + HUMAN_INTERVENTION = "Human干预群" # 人工干预 + OBSERVATION = "观察群" # 观察和记录 + +class MessagePriority(Enum): + """消息优先级""" + LOW = 1 + NORMAL = 2 + HIGH = 3 + URGENT = 4 + CRITICAL = 5 + +class CoordinationAction(Enum): + """协调动作""" + ESCALATE = "升级" # 升级到更高级别群聊 + DELEGATE = "委派" # 委派到专门群聊 + BROADCAST = "广播" # 广播到多个群聊 + FILTER = "过滤" # 过滤不相关消息 + MERGE = "合并" # 合并相关讨论 + ARCHIVE = "归档" # 归档历史讨论 + +@dataclass +class ChatMessage: + """群聊消息""" + id: str + chat_type: ChatType + sender: str + content: str + timestamp: datetime + priority: MessagePriority = MessagePriority.NORMAL + tags: List[str] = field(default_factory=list) + related_messages: List[str] = field(default_factory=list) + metadata: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class ChatRoom: + """群聊房间""" + id: str + chat_type: ChatType + name: str + description: str + participants: List[str] = field(default_factory=list) + moderators: List[str] = field(default_factory=list) + is_active: bool = True + created_at: datetime = field(default_factory=datetime.now) + last_activity: datetime = field(default_factory=datetime.now) + message_history: List[ChatMessage] = field(default_factory=list) + settings: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class CoordinationRule: + """协调规则""" + id: str + name: str + description: str + source_chat_types: List[ChatType] + target_chat_types: List[ChatType] + trigger_conditions: Dict[str, Any] + action: CoordinationAction + priority: int = 1 + is_active: bool = True + created_at: datetime = field(default_factory=datetime.now) + +class MultiChatCoordinator: + """多群聊协调器""" + + def __init__(self): + self.chat_rooms: Dict[str, ChatRoom] = {} + self.coordination_rules: Dict[str, CoordinationRule] = {} + self.message_queue: List[ChatMessage] = [] + self.event_handlers: Dict[str, List[Callable]] = {} + self.logger = logging.getLogger(__name__) + + # 初始化默认群聊房间 + self._initialize_default_rooms() + + # 初始化默认协调规则 + self._initialize_default_rules() + + def _initialize_default_rooms(self): + """初始化默认群聊房间""" + default_rooms = [ + { + "id": "main_debate", + "chat_type": ChatType.MAIN_DEBATE, + "name": "主辩论群", + "description": "公开辩论的主要场所", + "participants": ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"], + "moderators": ["系统"], + "settings": { + "max_message_length": 500, + "speaking_time_limit": 120, # 秒 + "auto_moderation": True + } + }, + { + "id": "positive_internal", + "chat_type": ChatType.INTERNAL_DISCUSSION, + "name": "正方内部讨论群", + "description": "正方团队内部策略讨论", + "participants": ["正1", "正2", "正3", "正4"], + "moderators": ["正1"], + "settings": { + "privacy_level": "high", + "auto_archive": True + } + }, + { + "id": "negative_internal", + "chat_type": ChatType.INTERNAL_DISCUSSION, + "name": "反方内部讨论群", + "description": "反方团队内部策略讨论", + "participants": ["反1", "反2", "反3", "反4"], + "moderators": ["反1"], + "settings": { + "privacy_level": "high", + "auto_archive": True + } + }, + { + "id": "strategy_meeting", + "chat_type": ChatType.STRATEGY_MEETING, + "name": "策略会议群", + "description": "高级策略制定和决策", + "participants": ["正1", "反1", "系统"], + "moderators": ["系统"], + "settings": { + "meeting_mode": True, + "record_decisions": True + } + }, + { + "id": "human_intervention", + "chat_type": ChatType.HUMAN_INTERVENTION, + "name": "Human干预群", + "description": "人工干预和监督", + "participants": ["Human", "系统"], + "moderators": ["Human"], + "settings": { + "alert_threshold": "high", + "auto_escalation": True + } + }, + { + "id": "observation", + "chat_type": ChatType.OBSERVATION, + "name": "观察群", + "description": "观察和记录所有活动", + "participants": ["观察者", "记录员"], + "moderators": ["系统"], + "settings": { + "read_only": True, + "full_logging": True + } + } + ] + + for room_config in default_rooms: + room = ChatRoom(**room_config) + self.chat_rooms[room.id] = room + + def _initialize_default_rules(self): + """初始化默认协调规则""" + default_rules = [ + { + "id": "escalate_urgent_to_human", + "name": "紧急情况升级到Human", + "description": "当检测到紧急情况时,自动升级到Human干预群", + "source_chat_types": [ChatType.MAIN_DEBATE, ChatType.INTERNAL_DISCUSSION], + "target_chat_types": [ChatType.HUMAN_INTERVENTION], + "trigger_conditions": { + "priority": MessagePriority.URGENT, + "keywords": ["紧急", "错误", "异常", "停止"] + }, + "action": CoordinationAction.ESCALATE, + "priority": 1 + }, + { + "id": "strategy_to_internal", + "name": "策略决策分发到内部群", + "description": "将策略会议的决策分发到相关内部讨论群", + "source_chat_types": [ChatType.STRATEGY_MEETING], + "target_chat_types": [ChatType.INTERNAL_DISCUSSION], + "trigger_conditions": { + "tags": ["决策", "策略", "指令"] + }, + "action": CoordinationAction.BROADCAST, + "priority": 2 + }, + { + "id": "filter_noise", + "name": "过滤噪音消息", + "description": "过滤低质量或无关的消息", + "source_chat_types": [ChatType.MAIN_DEBATE], + "target_chat_types": [], + "trigger_conditions": { + "priority": MessagePriority.LOW, + "content_length": {"max": 10} + }, + "action": CoordinationAction.FILTER, + "priority": 3 + }, + { + "id": "archive_old_discussions", + "name": "归档旧讨论", + "description": "自动归档超过时间限制的讨论", + "source_chat_types": [ChatType.INTERNAL_DISCUSSION], + "target_chat_types": [ChatType.OBSERVATION], + "trigger_conditions": { + "age_hours": 24, + "inactivity_hours": 2 + }, + "action": CoordinationAction.ARCHIVE, + "priority": 4 + } + ] + + for rule_config in default_rules: + rule = CoordinationRule(**rule_config) + self.coordination_rules[rule.id] = rule + + async def send_message(self, chat_id: str, sender: str, content: str, + priority: MessagePriority = MessagePriority.NORMAL, + tags: List[str] = None) -> ChatMessage: + """发送消息到指定群聊""" + if chat_id not in self.chat_rooms: + raise ValueError(f"群聊 {chat_id} 不存在") + + chat_room = self.chat_rooms[chat_id] + + # 检查发送者权限(系统用户有特殊权限) + if sender != "系统" and sender not in chat_room.participants and sender not in chat_room.moderators: + raise PermissionError(f"用户 {sender} 没有权限在群聊 {chat_id} 中发言") + + # 创建消息 + message = ChatMessage( + id=f"{chat_id}_{datetime.now().timestamp()}", + chat_type=chat_room.chat_type, + sender=sender, + content=content, + timestamp=datetime.now(), + priority=priority, + tags=tags or [] + ) + + # 添加到群聊历史 + chat_room.message_history.append(message) + chat_room.last_activity = datetime.now() + + # 添加到消息队列进行协调处理 + self.message_queue.append(message) + + # 触发事件处理 + await self._trigger_event_handlers("message_sent", message) + + # 处理协调规则 + await self._process_coordination_rules(message) + + self.logger.info(f"消息已发送到 {chat_id}: {sender} - {content[:50]}...") + + return message + + async def _process_coordination_rules(self, message: ChatMessage): + """处理协调规则""" + for rule in self.coordination_rules.values(): + if not rule.is_active: + continue + + # 检查源群聊类型 + if message.chat_type not in rule.source_chat_types: + continue + + # 检查触发条件 + if await self._check_trigger_conditions(message, rule.trigger_conditions): + await self._execute_coordination_action(message, rule) + + async def _check_trigger_conditions(self, message: ChatMessage, conditions: Dict[str, Any]) -> bool: + """检查触发条件""" + # 检查优先级 + if "priority" in conditions: + if message.priority != conditions["priority"]: + return False + + # 检查关键词 + if "keywords" in conditions: + keywords = conditions["keywords"] + if not any(keyword in message.content for keyword in keywords): + return False + + # 检查标签 + if "tags" in conditions: + required_tags = conditions["tags"] + if not any(tag in message.tags for tag in required_tags): + return False + + # 检查内容长度 + if "content_length" in conditions: + length_rules = conditions["content_length"] + content_length = len(message.content) + + if "min" in length_rules and content_length < length_rules["min"]: + return False + if "max" in length_rules and content_length > length_rules["max"]: + return False + + # 检查消息年龄 + if "age_hours" in conditions: + age_limit = timedelta(hours=conditions["age_hours"]) + if datetime.now() - message.timestamp > age_limit: + return True + + return True + + async def _execute_coordination_action(self, message: ChatMessage, rule: CoordinationRule): + """执行协调动作""" + action = rule.action + + if action == CoordinationAction.ESCALATE: + await self._escalate_message(message, rule.target_chat_types) + + elif action == CoordinationAction.BROADCAST: + await self._broadcast_message(message, rule.target_chat_types) + + elif action == CoordinationAction.FILTER: + await self._filter_message(message) + + elif action == CoordinationAction.ARCHIVE: + await self._archive_message(message, rule.target_chat_types) + + elif action == CoordinationAction.DELEGATE: + await self._delegate_message(message, rule.target_chat_types) + + elif action == CoordinationAction.MERGE: + await self._merge_discussions(message) + + self.logger.info(f"执行协调动作 {action.value} for message {message.id}") + + async def _escalate_message(self, message: ChatMessage, target_chat_types: List[ChatType]): + """升级消息到更高级别群聊""" + for chat_type in target_chat_types: + target_rooms = [room for room in self.chat_rooms.values() + if room.chat_type == chat_type and room.is_active] + + for room in target_rooms: + escalated_content = f"🚨 [升级消息] 来自 {message.chat_type.value}\n" \ + f"发送者: {message.sender}\n" \ + f"内容: {message.content}\n" \ + f"时间: {message.timestamp}" + + await self.send_message( + room.id, "系统", escalated_content, + MessagePriority.URGENT, ["升级", "自动"] + ) + + async def _broadcast_message(self, message: ChatMessage, target_chat_types: List[ChatType]): + """广播消息到多个群聊""" + for chat_type in target_chat_types: + target_rooms = [room for room in self.chat_rooms.values() + if room.chat_type == chat_type and room.is_active] + + for room in target_rooms: + broadcast_content = f"📢 [广播消息] 来自 {message.chat_type.value}\n" \ + f"{message.content}" + + await self.send_message( + room.id, "系统", broadcast_content, + message.priority, message.tags + ["广播"] + ) + + async def _filter_message(self, message: ChatMessage): + """过滤消息""" + # 标记消息为已过滤 + message.metadata["filtered"] = True + message.metadata["filter_reason"] = "低质量或无关内容" + + self.logger.info(f"消息 {message.id} 已被过滤") + + async def _archive_message(self, message: ChatMessage, target_chat_types: List[ChatType]): + """归档消息""" + for chat_type in target_chat_types: + target_rooms = [room for room in self.chat_rooms.values() + if room.chat_type == chat_type and room.is_active] + + for room in target_rooms: + archive_content = f"📁 [归档消息] 来自 {message.chat_type.value}\n" \ + f"原始内容: {message.content}\n" \ + f"归档时间: {datetime.now()}" + + await self.send_message( + room.id, "系统", archive_content, + MessagePriority.LOW, ["归档", "历史"] + ) + + async def _delegate_message(self, message: ChatMessage, target_chat_types: List[ChatType]): + """委派消息到专门群聊""" + # 类似于广播,但会移除原消息 + await self._broadcast_message(message, target_chat_types) + + # 标记原消息为已委派 + message.metadata["delegated"] = True + + async def _merge_discussions(self, message: ChatMessage): + """合并相关讨论""" + # 查找相关消息 + related_messages = self._find_related_messages(message) + + # 创建合并讨论摘要 + if related_messages: + summary = self._create_discussion_summary(message, related_messages) + + # 发送摘要到策略会议群 + strategy_rooms = [room for room in self.chat_rooms.values() + if room.chat_type == ChatType.STRATEGY_MEETING] + + for room in strategy_rooms: + await self.send_message( + room.id, "系统", summary, + MessagePriority.HIGH, ["合并", "摘要"] + ) + + def _find_related_messages(self, message: ChatMessage) -> List[ChatMessage]: + """查找相关消息""" + related = [] + + # 简单的相关性检测:相同标签或关键词 + for room in self.chat_rooms.values(): + for msg in room.message_history[-10:]: # 检查最近10条消息 + if msg.id != message.id: + # 检查标签重叠 + if set(msg.tags) & set(message.tags): + related.append(msg) + + # 检查内容相似性(简单关键词匹配) + elif self._calculate_content_similarity(msg.content, message.content) > 0.3: + related.append(msg) + + return related + + def _calculate_content_similarity(self, content1: str, content2: str) -> float: + """计算内容相似性""" + words1 = set(content1.split()) + words2 = set(content2.split()) + + if not words1 or not words2: + return 0.0 + + intersection = words1 & words2 + union = words1 | words2 + + return len(intersection) / len(union) + + def _create_discussion_summary(self, main_message: ChatMessage, related_messages: List[ChatMessage]) -> str: + """创建讨论摘要""" + summary = f"📋 讨论摘要\n" + summary += f"主要消息: {main_message.sender} - {main_message.content[:100]}...\n" + summary += f"相关消息数量: {len(related_messages)}\n\n" + + summary += "相关讨论:\n" + for i, msg in enumerate(related_messages[:5], 1): # 最多显示5条 + summary += f"{i}. {msg.sender}: {msg.content[:50]}...\n" + + return summary + + async def _trigger_event_handlers(self, event_type: str, data: Any): + """触发事件处理器""" + if event_type in self.event_handlers: + for handler in self.event_handlers[event_type]: + try: + await handler(data) + except Exception as e: + self.logger.error(f"事件处理器错误: {e}") + + def add_event_handler(self, event_type: str, handler: Callable): + """添加事件处理器""" + if event_type not in self.event_handlers: + self.event_handlers[event_type] = [] + self.event_handlers[event_type].append(handler) + + async def handle_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]: + """处理消息(兼容性方法)""" + try: + chat_id = message_data.get("chat_id", "main_debate") + speaker = message_data.get("speaker", "未知用户") + content = message_data.get("content", "") + priority = MessagePriority.NORMAL + + # 发送消息 + message = await self.send_message(chat_id, speaker, content, priority) + + return { + "success": True, + "message_id": message.id, + "processed_at": datetime.now().isoformat() + } + except Exception as e: + self.logger.error(f"处理消息失败: {e}") + return { + "success": False, + "error": str(e), + "processed_at": datetime.now().isoformat() + } + + def get_routing_status(self) -> Dict[str, Any]: + """获取路由状态(兼容性方法)""" + return { + "active_routes": len(self.coordination_rules), + "message_queue_size": len(self.message_queue), + "total_rooms": len(self.chat_rooms) + } + + async def coordinate_response(self, message_data: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]: + """协调响应(兼容性方法)""" + try: + # 基于上下文决定响应策略 + stage = context.get("stage", "起") + topic = context.get("topic", "未知主题") + + # 模拟协调决策 + coordination_decision = { + "recommended_action": "继续讨论", + "target_chat": "main_debate", + "priority": "normal", + "reasoning": f"基于当前阶段({stage})和主题({topic})的协调决策" + } + + return { + "success": True, + "coordination": coordination_decision, + "timestamp": datetime.now().isoformat() + } + except Exception as e: + return { + "success": False, + "error": str(e), + "timestamp": datetime.now().isoformat() + } + + def get_chat_status(self) -> Dict[str, Any]: + """获取群聊状态""" + status = { + "total_rooms": len(self.chat_rooms), + "active_rooms": len([r for r in self.chat_rooms.values() if r.is_active]), + "total_messages": sum(len(r.message_history) for r in self.chat_rooms.values()), + "pending_messages": len(self.message_queue), + "coordination_rules": len(self.coordination_rules), + "active_rules": len([r for r in self.coordination_rules.values() if r.is_active]), + "rooms": { + room_id: { + "name": room.name, + "type": room.chat_type.value, + "participants": len(room.participants), + "messages": len(room.message_history), + "last_activity": room.last_activity.isoformat(), + "is_active": room.is_active + } + for room_id, room in self.chat_rooms.items() + } + } + + return status + + def save_coordination_data(self, filename: str = "coordination_data.json"): + """保存协调数据""" + # 自定义JSON序列化函数 + def serialize_trigger_conditions(conditions): + serialized = {} + for key, value in conditions.items(): + if isinstance(value, MessagePriority): + serialized[key] = value.value + else: + serialized[key] = value + return serialized + + data = { + "chat_rooms": { + room_id: { + "id": room.id, + "chat_type": room.chat_type.value, + "name": room.name, + "description": room.description, + "participants": room.participants, + "moderators": room.moderators, + "is_active": room.is_active, + "created_at": room.created_at.isoformat(), + "last_activity": room.last_activity.isoformat(), + "settings": room.settings, + "message_count": len(room.message_history) + } + for room_id, room in self.chat_rooms.items() + }, + "coordination_rules": { + rule_id: { + "id": rule.id, + "name": rule.name, + "description": rule.description, + "source_chat_types": [ct.value for ct in rule.source_chat_types], + "target_chat_types": [ct.value for ct in rule.target_chat_types], + "trigger_conditions": serialize_trigger_conditions(rule.trigger_conditions), + "action": rule.action.value, + "priority": rule.priority, + "is_active": rule.is_active, + "created_at": rule.created_at.isoformat() + } + for rule_id, rule in self.coordination_rules.items() + }, + "status": self.get_chat_status(), + "export_time": datetime.now().isoformat() + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + self.logger.info(f"协调数据已保存到 {filename}") + +# 使用示例 +async def main(): + """使用示例""" + coordinator = MultiChatCoordinator() + + # 发送一些测试消息 + await coordinator.send_message( + "main_debate", "正1", + "我认为AI投资具有巨大的潜力和价值", + MessagePriority.NORMAL, ["观点", "AI"] + ) + + await coordinator.send_message( + "main_debate", "反1", + "但是AI投资的风险也不容忽视", + MessagePriority.NORMAL, ["反驳", "风险"] + ) + + await coordinator.send_message( + "positive_internal", "正2", + "我们需要准备更强有力的数据支持", + MessagePriority.HIGH, ["策略", "数据"] + ) + + # 模拟紧急情况 + await coordinator.send_message( + "main_debate", "正3", + "系统出现异常,需要紧急处理", + MessagePriority.URGENT, ["紧急", "系统"] + ) + + # 显示状态 + status = coordinator.get_chat_status() + print("\n📊 群聊协调系统状态:") + print(f"总群聊数: {status['total_rooms']}") + print(f"活跃群聊数: {status['active_rooms']}") + print(f"总消息数: {status['total_messages']}") + print(f"待处理消息: {status['pending_messages']}") + + print("\n📋 群聊详情:") + for room_id, room_info in status['rooms'].items(): + print(f" {room_info['name']} ({room_info['type']})") + print(f" 参与者: {room_info['participants']} 人") + print(f" 消息数: {room_info['messages']}") + print(f" 最后活动: {room_info['last_activity']}") + print() + + # 保存数据 + coordinator.save_coordination_data() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/debates/adk_memory_debate.py b/jixia_academy/core/debate_system/debates/adk_memory_debate.py new file mode 100644 index 0000000..d37d173 --- /dev/null +++ b/jixia_academy/core/debate_system/debates/adk_memory_debate.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫 ADK Memory Bank 论道系统 +实现带有记忆银行的八仙智能体辩论 +""" + +import os +import asyncio +from google.adk import Agent, Runner +from google.adk.sessions import InMemorySessionService +from google.adk.memory import VertexAiMemoryBankService +from google.adk.memory.memory_entry import MemoryEntry +from google.genai import types +import json +from datetime import datetime +from typing import Dict, List, Optional + +class BaxianMemoryManager: + """八仙记忆管理器""" + + def __init__(self): + self.memory_services: Dict[str, VertexAiMemoryBankService] = {} + self.agents: Dict[str, Agent] = {} + + async def initialize_baxian_agents(self): + """初始化八仙智能体及其记忆银行""" + # 从环境变量获取项目ID和位置 + project_id = os.getenv('GOOGLE_CLOUD_PROJECT_ID') + location = os.getenv('GOOGLE_CLOUD_LOCATION', 'us-central1') + + if not project_id: + raise ValueError("未设置 GOOGLE_CLOUD_PROJECT_ID 环境变量") + + # 八仙角色配置 + baxian_config = { + "铁拐李": { + "instruction": "你是铁拐李,八仙中的逆向思维专家。你善于从批判和质疑的角度看问题,总是能发现事物的另一面。你会从你的记忆中回忆相关的逆向投资案例和失败教训。", + "memory_context": "逆向投资案例、市场泡沫警告、风险识别经验" + }, + "吕洞宾": { + "instruction": "你是吕洞宾,八仙中的理性分析者。你善于平衡各方观点,用理性和逻辑来分析问题。你会从记忆中调用技术分析的成功案例和理论知识。", + "memory_context": "技术分析理论、成功预测案例、市场趋势分析" + }, + "何仙姑": { + "instruction": "你是何仙姑,八仙中的风险控制专家。你总是从风险管理的角度思考问题,善于发现潜在危险。你会回忆历史上的重大风险事件。", + "memory_context": "风险管理案例、黑天鹅事件、危机预警经验" + }, + "张果老": { + "instruction": "你是张果老,八仙中的历史智慧者。你善于从历史数据中寻找规律和智慧,总是能提供长期视角。你会从记忆中调用历史数据和长期趋势。", + "memory_context": "历史市场数据、长期投资趋势、周期性规律" + } + } + + # 为每个仙人创建智能体和记忆服务 + for name, config in baxian_config.items(): + # 创建记忆服务 + memory_service = VertexAiMemoryBankService( + project=project_id, + location=location + ) + + # 初始化记忆内容 + await self._initialize_agent_memory(memory_service, name, config['memory_context']) + + # 创建智能体 + agent = Agent( + name=name, + model="gemini-2.5-flash", + instruction=f"{config['instruction']} 在回答时,请先从你的记忆银行中检索相关信息,然后结合当前话题给出回应。", + memory_service=memory_service + ) + + self.memory_services[name] = memory_service + self.agents[name] = agent + + print(f"✅ 已初始化 {len(self.agents)} 个八仙智能体及其记忆服务") + + async def _initialize_agent_memory(self, memory_service: VertexAiMemoryBankService, agent_name: str, context: str): + """为智能体初始化记忆内容""" + + # 根据角色添加初始记忆 + initial_memories = { + "铁拐李": [ + "2000年互联网泡沫破裂,许多高估值科技股暴跌90%以上", + "2008年金融危机前,房地产市场过度繁荣,逆向思维者提前撤离", + "比特币从2万美元跌到3千美元,提醒我们任何资产都可能大幅回调", + "巴菲特说过:别人贪婪时我恐惧,别人恐惧时我贪婪" + ], + "吕洞宾": [ + "移动平均线交叉是经典的技术分析信号", + "RSI指标超过70通常表示超买,低于30表示超卖", + "支撑位和阻力位是技术分析的核心概念", + "成功的技术分析需要结合多个指标综合判断" + ], + "何仙姑": [ + "2008年雷曼兄弟倒闭引发全球金融危机", + "长期资本管理公司(LTCM)的失败说明了风险管理的重要性", + "分散投资是降低风险的基本原则", + "黑天鹅事件虽然罕见但影响巨大,需要提前准备" + ], + "张果老": [ + "股市存在7-10年的长期周期", + "康德拉季耶夫长波理论描述了50-60年的经济周期", + "历史上每次重大技术革命都带来新的投资机会", + "长期来看,优质资产总是向上的" + ] + } + + memories = initial_memories.get(agent_name, []) + for memory_text in memories: + memory_entry = MemoryEntry( + content=memory_text, + metadata={ + "agent": agent_name, + "type": "historical_knowledge", + "timestamp": datetime.now().isoformat() + } + ) + # 注意:VertexAiMemoryBankService 的 add_memory 方法可能需要不同的参数 + # 这里假设它有一个类似的方法 + await memory_service.add_memory(memory_entry) + + async def add_debate_memory(self, agent_name: str, content: str, topic: str): + """为智能体添加辩论记忆""" + if agent_name in self.memory_services: + memory_entry = MemoryEntry( + content=content, + metadata={ + "agent": agent_name, + "type": "debate_history", + "topic": topic, + "timestamp": datetime.now().isoformat() + } + ) + # 注意:VertexAiMemoryBankService 的 add_memory 方法可能需要不同的参数 + # 这里假设它有一个类似的方法 + await self.memory_services[agent_name].add_memory(memory_entry) + + async def retrieve_relevant_memories(self, agent_name: str, query: str, limit: int = 3) -> List[str]: + """检索智能体的相关记忆""" + if agent_name not in self.memory_services: + return [] + + try: + # 注意:VertexAiMemoryBankService 的 search 方法可能需要不同的参数 + # 这里假设它有一个类似的方法 + memories = await self.memory_services[agent_name].search(query, limit=limit) + return [memory.content for memory in memories] + except Exception as e: + print(f"⚠️ 记忆检索失败 ({agent_name}): {e}") + return [] + +class MemoryEnhancedDebate: + """带记忆增强的辩论系统""" + + def __init__(self): + self.memory_manager = BaxianMemoryManager() + self.session_service = InMemorySessionService() + self.runners: Dict[str, Runner] = {} + + async def initialize(self): + """初始化辩论系统""" + await self.memory_manager.initialize_baxian_agents() + + # 创建会话 + self.session = await self.session_service.create_session( + state={}, + app_name="稷下学宫记忆增强论道系统", + user_id="memory_debate_user" + ) + + # 为每个智能体创建Runner + for name, agent in self.memory_manager.agents.items(): + runner = Runner( + app_name="稷下学宫记忆增强论道系统", + agent=agent, + session_service=self.session_service + ) + self.runners[name] = runner + + async def conduct_memory_debate(self, topic: str, participants: List[str] = None): + """进行带记忆的辩论""" + if participants is None: + participants = ["铁拐李", "吕洞宾", "何仙姑", "张果老"] + + print(f"\n🎭 稷下学宫记忆增强论道开始...") + print(f"📋 论道主题: {topic}") + print(f"🎯 参与仙人: {', '.join(participants)}") + + debate_history = [] + + for round_num in range(2): # 进行2轮辩论 + print(f"\n🔄 第 {round_num + 1} 轮论道:") + + for participant in participants: + if participant not in self.runners: + continue + + print(f"\n🗣️ {participant} 发言:") + + # 检索相关记忆 + relevant_memories = await self.memory_manager.retrieve_relevant_memories( + participant, topic, limit=2 + ) + + # 构建包含记忆的提示 + memory_context = "" + if relevant_memories: + memory_context = f"\n从你的记忆中回忆到:\n" + "\n".join([f"- {memory}" for memory in relevant_memories]) + + # 构建辩论历史上下文 + history_context = "" + if debate_history: + recent_history = debate_history[-3:] # 最近3条发言 + history_context = f"\n最近的论道内容:\n" + "\n".join([f"- {h}" for h in recent_history]) + + prompt = f"关于'{topic}'这个话题{memory_context}{history_context}\n\n请结合你的记忆和当前讨论,从你的角色特点出发发表观点。请控制在150字以内。" + + # 发送消息并获取回复 + content = types.Content(role='user', parts=[types.Part(text=prompt)]) + response = self.runners[participant].run_async( + user_id=self.session.user_id, + session_id=self.session.id, + new_message=content + ) + + # 收集回复 + reply = "" + async for event in response: + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + reply += str(part.text) + + if reply.strip(): + clean_reply = reply.strip() + print(f" {clean_reply}") + + # 记录到辩论历史 + debate_entry = f"{participant}: {clean_reply}" + debate_history.append(debate_entry) + + # 添加到记忆银行 + await self.memory_manager.add_debate_memory( + participant, clean_reply, topic + ) + + await asyncio.sleep(1) # 避免API调用过快 + + print(f"\n🎉 记忆增强论道完成!") + print(f"📝 本次论道共产生 {len(debate_history)} 条发言,已存储到各仙人的记忆银行中。") + + return debate_history + + async def close(self): + """关闭资源""" + for runner in self.runners.values(): + await runner.close() + +async def main(): + """主函数""" + print("🚀 稷下学宫 ADK Memory Bank 论道系统") + + # 检查API密钥 + api_key = os.getenv('GOOGLE_API_KEY') + if not api_key: + print("❌ 未找到 GOOGLE_API_KEY 环境变量") + print("请使用: doppler run -- python src/jixia/debates/adk_memory_debate.py") + return + + print(f"✅ API密钥已配置") + + # 创建并初始化辩论系统 + debate_system = MemoryEnhancedDebate() + + try: + await debate_system.initialize() + + # 进行辩论 + await debate_system.conduct_memory_debate( + topic="人工智能对投资市场的影响", + participants=["铁拐李", "吕洞宾", "何仙姑", "张果老"] + ) + + except Exception as e: + print(f"❌ 运行失败: {e}") + import traceback + traceback.print_exc() + finally: + await debate_system.close() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/debates/baxian_debate_system.py b/jixia_academy/core/debate_system/debates/baxian_debate_system.py new file mode 100644 index 0000000..a14d3ba --- /dev/null +++ b/jixia_academy/core/debate_system/debates/baxian_debate_system.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫 八仙论道系统 +实现八仙四对矛盾的对角线辩论:男女、老少、富贫、贵贱 +基于先天八卦的智慧对话系统 +""" + +import os +import asyncio +from google.adk import Agent, Runner +from google.adk.sessions import InMemorySessionService +from google.genai import types +import re +import sys +from contextlib import contextmanager + +def create_baxian_agents(): + """创建八仙智能体 - 四对矛盾""" + + # 男女对立:吕洞宾(男)vs 何仙姑(女) + lu_dong_bin = Agent( + name="吕洞宾", + model="gemini-2.5-flash", + instruction="你是吕洞宾,八仙中的男性代表,理性分析者。你代表男性视角,善于逻辑思辨,注重理性和秩序。你的发言风格温和而深刻,总是能找到问题的核心。每次发言控制在80字以内。" + ) + + he_xian_gu = Agent( + name="何仙姑", + model="gemini-2.5-flash", + instruction="你是何仙姑,八仙中的女性代表,感性智慧者。你代表女性视角,善于直觉洞察,注重情感和和谐。你的发言风格柔和而犀利,总是能看到事物的另一面。每次发言控制在80字以内。" + ) + + # 老少对立:张果老(老)vs 韩湘子(少) + zhang_guo_lao = Agent( + name="张果老", + model="gemini-2.5-flash", + instruction="你是张果老,八仙中的长者代表,经验智慧者。你代表老年视角,善于从历史经验出发,注重传统和稳重。你的发言风格深沉而睿智,总是能从历史中汲取教训。每次发言控制在80字以内。" + ) + + han_xiang_zi = Agent( + name="韩湘子", + model="gemini-2.5-flash", + instruction="你是韩湘子,八仙中的青年代表,创新思维者。你代表年轻视角,善于创新思考,注重变革和进步。你的发言风格活泼而敏锐,总是能提出新颖的观点。每次发言控制在80字以内。" + ) + + # 富贫对立:汉钟离(富)vs 蓝采和(贫) + han_zhong_li = Agent( + name="汉钟离", + model="gemini-2.5-flash", + instruction="你是汉钟离,八仙中的富贵代表,资源掌控者。你代表富有阶层视角,善于从资源配置角度思考,注重效率和投资回报。你的发言风格稳重而务实,总是能看到经济利益。每次发言控制在80字以内。" + ) + + lan_cai_he = Agent( + name="蓝采和", + model="gemini-2.5-flash", + instruction="你是蓝采和,八仙中的贫困代表,民生关怀者。你代表普通民众视角,善于从底层角度思考,注重公平和民生。你的发言风格朴实而真诚,总是能关注到弱势群体。每次发言控制在80字以内。" + ) + + # 贵贱对立:曹国舅(贵)vs 铁拐李(贱) + cao_guo_jiu = Agent( + name="曹国舅", + model="gemini-2.5-flash", + instruction="你是曹国舅,八仙中的贵族代表,权力思考者。你代表上层社会视角,善于从权力结构角度分析,注重秩序和等级。你的发言风格优雅而权威,总是能看到政治层面。每次发言控制在80字以内。" + ) + + tie_guai_li = Agent( + name="铁拐李", + model="gemini-2.5-flash", + instruction="你是铁拐李,八仙中的底层代表,逆向思维者。你代表社会底层视角,善于从批判角度质疑,注重真实和反叛。你的发言风格直接而犀利,总是能揭示问题本质。每次发言控制在80字以内。" + ) + + return { + 'male_female': (lu_dong_bin, he_xian_gu), + 'old_young': (zhang_guo_lao, han_xiang_zi), + 'rich_poor': (han_zhong_li, lan_cai_he), + 'noble_humble': (cao_guo_jiu, tie_guai_li) + } + +@contextmanager +def suppress_stdout(): + """抑制标准输出""" + with open(os.devnull, "w") as devnull: + old_stdout = sys.stdout + sys.stdout = devnull + try: + yield + finally: + sys.stdout = old_stdout + +def clean_debug_output(text): + """清理调试输出""" + if not text: + return "" + + # 移除调试信息,但保留实际内容 + lines = text.split('\n') + cleaned_lines = [] + + for line in lines: + line = line.strip() + # 只过滤明确的调试信息,保留实际回复内容 + if any(debug_pattern in line for debug_pattern in + ['Event from', 'API_KEY', 'Both GOOGLE_API_KEY', 'Using GOOGLE_API_KEY']): + continue + if line and not line.startswith('DEBUG') and not line.startswith('INFO'): + cleaned_lines.append(line) + + result = ' '.join(cleaned_lines) + return result if result.strip() else text.strip() + +async def conduct_diagonal_debate(agent1, agent2, topic, perspective1, perspective2, round_num): + """进行对角线辩论""" + print(f"\n🎯 第{round_num}轮对角线辩论:{agent1.name} vs {agent2.name}") + print(f"📋 辩论视角:{perspective1} vs {perspective2}") + + # 设置环境变量以抑制ADK调试输出 + os.environ['GRPC_VERBOSITY'] = 'ERROR' + os.environ['GRPC_TRACE'] = '' + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + + import warnings + warnings.filterwarnings('ignore') + + # 创建会话服务和运行器 + session_service = InMemorySessionService() + + # 创建会话 + session = await session_service.create_session( + state={}, + app_name="稷下学宫八仙论道系统", + user_id="baxian_debate_user" + ) + + # 创建Runner实例 + runner1 = Runner(agent=agent1, session_service=session_service, app_name="稷下学宫八仙论道系统") + runner2 = Runner(agent=agent2, session_service=session_service, app_name="稷下学宫八仙论道系统") + + try: + # 第一轮:agent1 发起 + prompt1 = f"请从{perspective1}的角度,对'{topic}'发表你的观点。要求:观点鲜明,论证有力,体现{perspective1}的特色。" + + content1 = types.Content(role='user', parts=[types.Part(text=prompt1)]) + response1 = runner1.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=content1 + ) + + # 提取回复内容 + agent1_reply = "" + async for event in response1: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent1_reply += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent1_reply += text_content + + print(f"\n🗣️ {agent1.name}({perspective1}):") + print(f" {agent1_reply}") + + # 第二轮:agent2 回应 + prompt2 = f"针对{agent1.name}刚才的观点:'{agent1_reply}',请从{perspective2}的角度进行回应和反驳。要求:有理有据,体现{perspective2}的独特视角。" + + content2 = types.Content(role='user', parts=[types.Part(text=prompt2)]) + response2 = runner2.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=content2 + ) + + agent2_reply = "" + async for event in response2: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent2_reply += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent2_reply += text_content + + print(f"\n🗣️ {agent2.name}({perspective2}):") + print(f" {agent2_reply}") + + # 第三轮:agent1 再次回应 + prompt3 = f"听了{agent2.name}的观点:'{agent2_reply}',请从{perspective1}的角度进行最后的总结和回应。" + + content3 = types.Content(role='user', parts=[types.Part(text=prompt3)]) + response3 = runner1.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=content3 + ) + + agent1_final = "" + async for event in response3: + # 只处理包含实际文本内容的事件,过滤调试信息 + if hasattr(event, 'content') and event.content: + if hasattr(event.content, 'parts') and event.content.parts: + for part in event.content.parts: + if hasattr(part, 'text') and part.text and part.text.strip(): + text_content = str(part.text).strip() + # 过滤掉调试信息和系统消息 + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent1_final += text_content + elif hasattr(event, 'text') and event.text: + text_content = str(event.text).strip() + if not text_content.startswith('Event from') and not 'API_KEY' in text_content: + agent1_final += text_content + + print(f"\n🗣️ {agent1.name}({perspective1})总结:") + print(f" {agent1_final}") + + except Exception as e: + print(f"❌ 对角线辩论出现错误: {e}") + raise + +async def conduct_baxian_debate(): + """进行八仙四对矛盾的完整辩论""" + print("\n🏛️ 稷下学宫 - 八仙论道系统启动") + print("📚 八仙者,南北朝的产物,男女老少,富贵贫贱,皆可成仙") + print("🎯 四对矛盾暗合先天八卦,智慧交锋即将开始") + + topic = "雅江水电站对中印关系的影响" + print(f"\n📋 论道主题:{topic}") + + # 创建八仙智能体 + agents = create_baxian_agents() + + print("\n🔥 八仙真实ADK论道模式") + + # 四对矛盾的对角线辩论 + debates = [ + (agents['male_female'], "男性理性", "女性感性", "男女对立"), + (agents['old_young'], "长者经验", "青年创新", "老少对立"), + (agents['rich_poor'], "富者效率", "贫者公平", "富贫对立"), + (agents['noble_humble'], "贵族秩序", "底层真实", "贵贱对立") + ] + + for i, ((agent1, agent2), perspective1, perspective2, debate_type) in enumerate(debates, 1): + print(f"\n{'='*60}") + print(f"🎭 {debate_type}辩论") + print(f"{'='*60}") + + await conduct_diagonal_debate(agent1, agent2, topic, perspective1, perspective2, i) + + if i < len(debates): + print("\n⏳ 准备下一轮辩论...") + await asyncio.sleep(1) + + print("\n🎉 八仙论道完成!") + print("\n📝 四对矛盾,八种视角,智慧的交锋展现了问题的多面性。") + print("💡 这就是稷下学宫八仙论道的魅力所在。") + +def main(): + """主函数""" + print("🚀 稷下学宫 八仙ADK 真实论道系统") + + # 检查API密钥 + if not os.getenv('GOOGLE_API_KEY'): + print("❌ 请设置 GOOGLE_API_KEY 环境变量") + return + + print("✅ API密钥已配置") + + try: + asyncio.run(conduct_baxian_debate()) + except KeyboardInterrupt: + print("\n👋 用户中断,论道结束") + except Exception as e: + print(f"❌ 系统错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/core/debate_system/debates/enhanced_priority_algorithm.py b/jixia_academy/core/debate_system/debates/enhanced_priority_algorithm.py new file mode 100644 index 0000000..3ef2aa1 --- /dev/null +++ b/jixia_academy/core/debate_system/debates/enhanced_priority_algorithm.py @@ -0,0 +1,980 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +增强版优先级算法 v2.1.0 +实现更复杂的权重计算和上下文分析能力 +""" + +import re +import math +from typing import Dict, List, Any, Optional, Tuple, Set +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from enum import Enum +import json +from collections import defaultdict, deque +import hashlib +import statistics + +class ArgumentType(Enum): + """论点类型""" + ATTACK = "攻击" + DEFENSE = "防御" + SUPPORT = "支持" + REFUTE = "反驳" + SUMMARY = "总结" + QUESTION = "质疑" + +class EmotionLevel(Enum): + """情绪强度""" + CALM = 1 + MILD = 2 + MODERATE = 3 + INTENSE = 4 + EXTREME = 5 + +@dataclass +class SpeechAnalysis: + """发言分析结果""" + argument_type: ArgumentType + emotion_level: EmotionLevel + logic_strength: float # 0-1 + evidence_quality: float # 0-1 + relevance_score: float # 0-1 + urgency_score: float # 0-1 + target_speakers: List[str] # 针对的发言者 + keywords: List[str] + sentiment_score: float # -1 to 1 + +@dataclass +class SpeakerProfile: + """发言者档案""" + name: str + team: str + recent_speeches: List[Dict] = field(default_factory=list) + total_speech_count: int = 0 + average_response_time: float = 30.0 + expertise_areas: List[str] = field(default_factory=list) + debate_style: str = "analytical" # "aggressive", "analytical", "diplomatic", "creative" + current_energy: float = 1.0 # 0-1 + last_speech_time: Optional[datetime] = None + # 新增字段 + historical_performance: Dict[str, float] = field(default_factory=dict) + context_adaptability: float = 0.7 # 上下文适应能力 + argument_effectiveness: Dict[str, float] = field(default_factory=dict) # 不同类型论点的有效性 + collaboration_score: float = 0.5 # 团队协作得分 + interruption_tendency: float = 0.3 # 打断倾向 + topic_expertise: Dict[str, float] = field(default_factory=dict) # 话题专业度 + +class EnhancedPriorityAlgorithm: + """增强版优先级算法""" + + def __init__(self): + # 权重配置 + self.weights = { + "rebuttal_urgency": 0.30, # 反驳紧急性 + "argument_strength": 0.25, # 论点强度 + "time_pressure": 0.20, # 时间压力 + "audience_reaction": 0.15, # 观众反应 + "strategy_need": 0.10 # 策略需要 + } + + # 情感关键词库 + self.emotion_keywords = { + EmotionLevel.CALM: ["认为", "分析", "数据显示", "根据", "客观"], + EmotionLevel.MILD: ["不同意", "质疑", "担心", "建议"], + EmotionLevel.MODERATE: ["强烈", "明显", "严重", "重要"], + EmotionLevel.INTENSE: ["绝对", "完全", "彻底", "必须"], + EmotionLevel.EXTREME: ["荒谬", "愚蠢", "灾难", "危险"] + } + + # 论点类型关键词 + self.argument_keywords = { + ArgumentType.ATTACK: ["错误", "问题", "缺陷", "失败"], + ArgumentType.DEFENSE: ["解释", "澄清", "说明", "回应"], + ArgumentType.SUPPORT: ["支持", "赞同", "证实", "补充"], + ArgumentType.REFUTE: ["反驳", "否定", "驳斥", "反对"], + ArgumentType.SUMMARY: ["总结", "综上", "结论", "最后"], + ArgumentType.QUESTION: ["为什么", "如何", "是否", "难道"] + } + + # 发言者档案 + self.speaker_profiles: Dict[str, SpeakerProfile] = {} + + # 辩论历史分析 + self.debate_history: List[Dict] = [] + + # 新增: 高级分析器组件 + self.context_analyzer = ContextAnalyzer() + self.learning_system = LearningSystem() + self.topic_drift_detector = TopicDriftDetector() + self.emotion_dynamics = EmotionDynamicsModel() + + def analyze_speech(self, message: str, speaker: str, context: Dict) -> SpeechAnalysis: + """分析发言内容""" + # 检测论点类型 + argument_type = self._detect_argument_type(message) + + # 检测情绪强度 + emotion_level = self._detect_emotion_level(message) + + # 计算逻辑强度 + logic_strength = self._calculate_logic_strength(message) + + # 计算证据质量 + evidence_quality = self._calculate_evidence_quality(message) + + # 计算相关性分数 + relevance_score = self._calculate_relevance_score(message, context) + + # 计算紧急性分数 + urgency_score = self._calculate_urgency_score(message, context) + + # 识别目标发言者 + target_speakers = self._identify_target_speakers(message) + + # 提取关键词 + keywords = self._extract_keywords(message) + + # 计算情感分数 + sentiment_score = self._calculate_sentiment_score(message) + + return SpeechAnalysis( + argument_type=argument_type, + emotion_level=emotion_level, + logic_strength=logic_strength, + evidence_quality=evidence_quality, + relevance_score=relevance_score, + urgency_score=urgency_score, + target_speakers=target_speakers, + keywords=keywords, + sentiment_score=sentiment_score + ) + + def calculate_speaker_priority(self, speaker: str, context: Dict, + recent_speeches: List[Dict]) -> float: + """计算发言者优先级 - 增强版""" + # 获取或创建发言者档案 + profile = self._get_or_create_speaker_profile(speaker) + + # 更新发言者档案 + self._update_speaker_profile(profile, recent_speeches) + + # === 基础分数计算 === + rebuttal_urgency = self._calculate_rebuttal_urgency(speaker, context, recent_speeches) + argument_strength = self._calculate_argument_strength(speaker, profile) + time_pressure = self._calculate_time_pressure(speaker, context) + audience_reaction = self._calculate_audience_reaction(speaker, context) + strategy_need = self._calculate_strategy_need(speaker, context, profile) + + # === 新增高级分析 === + # 1. 上下文流程分析 + flow_analysis = self.context_analyzer.analyze_debate_flow(recent_speeches) + flow_bonus = self._calculate_flow_bonus(speaker, flow_analysis) + + # 2. 话题漂移检测 + if recent_speeches: + last_speech = recent_speeches[-1].get("content", "") + drift_analysis = self.topic_drift_detector.detect_drift(last_speech, context) + drift_penalty = self._calculate_drift_penalty(speaker, drift_analysis) + else: + drift_penalty = 0.0 + + # 3. 情绪动态分析 + emotion_analysis = self.emotion_dynamics.analyze_emotion_dynamics(recent_speeches) + emotion_bonus = self._calculate_emotion_bonus(speaker, emotion_analysis, profile) + + # 4. 学习系统适应 + adaptation = self.learning_system.get_speaker_adaptation(speaker) + adaptation_factor = adaptation.get("confidence", 0.5) + + # 5. 个性化权重调整 + personalized_weights = self._get_personalized_weights(speaker, profile, context) + + # === 加权计算总分 === + base_score = ( + rebuttal_urgency * personalized_weights["rebuttal_urgency"] + + argument_strength * personalized_weights["argument_strength"] + + time_pressure * personalized_weights["time_pressure"] + + audience_reaction * personalized_weights["audience_reaction"] + + strategy_need * personalized_weights["strategy_need"] + ) + + # 应用高级调整 + enhanced_score = base_score + flow_bonus - drift_penalty + emotion_bonus + enhanced_score *= adaptation_factor + + # 应用传统修正因子 + final_score = self._apply_correction_factors(enhanced_score, speaker, profile, context) + + return min(max(final_score, 0.0), 1.0) # 限制在0-1范围内 + + def get_next_speaker(self, available_speakers: List[str], context: Dict, + recent_speeches: List[Dict]) -> Tuple[str, float, Dict]: + """获取下一个发言者""" + speaker_scores = {} + detailed_analysis = {} + + for speaker in available_speakers: + score = self.calculate_speaker_priority(speaker, context, recent_speeches) + speaker_scores[speaker] = score + + # 记录详细分析 + detailed_analysis[speaker] = { + "priority_score": score, + "profile": self.speaker_profiles.get(speaker), + "analysis_timestamp": datetime.now().isoformat() + } + + # 选择最高分发言者 + best_speaker = max(speaker_scores, key=speaker_scores.get) + best_score = speaker_scores[best_speaker] + + return best_speaker, best_score, detailed_analysis + + def _detect_argument_type(self, message: str) -> ArgumentType: + """检测论点类型""" + message_lower = message.lower() + type_scores = {} + + for arg_type, keywords in self.argument_keywords.items(): + score = sum(1 for keyword in keywords if keyword in message_lower) + type_scores[arg_type] = score + + if not type_scores or max(type_scores.values()) == 0: + return ArgumentType.SUPPORT # 默认类型 + + return max(type_scores, key=type_scores.get) + + def _detect_emotion_level(self, message: str) -> EmotionLevel: + """检测情绪强度""" + message_lower = message.lower() + + for emotion_level in reversed(list(EmotionLevel)): + keywords = self.emotion_keywords.get(emotion_level, []) + if any(keyword in message_lower for keyword in keywords): + return emotion_level + + return EmotionLevel.CALM + + def _calculate_logic_strength(self, message: str) -> float: + """计算逻辑强度""" + logic_indicators = [ + "因为", "所以", "因此", "由于", "根据", "数据显示", + "研究表明", "事实上", "例如", "比如", "首先", "其次", "最后" + ] + + message_lower = message.lower() + logic_count = sum(1 for indicator in logic_indicators if indicator in message_lower) + + # 基于逻辑词汇密度计算 + word_count = len(message.split()) + if word_count == 0: + return 0.0 + + logic_density = logic_count / word_count + return min(logic_density * 10, 1.0) # 归一化到0-1 + + def _calculate_evidence_quality(self, message: str) -> float: + """计算证据质量""" + evidence_indicators = [ + "数据", "统计", "研究", "报告", "调查", "实验", + "案例", "例子", "证据", "资料", "文献", "来源" + ] + + message_lower = message.lower() + evidence_count = sum(1 for indicator in evidence_indicators if indicator in message_lower) + + # 检查是否有具体数字 + number_pattern = r'\d+(?:\.\d+)?%?' + numbers = re.findall(number_pattern, message) + number_bonus = min(len(numbers) * 0.1, 0.3) + + base_score = min(evidence_count * 0.2, 0.7) + return min(base_score + number_bonus, 1.0) + + def _calculate_relevance_score(self, message: str, context: Dict) -> float: + """计算相关性分数""" + # 简化实现:基于关键词匹配 + topic_keywords = context.get("topic_keywords", []) + if not topic_keywords: + return 0.5 # 默认中等相关性 + + message_lower = message.lower() + relevance_count = sum(1 for keyword in topic_keywords if keyword.lower() in message_lower) + + return min(relevance_count / len(topic_keywords), 1.0) + + def _calculate_urgency_score(self, message: str, context: Dict) -> float: + """计算紧急性分数""" + urgency_keywords = ["紧急", "立即", "马上", "现在", "重要", "关键", "危险"] + + message_lower = message.lower() + urgency_count = sum(1 for keyword in urgency_keywords if keyword in message_lower) + + # 基于时间压力 + time_factor = context.get("time_remaining", 1.0) + time_urgency = 1.0 - time_factor + + keyword_urgency = min(urgency_count * 0.3, 1.0) + + return min(keyword_urgency + time_urgency * 0.5, 1.0) + + def _identify_target_speakers(self, message: str) -> List[str]: + """识别目标发言者""" + # 简化实现:查找提及的发言者名称 + speaker_names = ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"] + targets = [] + + for name in speaker_names: + if name in message: + targets.append(name) + + return targets + + def _extract_keywords(self, message: str) -> List[str]: + """提取关键词""" + # 简化实现:提取长度大于2的词汇 + words = re.findall(r'\b\w{3,}\b', message) + return list(set(words))[:10] # 最多返回10个关键词 + + def _calculate_sentiment_score(self, message: str) -> float: + """计算情感分数""" + positive_words = ["好", "优秀", "正确", "支持", "赞同", "成功", "有效"] + negative_words = ["坏", "错误", "失败", "反对", "问题", "危险", "无效"] + + message_lower = message.lower() + + positive_count = sum(1 for word in positive_words if word in message_lower) + negative_count = sum(1 for word in negative_words if word in message_lower) + + total_count = positive_count + negative_count + if total_count == 0: + return 0.0 + + return (positive_count - negative_count) / total_count + + def _get_or_create_speaker_profile(self, speaker: str) -> SpeakerProfile: + """获取或创建发言者档案""" + if speaker not in self.speaker_profiles: + self.speaker_profiles[speaker] = SpeakerProfile( + name=speaker, + team="positive" if "正" in speaker else "negative", + recent_speeches=[], + total_speech_count=0, + average_response_time=3.0, + expertise_areas=[], + debate_style="analytical", + current_energy=1.0 + ) + + return self.speaker_profiles[speaker] + + def _update_speaker_profile(self, profile: SpeakerProfile, recent_speeches: List[Dict]): + """更新发言者档案""" + # 更新发言历史 + speaker_speeches = [s for s in recent_speeches if s.get("speaker") == profile.name] + profile.recent_speeches = speaker_speeches[-5:] # 保留最近5次发言 + profile.total_speech_count = len(speaker_speeches) + + # 更新能量水平(基于发言频率) + if profile.last_speech_time: + time_since_last = datetime.now() - profile.last_speech_time + energy_recovery = min(time_since_last.seconds / 300, 0.5) # 5分钟恢复50% + profile.current_energy = min(profile.current_energy + energy_recovery, 1.0) + + profile.last_speech_time = datetime.now() + + def _calculate_rebuttal_urgency(self, speaker: str, context: Dict, + recent_speeches: List[Dict]) -> float: + """计算反驳紧急性""" + # 检查是否有针对该发言者团队的攻击 + team = "positive" if "正" in speaker else "negative" + opposing_team = "negative" if team == "positive" else "positive" + + recent_attacks = 0 + for speech in recent_speeches[-5:]: # 检查最近5次发言 + if speech.get("team") == opposing_team: + analysis = speech.get("analysis", {}) + if analysis.get("argument_type") in [ArgumentType.ATTACK, ArgumentType.REFUTE]: + recent_attacks += 1 + + # 基础紧急性 + 攻击响应紧急性 + # 为不同发言者生成不同的基础紧急性 + speaker_hash = hash(speaker) % 10 # 使用哈希值生成0-9的数字 + base_urgency = 0.1 + speaker_hash * 0.05 # 不同发言者有不同的基础紧急性 + attack_urgency = recent_attacks * 0.3 + + return min(base_urgency + attack_urgency, 1.0) + + def _calculate_argument_strength(self, speaker: str, profile: SpeakerProfile) -> float: + """计算论点强度""" + # 基于历史表现 + if not profile.recent_speeches: + # 为不同发言者提供不同的基础论点强度 + speaker_hash = hash(speaker) % 10 # 使用哈希值生成0-9的数字 + team_prefix = "正" if "正" in speaker else "反" + + # 基础强度根据发言者哈希值变化 + base_strength = 0.4 + speaker_hash * 0.06 # 0.4-1.0范围 + + # 团队差异化 + team_factor = 1.05 if team_prefix == "正" else 0.95 + + return min(base_strength * team_factor, 1.0) + + avg_logic = sum(s.get("analysis", {}).get("logic_strength", 0.5) + for s in profile.recent_speeches) / len(profile.recent_speeches) + avg_evidence = sum(s.get("analysis", {}).get("evidence_quality", 0.5) + for s in profile.recent_speeches) / len(profile.recent_speeches) + + return (avg_logic + avg_evidence) / 2 + + def _calculate_time_pressure(self, speaker: str, context: Dict) -> float: + """计算时间压力""" + time_remaining = context.get("time_remaining", 1.0) + stage_progress = context.get("stage_progress", 0) + max_progress = context.get("max_progress", 1) + + # 时间压力随剩余时间减少而增加 + time_pressure = 1.0 - time_remaining + + # 阶段进度压力 + progress_pressure = stage_progress / max_progress + + # 发言者个体差异 + speaker_hash = hash(speaker) % 10 # 使用哈希值生成0-9的数字 + speaker_factor = 0.8 + speaker_hash * 0.02 # 不同发言者有不同的时间敏感度 + + base_pressure = (time_pressure + progress_pressure) / 2 + return min(base_pressure * speaker_factor, 1.0) + + def _calculate_audience_reaction(self, speaker: str, context: Dict) -> float: + """计算观众反应""" + # 简化实现:基于团队表现 + team = "positive" if "正" in speaker else "negative" + team_score = context.get(f"{team}_team_score", 0.5) + + # 发言者个体魅力差异 + speaker_hash = hash(speaker) % 10 # 使用哈希值生成0-9的数字 + charisma_factor = 0.7 + speaker_hash * 0.03 # 不同发言者有不同的观众吸引力 + + # 如果团队表现不佳,需要更多发言机会 + base_reaction = 1.0 - team_score + return min(base_reaction * charisma_factor, 1.0) + + def _calculate_strategy_need(self, speaker: str, context: Dict, + profile: SpeakerProfile) -> float: + """计算策略需要""" + # 基于发言者专长和当前需求 + current_stage = context.get("current_stage", "") + + # 为不同发言者提供差异化的策略需求 + speaker_hash = hash(speaker) % 10 # 使用哈希值生成0-9的数字 + team_prefix = "正" if "正" in speaker else "反" + + strategy_match = { + "起": 0.8 if speaker_hash == 0 else 0.3 + speaker_hash * 0.05, # 开场需要主力,但有差异 + "承": 0.4 + speaker_hash * 0.06, # 承接阶段根据发言者哈希差异化 + "转": max(0.2, 1.0 - profile.current_energy + speaker_hash * 0.05), # 自由辩论看能量和哈希 + "合": 0.9 if speaker_hash == 0 else 0.3 + speaker_hash * 0.05 # 总结需要主力,但有差异 + } + + base_score = strategy_match.get(current_stage, 0.5) + + # 添加团队差异化因子 + team_factor = 1.1 if team_prefix == "正" else 0.9 + + return min(base_score * team_factor, 1.0) + + def _apply_correction_factors(self, base_score: float, speaker: str, + profile: SpeakerProfile, context: Dict) -> float: + """应用修正因子""" + corrected_score = base_score + + # 能量修正 + corrected_score *= profile.current_energy + + # 发言频率修正(避免某人发言过多) + recent_count = len([s for s in profile.recent_speeches + if s.get("timestamp", "") > (datetime.now() - timedelta(minutes=5)).isoformat()]) + if recent_count > 2: + corrected_score *= 0.7 # 降低优先级 + + # 团队平衡修正 + team = "positive" if "正" in speaker else "negative" + team_recent_count = context.get(f"{team}_recent_speeches", 0) + opposing_recent_count = context.get(f"{'negative' if team == 'positive' else 'positive'}_recent_speeches", 0) + + if team_recent_count > opposing_recent_count + 2: + corrected_score *= 0.8 # 平衡发言机会 + + return corrected_score + + def calculate_priority(self, speaker: str, context: Dict, recent_speeches: List[Dict]) -> float: + """计算发言者优先级(兼容性方法)""" + return self.calculate_speaker_priority(speaker, context, recent_speeches) + + def get_algorithm_status(self) -> Dict[str, Any]: + """获取算法状态""" + return { + "weights": self.weights, + "speaker_count": len(self.speaker_profiles), + "total_speeches_analyzed": len(self.debate_history), + "algorithm_version": "2.1.0", + "last_updated": datetime.now().isoformat() + } + + def save_analysis_data(self, filename: str = "priority_analysis.json"): + """保存分析数据""" + data = { + "algorithm_status": self.get_algorithm_status(), + "speaker_profiles": { + name: { + "name": profile.name, + "team": profile.team, + "total_speech_count": profile.total_speech_count, + "average_response_time": profile.average_response_time, + "expertise_areas": profile.expertise_areas, + "debate_style": profile.debate_style, + "current_energy": profile.current_energy, + "last_speech_time": profile.last_speech_time.isoformat() if profile.last_speech_time else None + } + for name, profile in self.speaker_profiles.items() + }, + "debate_history": self.debate_history + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + print(f"💾 优先级分析数据已保存到 {filename}") + +def main(): + """测试增强版优先级算法""" + print("🚀 增强版优先级算法测试") + print("=" * 50) + + algorithm = EnhancedPriorityAlgorithm() + + # 模拟辩论上下文 + context = { + "current_stage": "转", + "stage_progress": 10, + "max_progress": 36, + "time_remaining": 0.6, + "topic_keywords": ["人工智能", "投资", "风险", "收益"], + "positive_team_score": 0.6, + "negative_team_score": 0.4, + "positive_recent_speeches": 3, + "negative_recent_speeches": 2 + } + + # 模拟最近发言 + recent_speeches = [ + { + "speaker": "正1", + "team": "positive", + "message": "根据数据显示,AI投资确实能带来显著收益", + "timestamp": datetime.now().isoformat(), + "analysis": { + "argument_type": ArgumentType.SUPPORT, + "logic_strength": 0.8, + "evidence_quality": 0.7 + } + }, + { + "speaker": "反2", + "team": "negative", + "message": "这种观点完全错误!AI投资风险巨大!", + "timestamp": datetime.now().isoformat(), + "analysis": { + "argument_type": ArgumentType.ATTACK, + "logic_strength": 0.3, + "evidence_quality": 0.2 + } + } + ] + + available_speakers = ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"] + + # 计算下一个发言者 + next_speaker, score, analysis = algorithm.get_next_speaker( + available_speakers, context, recent_speeches + ) + + print(f"\n🎯 推荐发言者: {next_speaker}") + print(f"📊 优先级分数: {score:.3f}") + print(f"\n📈 详细分析:") + + for speaker, data in analysis.items(): + print(f" {speaker}: {data['priority_score']:.3f}") + + # 保存分析数据 + algorithm.save_analysis_data() + + print("\n✅ 增强版优先级算法测试完成!") + +if __name__ == "__main__": + main() + + +class ContextAnalyzer: + """高级上下文分析器""" + + def __init__(self): + self.context_memory = deque(maxlen=20) # 保留最近20轮的上下文 + self.semantic_vectors = {} # 语义向量缓存 + + def analyze_debate_flow(self, recent_speeches: List[Dict]) -> Dict[str, Any]: + """分析辩论流程""" + if not recent_speeches: + return {"flow_direction": "neutral", "momentum": 0.5, "tension": 0.3} + + # 分析辩论动量 + momentum = self._calculate_debate_momentum(recent_speeches) + + # 分析辩论紧张度 + tension = self._calculate_debate_tension(recent_speeches) + + # 分析流程方向 + flow_direction = self._analyze_flow_direction(recent_speeches) + + # 检测话题转换点 + topic_shifts = self._detect_topic_shifts(recent_speeches) + + return { + "flow_direction": flow_direction, + "momentum": momentum, + "tension": tension, + "topic_shifts": topic_shifts, + "engagement_level": self._calculate_engagement_level(recent_speeches) + } + + def _calculate_debate_momentum(self, speeches: List[Dict]) -> float: + """计算辩论动量""" + if len(speeches) < 2: + return 0.5 + + # 基于发言长度和情绪强度变化 + momentum_factors = [] + for i in range(1, len(speeches)): + prev_speech = speeches[i-1] + curr_speech = speeches[i] + + # 长度变化 + length_change = len(curr_speech.get("content", "")) - len(prev_speech.get("content", "")) + length_factor = min(abs(length_change) / 100, 1.0) # 归一化 + + momentum_factors.append(length_factor) + + return statistics.mean(momentum_factors) if momentum_factors else 0.5 + + def _calculate_debate_tension(self, speeches: List[Dict]) -> float: + """计算辩论紧张度""" + if not speeches: + return 0.3 + + tension_keywords = ["反驳", "错误", "质疑", "不同意", "反对", "驳斥"] + + tension_scores = [] + for speech in speeches[-5:]: # 只看最近5轮 + content = speech.get("content", "") + tension_count = sum(1 for keyword in tension_keywords if keyword in content) + tension_scores.append(min(tension_count / 3, 1.0)) + + return statistics.mean(tension_scores) if tension_scores else 0.3 + + def _analyze_flow_direction(self, speeches: List[Dict]) -> str: + """分析流程方向""" + if len(speeches) < 3: + return "neutral" + + recent_teams = [speech.get("team", "unknown") for speech in speeches[-3:]] + + positive_count = recent_teams.count("positive") + negative_count = recent_teams.count("negative") + + if positive_count > negative_count: + return "positive_dominant" + elif negative_count > positive_count: + return "negative_dominant" + else: + return "balanced" + + def _detect_topic_shifts(self, speeches: List[Dict]) -> List[Dict]: + """检测话题转换点""" + shifts = [] + if len(speeches) < 2: + return shifts + + # 简化的话题转换检测 + for i in range(1, len(speeches)): + prev_keywords = set(speeches[i-1].get("content", "").split()[:10]) + curr_keywords = set(speeches[i].get("content", "").split()[:10]) + + # 计算关键词重叠度 + overlap = len(prev_keywords & curr_keywords) / max(len(prev_keywords | curr_keywords), 1) + + if overlap < 0.3: # 重叠度低于30%认为是话题转换 + shifts.append({ + "position": i, + "speaker": speeches[i].get("speaker"), + "shift_intensity": 1 - overlap + }) + + return shifts + + def _calculate_engagement_level(self, speeches: List[Dict]) -> float: + """计算参与度""" + if not speeches: + return 0.5 + + # 基于发言频率和长度 + total_length = sum(len(speech.get("content", "")) for speech in speeches) + avg_length = total_length / len(speeches) + + # 归一化到0-1 + engagement = min(avg_length / 100, 1.0) + return engagement + + +class LearningSystem: + """学习系统,用于优化算法参数""" + + def __init__(self): + self.performance_history = defaultdict(list) + self.weight_adjustments = defaultdict(float) + self.learning_rate = 0.05 + + def record_performance(self, speaker: str, predicted_priority: float, + actual_effectiveness: float, context: Dict): + """记录表现数据""" + self.performance_history[speaker].append({ + "predicted_priority": predicted_priority, + "actual_effectiveness": actual_effectiveness, + "context": context, + "timestamp": datetime.now(), + "error": abs(predicted_priority - actual_effectiveness) + }) + + def optimize_weights(self, algorithm_weights: Dict[str, float]) -> Dict[str, float]: + """优化权重参数""" + if not self.performance_history: + return algorithm_weights + + # 计算每个组件的平均误差 + component_errors = {} + for component in algorithm_weights.keys(): + errors = [] + for speaker_data in self.performance_history.values(): + for record in speaker_data[-10:]: # 只看最近10次 + errors.append(record["error"]) + + if errors: + component_errors[component] = statistics.mean(errors) + + # 根据误差调整权重 + optimized_weights = algorithm_weights.copy() + for component, error in component_errors.items(): + if error > 0.3: # 误差过大,降低权重 + adjustment = -self.learning_rate * error + else: # 误差合理,略微增加权重 + adjustment = self.learning_rate * (0.3 - error) + + optimized_weights[component] = max(0.05, min(0.5, + optimized_weights[component] + adjustment)) + + # 归一化权重 + total_weight = sum(optimized_weights.values()) + if total_weight > 0: + optimized_weights = {k: v/total_weight for k, v in optimized_weights.items()} + + return optimized_weights + + def get_speaker_adaptation(self, speaker: str) -> Dict[str, float]: + """获取发言者特定的适应参数""" + if speaker not in self.performance_history: + return {"confidence": 0.5, "adaptability": 0.5} + + recent_records = self.performance_history[speaker][-5:] + if not recent_records: + return {"confidence": 0.5, "adaptability": 0.5} + + # 计算准确性趋势 + errors = [record["error"] for record in recent_records] + avg_error = statistics.mean(errors) + + confidence = max(0.1, 1.0 - avg_error) + adaptability = min(1.0, 0.3 + (1.0 - statistics.stdev(errors)) if len(errors) > 1 else 0.7) + + return {"confidence": confidence, "adaptability": adaptability} + + +class TopicDriftDetector: + """话题漂移检测器""" + + def __init__(self): + self.topic_history = deque(maxlen=50) + self.keywords_cache = {} + + def detect_drift(self, current_speech: str, context: Dict) -> Dict[str, Any]: + """检测话题漂移""" + current_keywords = self._extract_topic_keywords(current_speech) + + if not self.topic_history: + self.topic_history.append(current_keywords) + return {"drift_detected": False, "drift_intensity": 0.0} + + # 计算与历史话题的相似度 + similarities = [] + for historical_keywords in list(self.topic_history)[-5:]: # 最近5轮 + similarity = self._calculate_keyword_similarity(current_keywords, historical_keywords) + similarities.append(similarity) + + avg_similarity = statistics.mean(similarities) + drift_intensity = 1.0 - avg_similarity + + # 更新历史 + self.topic_history.append(current_keywords) + + return { + "drift_detected": drift_intensity > 0.4, # 阈值40% + "drift_intensity": drift_intensity, + "current_keywords": current_keywords, + "recommendation": self._get_drift_recommendation(float(drift_intensity)) + } + + def _extract_topic_keywords(self, text: str) -> Set[str]: + """提取话题关键词""" + # 简化的关键词提取 + words = re.findall(r'\b\w{2,}\b', text.lower()) + + # 过滤停用词 + stop_words = {"的", "了", "在", "是", "我", "你", "他", "她", "我们", "这", "那"} + keywords = {word for word in words if word not in stop_words and len(word) > 1} + + return keywords + + def _calculate_keyword_similarity(self, keywords1: Set[str], keywords2: Set[str]) -> float: + """计算关键词相似度""" + if not keywords1 or not keywords2: + return 0.0 + + intersection = keywords1 & keywords2 + union = keywords1 | keywords2 + + return len(intersection) / len(union) if union else 0.0 + + def _get_drift_recommendation(self, drift_intensity: float) -> str: + """获取漂移建议""" + if drift_intensity > 0.7: + return "major_topic_shift_detected" + elif drift_intensity > 0.4: + return "moderate_drift_detected" + else: + return "topic_stable" + + +class EmotionDynamicsModel: + """情绪动力学模型""" + + def __init__(self): + self.emotion_history = deque(maxlen=30) + self.speaker_emotion_profiles = defaultdict(list) + + def analyze_emotion_dynamics(self, recent_speeches: List[Dict]) -> Dict[str, Any]: + """分析情绪动态""" + if not recent_speeches: + return {"overall_trend": "neutral", "intensity_change": 0.0} + + # 提取情绪序列 + emotion_sequence = [] + for speech in recent_speeches: + emotion_score = self._calculate_emotion_score(speech.get("content", "")) + emotion_sequence.append(emotion_score) + + # 更新发言者情绪档案 + speaker = speech.get("speaker") + if speaker: + self.speaker_emotion_profiles[speaker].append(emotion_score) + + if len(emotion_sequence) < 2: + return {"overall_trend": "neutral", "intensity_change": 0.0} + + # 计算情绪趋势 + trend = self._calculate_emotion_trend(emotion_sequence) + + # 计算强度变化 + intensity_change = emotion_sequence[-1] - emotion_sequence[0] + + # 检测情绪拐点 + turning_points = self._detect_emotion_turning_points(emotion_sequence) + + return { + "overall_trend": trend, + "intensity_change": intensity_change, + "current_intensity": emotion_sequence[-1], + "turning_points": turning_points, + "volatility": statistics.stdev(emotion_sequence) if len(emotion_sequence) > 1 else 0.0 + } + + def _calculate_emotion_score(self, text: str) -> float: + """计算情绪分数""" + positive_words = ["好", "棒", "优秀", "正确", "支持", "赞同", "有效"] + negative_words = ["坏", "错", "糟糕", "反对", "质疑", "问题", "失败"] + intense_words = ["强烈", "坚决", "绝对", "完全", "彻底"] + + text_lower = text.lower() + + positive_count = sum(1 for word in positive_words if word in text_lower) + negative_count = sum(1 for word in negative_words if word in text_lower) + intense_count = sum(1 for word in intense_words if word in text_lower) + + base_emotion = (positive_count - negative_count) / max(len(text.split()), 1) + intensity_multiplier = 1 + (intense_count * 0.5) + + return base_emotion * intensity_multiplier + + def _calculate_emotion_trend(self, sequence: List[float]) -> str: + """计算情绪趋势""" + if len(sequence) < 2: + return "neutral" + + # 简单线性回归估算 + if len(sequence) < 2: + return 0.0 + + # 计算斜率 + n = len(sequence) + sum_x = sum(range(n)) + sum_y = sum(sequence) + sum_xy = sum(i * sequence[i] for i in range(n)) + sum_x2 = sum(i * i for i in range(n)) + + slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x) + + if slope > 0.1: + return "escalating" + elif slope < -0.1: + return "de_escalating" + else: + return "stable" + + def _detect_emotion_turning_points(self, sequence: List[float]) -> List[int]: + """检测情绪拐点""" + if len(sequence) < 3: + return [] + + turning_points = [] + for i in range(1, len(sequence) - 1): + prev_val = sequence[i-1] + curr_val = sequence[i] + next_val = sequence[i+1] + + # 检测峰值和谷值 + if (curr_val > prev_val and curr_val > next_val) or \ + (curr_val < prev_val and curr_val < next_val): + turning_points.append(i) + + return turning_points \ No newline at end of file diff --git a/jixia_academy/core/debate_system/debates/optimized_debate_flow.py b/jixia_academy/core/debate_system/debates/optimized_debate_flow.py new file mode 100644 index 0000000..50bd4be --- /dev/null +++ b/jixia_academy/core/debate_system/debates/optimized_debate_flow.py @@ -0,0 +1,733 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +优化的辩论流程控制系统 v2.1.0 +改进阶段转换和发言权争夺逻辑 +""" + +import asyncio +import json +import time +from datetime import datetime, timedelta +from typing import Dict, List, Any, Optional, Tuple, Callable +from dataclasses import dataclass, field +from enum import Enum +from collections import defaultdict, deque +import threading +import queue + +class DebateStage(Enum): + """辩论阶段枚举""" + QI = "起" # 八仙按先天八卦顺序 + CHENG = "承" # 雁阵式承接 + ZHUAN = "转" # 自由辩论(36次handoff) + HE = "合" # 交替总结 + +class FlowControlMode(Enum): + """流程控制模式""" + STRICT = "严格模式" # 严格按规则执行 + ADAPTIVE = "自适应模式" # 根据辩论质量调整 + DYNAMIC = "动态模式" # 实时响应辩论状态 + +class TransitionTrigger(Enum): + """阶段转换触发条件""" + TIME_BASED = "时间触发" + PROGRESS_BASED = "进度触发" + QUALITY_BASED = "质量触发" + CONSENSUS_BASED = "共识触发" + EMERGENCY = "紧急触发" + +class SpeakerSelectionStrategy(Enum): + """发言者选择策略""" + PRIORITY_ALGORITHM = "优先级算法" + ROUND_ROBIN = "轮询" + RANDOM_WEIGHTED = "加权随机" + CONTEXT_AWARE = "上下文感知" + COMPETITIVE = "竞争模式" + +@dataclass +class FlowControlConfig: + """流程控制配置""" + mode: FlowControlMode = FlowControlMode.ADAPTIVE + transition_triggers: List[TransitionTrigger] = field(default_factory=lambda: [TransitionTrigger.PROGRESS_BASED, TransitionTrigger.QUALITY_BASED]) + speaker_selection_strategy: SpeakerSelectionStrategy = SpeakerSelectionStrategy.CONTEXT_AWARE + min_stage_duration: int = 60 # 秒 + max_stage_duration: int = 900 # 秒 + quality_threshold: float = 0.6 # 质量阈值 + participation_balance_threshold: float = 0.3 # 参与平衡阈值 + emergency_intervention_enabled: bool = True + auto_stage_transition: bool = True + speaker_timeout: int = 30 # 发言超时时间 + +@dataclass +class StageMetrics: + """阶段指标""" + start_time: datetime + duration: float = 0.0 + speech_count: int = 0 + quality_score: float = 0.0 + participation_balance: float = 0.0 + engagement_level: float = 0.0 + topic_coherence: float = 0.0 + conflict_intensity: float = 0.0 + speaker_distribution: Dict[str, int] = field(default_factory=dict) + transition_readiness: float = 0.0 + +@dataclass +class SpeakerRequest: + """发言请求""" + speaker: str + priority: float + timestamp: datetime + reason: str + urgency_level: int = 1 # 1-5 + estimated_duration: int = 30 # 秒 + topic_relevance: float = 1.0 + +@dataclass +class FlowEvent: + """流程事件""" + event_type: str + timestamp: datetime + data: Dict[str, Any] + source: str + priority: int = 1 + +class OptimizedDebateFlowController: + """优化的辩论流程控制器""" + + def __init__(self, config: FlowControlConfig = None): + self.config = config or FlowControlConfig() + + # 当前状态 + self.current_stage = DebateStage.QI + self.stage_progress = 0 + self.total_handoffs = 0 + self.current_speaker: Optional[str] = None + self.debate_start_time = datetime.now() + + # 阶段配置 + self.stage_configs = { + DebateStage.QI: { + "max_progress": 8, + "min_duration": 120, + "max_duration": 600, + "speaker_order": ["吕洞宾", "何仙姑", "铁拐李", "汉钟离", "曹国舅", "韩湘子", "蓝采和", "张果老"], + "selection_strategy": SpeakerSelectionStrategy.ROUND_ROBIN + }, + DebateStage.CHENG: { + "max_progress": 8, + "min_duration": 180, + "max_duration": 600, + "speaker_order": ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"], + "selection_strategy": SpeakerSelectionStrategy.ROUND_ROBIN + }, + DebateStage.ZHUAN: { + "max_progress": 36, + "min_duration": 300, + "max_duration": 900, + "speaker_order": ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"], + "selection_strategy": SpeakerSelectionStrategy.CONTEXT_AWARE + }, + DebateStage.HE: { + "max_progress": 8, + "min_duration": 120, + "max_duration": 480, + "speaker_order": ["反1", "正1", "反2", "正2", "反3", "正3", "反4", "正4"], + "selection_strategy": SpeakerSelectionStrategy.ROUND_ROBIN + } + } + + # 阶段指标 + self.stage_metrics: Dict[DebateStage, StageMetrics] = {} + self.current_stage_metrics = StageMetrics(start_time=datetime.now()) + + # 发言请求队列 + self.speaker_request_queue = queue.PriorityQueue() + self.pending_requests: Dict[str, SpeakerRequest] = {} + + # 事件系统 + self.event_queue = queue.Queue() + self.event_handlers: Dict[str, List[Callable]] = defaultdict(list) + + # 历史记录 + self.debate_history: List[Dict] = [] + self.stage_transition_history: List[Dict] = [] + self.speaker_performance: Dict[str, Dict] = defaultdict(dict) + + # 实时监控 + self.monitoring_active = False + self.monitoring_thread: Optional[threading.Thread] = None + + # 流程锁 + self.flow_lock = threading.RLock() + + # 初始化当前阶段指标 + self._initialize_stage_metrics() + + def _initialize_stage_metrics(self): + """初始化阶段指标""" + self.current_stage_metrics = StageMetrics( + start_time=datetime.now(), + speaker_distribution={} + ) + + def get_current_speaker(self) -> Optional[str]: + """获取当前发言者""" + with self.flow_lock: + config = self.stage_configs[self.current_stage] + strategy = config.get("selection_strategy", self.config.speaker_selection_strategy) + + if strategy == SpeakerSelectionStrategy.ROUND_ROBIN: + return self._get_round_robin_speaker() + elif strategy == SpeakerSelectionStrategy.CONTEXT_AWARE: + return self._get_context_aware_speaker() + elif strategy == SpeakerSelectionStrategy.PRIORITY_ALGORITHM: + return self._get_priority_speaker() + elif strategy == SpeakerSelectionStrategy.COMPETITIVE: + return self._get_competitive_speaker() + else: + return self._get_round_robin_speaker() + + def _get_round_robin_speaker(self) -> str: + """轮询方式获取发言者""" + config = self.stage_configs[self.current_stage] + speaker_order = config["speaker_order"] + return speaker_order[self.stage_progress % len(speaker_order)] + + def _get_context_aware_speaker(self) -> Optional[str]: + """上下文感知方式获取发言者""" + # 检查是否有紧急发言请求 + if not self.speaker_request_queue.empty(): + try: + priority, request = self.speaker_request_queue.get_nowait() + if request.urgency_level >= 4: # 高紧急度 + return request.speaker + else: + # 重新放回队列 + self.speaker_request_queue.put((priority, request)) + except queue.Empty: + pass + + # 分析当前上下文 + context = self._analyze_current_context() + + # 根据上下文选择最合适的发言者 + available_speakers = self.stage_configs[self.current_stage]["speaker_order"] + best_speaker = None + best_score = -1 + + for speaker in available_speakers: + score = self._calculate_speaker_context_score(speaker, context) + if score > best_score: + best_score = score + best_speaker = speaker + + return best_speaker + + def _get_priority_speaker(self) -> Optional[str]: + """优先级算法获取发言者""" + # 这里可以集成现有的优先级算法 + # 暂时使用简化版本 + return self._get_context_aware_speaker() + + def _get_competitive_speaker(self) -> Optional[str]: + """竞争模式获取发言者""" + # 让发言者竞争发言权 + if not self.speaker_request_queue.empty(): + try: + priority, request = self.speaker_request_queue.get_nowait() + return request.speaker + except queue.Empty: + pass + + return self._get_round_robin_speaker() + + def request_speaking_turn(self, speaker: str, reason: str, urgency: int = 1, + estimated_duration: int = 30, topic_relevance: float = 1.0): + """请求发言权""" + request = SpeakerRequest( + speaker=speaker, + priority=self._calculate_request_priority(speaker, reason, urgency, topic_relevance), + timestamp=datetime.now(), + reason=reason, + urgency_level=urgency, + estimated_duration=estimated_duration, + topic_relevance=topic_relevance + ) + + # 使用负优先级,因为PriorityQueue是最小堆 + self.speaker_request_queue.put((-request.priority, request)) + self.pending_requests[speaker] = request + + # 触发事件 + self._emit_event("speaker_request", { + "speaker": speaker, + "reason": reason, + "urgency": urgency, + "priority": request.priority + }) + + def _calculate_request_priority(self, speaker: str, reason: str, urgency: int, + topic_relevance: float) -> float: + """计算发言请求优先级""" + base_priority = urgency * 10 + + # 主题相关性加权 + relevance_bonus = topic_relevance * 5 + + # 发言频率调整 + speaker_count = self.current_stage_metrics.speaker_distribution.get(speaker, 0) + frequency_penalty = speaker_count * 2 + + # 时间因素 + time_factor = 1.0 + if self.current_speaker and self.current_speaker != speaker: + time_factor = 1.2 # 鼓励轮换 + + priority = (base_priority + relevance_bonus - frequency_penalty) * time_factor + return max(0.1, priority) + + def _analyze_current_context(self) -> Dict[str, Any]: + """分析当前辩论上下文""" + recent_speeches = self.debate_history[-5:] if self.debate_history else [] + + context = { + "stage": self.current_stage.value, + "progress": self.stage_progress, + "recent_speakers": [speech.get("speaker") for speech in recent_speeches], + "topic_drift": self._calculate_topic_drift(), + "emotional_intensity": self._calculate_emotional_intensity(), + "argument_balance": self._calculate_argument_balance(), + "time_pressure": self._calculate_time_pressure(), + "participation_balance": self._calculate_participation_balance() + } + + return context + + def _calculate_speaker_context_score(self, speaker: str, context: Dict[str, Any]) -> float: + """计算发言者在当前上下文下的适合度分数""" + score = 0.0 + + # 避免连续发言 + recent_speakers = context.get("recent_speakers", []) + if speaker in recent_speakers[-2:]: + score -= 10 + + # 参与平衡 + speaker_count = self.current_stage_metrics.speaker_distribution.get(speaker, 0) + avg_count = sum(self.current_stage_metrics.speaker_distribution.values()) / max(1, len(self.current_stage_metrics.speaker_distribution)) + if speaker_count < avg_count: + score += 5 + + # 队伍平衡 + if self.current_stage == DebateStage.ZHUAN: + positive_count = sum(1 for s in recent_speakers if "正" in s) + negative_count = sum(1 for s in recent_speakers if "反" in s) + + if "正" in speaker and positive_count < negative_count: + score += 3 + elif "反" in speaker and negative_count < positive_count: + score += 3 + + # 时间压力响应 + time_pressure = context.get("time_pressure", 0) + if time_pressure > 0.7 and speaker.endswith("1"): # 主力发言者 + score += 5 + + # 检查发言请求 + if speaker in self.pending_requests: + request = self.pending_requests[speaker] + score += request.urgency_level * 2 + score += request.topic_relevance * 3 + + return score + + def advance_stage(self, force: bool = False) -> bool: + """推进辩论阶段""" + with self.flow_lock: + if not force and not self._should_advance_stage(): + return False + + # 记录当前阶段结束 + self._finalize_current_stage() + + # 转换到下一阶段 + success = self._transition_to_next_stage() + + if success: + # 初始化新阶段 + self._initialize_new_stage() + + # 触发事件 + self._emit_event("stage_advanced", { + "from_stage": self.current_stage.value, + "to_stage": self.current_stage.value, + "progress": self.stage_progress, + "forced": force + }) + + return success + + def _should_advance_stage(self) -> bool: + """判断是否应该推进阶段""" + config = self.stage_configs[self.current_stage] + + # 检查进度触发 + if TransitionTrigger.PROGRESS_BASED in self.config.transition_triggers: + if self.stage_progress >= config["max_progress"] - 1: + return True + + # 检查时间触发 + if TransitionTrigger.TIME_BASED in self.config.transition_triggers: + stage_duration = (datetime.now() - self.current_stage_metrics.start_time).total_seconds() + if stage_duration >= config.get("max_duration", 600): + return True + + # 检查质量触发 + if TransitionTrigger.QUALITY_BASED in self.config.transition_triggers: + if (self.current_stage_metrics.quality_score >= self.config.quality_threshold and + self.stage_progress >= config["max_progress"] // 2): + return True + + # 检查共识触发 + if TransitionTrigger.CONSENSUS_BASED in self.config.transition_triggers: + if self.current_stage_metrics.transition_readiness >= 0.8: + return True + + return False + + def _finalize_current_stage(self): + """结束当前阶段""" + # 更新阶段指标 + self.current_stage_metrics.duration = (datetime.now() - self.current_stage_metrics.start_time).total_seconds() + + # 保存阶段指标 + self.stage_metrics[self.current_stage] = self.current_stage_metrics + + # 记录阶段转换历史 + self.stage_transition_history.append({ + "stage": self.current_stage.value, + "start_time": self.current_stage_metrics.start_time.isoformat(), + "duration": self.current_stage_metrics.duration, + "speech_count": self.current_stage_metrics.speech_count, + "quality_score": self.current_stage_metrics.quality_score, + "participation_balance": self.current_stage_metrics.participation_balance + }) + + def _transition_to_next_stage(self) -> bool: + """转换到下一阶段""" + stage_transitions = { + DebateStage.QI: DebateStage.CHENG, + DebateStage.CHENG: DebateStage.ZHUAN, + DebateStage.ZHUAN: DebateStage.HE, + DebateStage.HE: None + } + + next_stage = stage_transitions.get(self.current_stage) + if next_stage: + self.current_stage = next_stage + self.stage_progress = 0 + return True + else: + # 辩论结束 + self._emit_event("debate_finished", { + "total_duration": (datetime.now() - self.debate_start_time).total_seconds(), + "total_handoffs": self.total_handoffs, + "stages_completed": len(self.stage_metrics) + }) + return False + + def _initialize_new_stage(self): + """初始化新阶段""" + self._initialize_stage_metrics() + + # 清空发言请求队列 + while not self.speaker_request_queue.empty(): + try: + self.speaker_request_queue.get_nowait() + except queue.Empty: + break + + self.pending_requests.clear() + + def record_speech(self, speaker: str, message: str, metadata: Dict[str, Any] = None): + """记录发言""" + with self.flow_lock: + speech_record = { + "timestamp": datetime.now().isoformat(), + "stage": self.current_stage.value, + "stage_progress": self.stage_progress, + "speaker": speaker, + "message": message, + "total_handoffs": self.total_handoffs, + "metadata": metadata or {} + } + + self.debate_history.append(speech_record) + self.current_speaker = speaker + + # 更新阶段指标 + self._update_stage_metrics(speaker, message) + + # 如果是转阶段,增加handoff计数 + if self.current_stage == DebateStage.ZHUAN: + self.total_handoffs += 1 + + # 推进进度 + self.stage_progress += 1 + + # 移除已完成的发言请求 + if speaker in self.pending_requests: + del self.pending_requests[speaker] + + # 触发事件 + self._emit_event("speech_recorded", { + "speaker": speaker, + "stage": self.current_stage.value, + "progress": self.stage_progress + }) + + def _update_stage_metrics(self, speaker: str, message: str): + """更新阶段指标""" + # 更新发言计数 + self.current_stage_metrics.speech_count += 1 + + # 更新发言者分布 + if speaker not in self.current_stage_metrics.speaker_distribution: + self.current_stage_metrics.speaker_distribution[speaker] = 0 + self.current_stage_metrics.speaker_distribution[speaker] += 1 + + # 计算参与平衡度 + self.current_stage_metrics.participation_balance = self._calculate_participation_balance() + + # 计算质量分数(简化版本) + self.current_stage_metrics.quality_score = self._calculate_quality_score(message) + + # 计算转换准备度 + self.current_stage_metrics.transition_readiness = self._calculate_transition_readiness() + + def _calculate_topic_drift(self) -> float: + """计算主题偏移度""" + # 简化实现 + return 0.1 + + def _calculate_emotional_intensity(self) -> float: + """计算情绪强度""" + # 简化实现 + return 0.5 + + def _calculate_argument_balance(self) -> float: + """计算论点平衡度""" + # 简化实现 + return 0.7 + + def _calculate_time_pressure(self) -> float: + """计算时间压力""" + config = self.stage_configs[self.current_stage] + stage_duration = (datetime.now() - self.current_stage_metrics.start_time).total_seconds() + max_duration = config.get("max_duration", 600) + return min(1.0, stage_duration / max_duration) + + def _calculate_participation_balance(self) -> float: + """计算参与平衡度""" + if not self.current_stage_metrics.speaker_distribution: + return 1.0 + + counts = list(self.current_stage_metrics.speaker_distribution.values()) + if not counts: + return 1.0 + + avg_count = sum(counts) / len(counts) + variance = sum((count - avg_count) ** 2 for count in counts) / len(counts) + + # 归一化到0-1范围 + balance = 1.0 / (1.0 + variance) + return balance + + def _calculate_quality_score(self, message: str) -> float: + """计算质量分数""" + # 简化实现,基于消息长度和关键词 + base_score = min(1.0, len(message) / 100) + + # 检查关键词 + quality_keywords = ["因为", "所以", "但是", "然而", "数据", "证据", "分析"] + keyword_bonus = sum(0.1 for keyword in quality_keywords if keyword in message) + + return min(1.0, base_score + keyword_bonus) + + def _calculate_transition_readiness(self) -> float: + """计算转换准备度""" + # 综合多个因素 + progress_factor = self.stage_progress / self.stage_configs[self.current_stage]["max_progress"] + quality_factor = self.current_stage_metrics.quality_score + balance_factor = self.current_stage_metrics.participation_balance + + readiness = (progress_factor * 0.4 + quality_factor * 0.3 + balance_factor * 0.3) + return min(1.0, readiness) + + def _emit_event(self, event_type: str, data: Dict[str, Any]): + """发出事件""" + event = FlowEvent( + event_type=event_type, + timestamp=datetime.now(), + data=data, + source="flow_controller" + ) + + self.event_queue.put(event) + + # 调用事件处理器 + for handler in self.event_handlers.get(event_type, []): + try: + handler(event) + except Exception as e: + print(f"事件处理器错误: {e}") + + def add_event_handler(self, event_type: str, handler: Callable): + """添加事件处理器""" + self.event_handlers[event_type].append(handler) + + def get_flow_status(self) -> Dict[str, Any]: + """获取流程状态""" + return { + "current_stage": self.current_stage.value, + "stage_progress": self.stage_progress, + "total_handoffs": self.total_handoffs, + "current_speaker": self.current_speaker, + "stage_metrics": { + "duration": (datetime.now() - self.current_stage_metrics.start_time).total_seconds(), + "speech_count": self.current_stage_metrics.speech_count, + "quality_score": self.current_stage_metrics.quality_score, + "participation_balance": self.current_stage_metrics.participation_balance, + "transition_readiness": self.current_stage_metrics.transition_readiness + }, + "pending_requests": len(self.pending_requests), + "config": { + "mode": self.config.mode.value, + "auto_transition": self.config.auto_stage_transition, + "quality_threshold": self.config.quality_threshold + } + } + + def save_flow_data(self, filename: str = "debate_flow_data.json"): + """保存流程数据""" + flow_data = { + "config": { + "mode": self.config.mode.value, + "transition_triggers": [t.value for t in self.config.transition_triggers], + "speaker_selection_strategy": self.config.speaker_selection_strategy.value, + "quality_threshold": self.config.quality_threshold, + "auto_stage_transition": self.config.auto_stage_transition + }, + "current_state": { + "stage": self.current_stage.value, + "progress": self.stage_progress, + "total_handoffs": self.total_handoffs, + "current_speaker": self.current_speaker, + "debate_start_time": self.debate_start_time.isoformat() + }, + "stage_metrics": { + stage.value: { + "start_time": metrics.start_time.isoformat(), + "duration": metrics.duration, + "speech_count": metrics.speech_count, + "quality_score": metrics.quality_score, + "participation_balance": metrics.participation_balance, + "speaker_distribution": metrics.speaker_distribution + } for stage, metrics in self.stage_metrics.items() + }, + "current_stage_metrics": { + "start_time": self.current_stage_metrics.start_time.isoformat(), + "duration": (datetime.now() - self.current_stage_metrics.start_time).total_seconds(), + "speech_count": self.current_stage_metrics.speech_count, + "quality_score": self.current_stage_metrics.quality_score, + "participation_balance": self.current_stage_metrics.participation_balance, + "speaker_distribution": self.current_stage_metrics.speaker_distribution, + "transition_readiness": self.current_stage_metrics.transition_readiness + }, + "debate_history": self.debate_history, + "stage_transition_history": self.stage_transition_history, + "timestamp": datetime.now().isoformat() + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(flow_data, f, ensure_ascii=False, indent=2) + + print(f"✅ 流程数据已保存到 {filename}") + +def main(): + """测试优化的辩论流程控制系统""" + print("🎭 测试优化的辩论流程控制系统") + print("=" * 50) + + # 创建配置 + config = FlowControlConfig( + mode=FlowControlMode.ADAPTIVE, + transition_triggers=[TransitionTrigger.PROGRESS_BASED, TransitionTrigger.QUALITY_BASED], + speaker_selection_strategy=SpeakerSelectionStrategy.CONTEXT_AWARE, + auto_stage_transition=True + ) + + # 创建流程控制器 + controller = OptimizedDebateFlowController(config) + + # 添加事件处理器 + def on_stage_advanced(event): + print(f"🎭 阶段转换: {event.data}") + + def on_speech_recorded(event): + print(f"🗣️ 发言记录: {event.data['speaker']} 在 {event.data['stage']} 阶段") + + controller.add_event_handler("stage_advanced", on_stage_advanced) + controller.add_event_handler("speech_recorded", on_speech_recorded) + + # 模拟辩论流程 + test_speeches = [ + ("吕洞宾", "我认为AI投资具有巨大的潜力和机会。"), + ("何仙姑", "但我们也需要考虑其中的风险因素。"), + ("铁拐李", "数据显示AI行业的增长率确实很高。"), + ("汉钟离", "然而市场波动性也不容忽视。") + ] + + print("\n📋 开始模拟辩论流程") + print("-" * 30) + + for i, (speaker, message) in enumerate(test_speeches): + print(f"\n第 {i+1} 轮发言:") + + # 获取当前发言者 + current_speaker = controller.get_current_speaker() + print(f"推荐发言者: {current_speaker}") + + # 记录发言 + controller.record_speech(speaker, message) + + # 显示流程状态 + status = controller.get_flow_status() + print(f"当前状态: {status['current_stage']} 阶段,进度 {status['stage_progress']}") + print(f"质量分数: {status['stage_metrics']['quality_score']:.3f}") + print(f"参与平衡: {status['stage_metrics']['participation_balance']:.3f}") + + # 检查是否需要推进阶段 + if controller._should_advance_stage(): + print("🔄 准备推进到下一阶段") + controller.advance_stage() + + # 测试发言请求 + print("\n📢 测试发言请求系统") + print("-" * 30) + + controller.request_speaking_turn("正1", "需要反驳对方观点", urgency=4, topic_relevance=0.9) + controller.request_speaking_turn("反2", "补充论据", urgency=2, topic_relevance=0.7) + + next_speaker = controller.get_current_speaker() + print(f"基于请求的下一位发言者: {next_speaker}") + + # 保存数据 + controller.save_flow_data("test_flow_data.json") + + print("\n✅ 测试完成") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/core/debate_system/debates/qi_cheng_zhuan_he_debate.py b/jixia_academy/core/debate_system/debates/qi_cheng_zhuan_he_debate.py new file mode 100644 index 0000000..d99f23e --- /dev/null +++ b/jixia_academy/core/debate_system/debates/qi_cheng_zhuan_he_debate.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +太公心易 - 起承转合辩论系统 +基于先天八卦的八仙辩论架构 +""" + +import asyncio +import json +from datetime import datetime +from typing import Dict, List, Any, Optional +from dataclasses import dataclass +from enum import Enum +import sys +import os +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from enhanced_priority_algorithm import EnhancedPriorityAlgorithm, SpeechAnalysis + +class DebateStage(Enum): + """辩论阶段枚举""" + QI = "起" # 八仙按先天八卦顺序 + CHENG = "承" # 雁阵式承接 + ZHUAN = "转" # 自由辩论(36次handoff) + HE = "合" # 交替总结 + +@dataclass +class Speaker: + """发言者数据类""" + name: str + role: str + team: str # "positive" or "negative" + bagua_position: Optional[int] = None # 八卦位置(0-7) + +@dataclass +class DebateContext: + """辩论上下文""" + current_stage: DebateStage + stage_progress: int + total_handoffs: int + current_speaker: Optional[str] = None + last_message: Optional[str] = None + debate_history: List[Dict] = None + last_priority_analysis: Optional[Dict[str, Any]] = None + +class QiChengZhuanHeDebateSystem: + """起承转合辩论系统""" + + def __init__(self): + # 八仙配置(按先天八卦顺序) + self.baxian_speakers = { + "吕洞宾": Speaker("吕洞宾", "剑仙投资顾问", "neutral", 0), # 乾 + "何仙姑": Speaker("何仙姑", "慈悲风控专家", "neutral", 1), # 兑 + "铁拐李": Speaker("铁拐李", "逆向思维专家", "neutral", 2), # 离 + "汉钟离": Speaker("汉钟离", "平衡协调者", "neutral", 3), # 震 + "蓝采和": Speaker("蓝采和", "创新思维者", "neutral", 4), # 巽 + "张果老": Speaker("张果老", "历史智慧者", "neutral", 5), # 坎 + "韩湘子": Speaker("韩湘子", "艺术感知者", "neutral", 6), # 艮 + "曹国舅": Speaker("曹国舅", "实务执行者", "neutral", 7) # 坤 + } + + # 雁阵队伍配置 + self.goose_formation = { + "positive": ["正1", "正2", "正3", "正4"], + "negative": ["反1", "反2", "反3", "反4"] + } + + # 辩论状态 + self.context = DebateContext( + current_stage=DebateStage.QI, + stage_progress=0, + total_handoffs=0, + debate_history=[] + ) + + # 阶段配置 + self.stage_configs = { + DebateStage.QI: { + "duration": "8-10分钟", + "max_progress": 8, # 八仙轮流发言 + "description": "八仙按先天八卦顺序阐述观点" + }, + DebateStage.CHENG: { + "duration": "8-10分钟", + "max_progress": 8, # 正反各4人 + "description": "雁阵式承接,总体阐述+讥讽" + }, + DebateStage.ZHUAN: { + "duration": "12-15分钟", + "max_progress": 36, # 36次handoff + "description": "自由辩论,优先级算法决定发言" + }, + DebateStage.HE: { + "duration": "8-10分钟", + "max_progress": 8, # 交替总结 + "description": "交替总结,最终论证" + } + } + + # 增强版优先级算法 + self.priority_algorithm = EnhancedPriorityAlgorithm() + + # 记忆系统 + self.memory_system = DebateMemorySystem() + + def get_current_speaker(self) -> str: + """获取当前发言者""" + stage = self.context.current_stage + progress = self.context.stage_progress + + if stage == DebateStage.QI: + return self._get_bagua_speaker(progress) + elif stage == DebateStage.CHENG: + return self._get_goose_formation_speaker(progress) + elif stage == DebateStage.ZHUAN: + return self._get_priority_speaker() + elif stage == DebateStage.HE: + return self._get_alternating_speaker(progress) + + return "未知发言者" + + def _get_bagua_speaker(self, progress: int) -> str: + """获取八卦顺序发言者""" + bagua_sequence = ["吕洞宾", "何仙姑", "铁拐李", "汉钟离", "蓝采和", "张果老", "韩湘子", "曹国舅"] + return bagua_sequence[progress % 8] + + def _get_goose_formation_speaker(self, progress: int) -> str: + """获取雁阵发言者""" + if progress < 4: + # 正方雁阵 + return self.goose_formation["positive"][progress] + else: + # 反方雁阵 + return self.goose_formation["negative"][progress - 4] + + def _get_priority_speaker(self) -> str: + """获取优先级发言者(转阶段)""" + available_speakers = ["正1", "正2", "正3", "正4", "反1", "反2", "反3", "反4"] + + # 构建上下文 + context = { + "current_stage": self.context.current_stage.value, + "stage_progress": self.context.stage_progress, + "max_progress": self.stage_configs[self.context.current_stage]["max_progress"], + "time_remaining": max(0.1, 1.0 - (self.context.stage_progress / self.stage_configs[self.context.current_stage]["max_progress"])), + "topic_keywords": ["投资", "AI", "风险", "收益"], # 可配置 + "positive_team_score": 0.5, # 可动态计算 + "negative_team_score": 0.5, # 可动态计算 + "positive_recent_speeches": len([h for h in self.context.debate_history[-10:] if "正" in h.get("speaker", "")]), + "negative_recent_speeches": len([h for h in self.context.debate_history[-10:] if "反" in h.get("speaker", "")]) + } + + # 获取最近发言历史 + recent_speeches = self.context.debate_history[-10:] if self.context.debate_history else [] + + next_speaker, score, analysis = self.priority_algorithm.get_next_speaker( + available_speakers, context, recent_speeches + ) + + # 记录分析结果 + self.context.last_priority_analysis = { + "recommended_speaker": next_speaker, + "priority_score": score, + "analysis": analysis, + "timestamp": datetime.now().isoformat() + } + + return next_speaker + + def _get_alternating_speaker(self, progress: int) -> str: + """获取交替总结发言者""" + alternating_sequence = ["反1", "正1", "反2", "正2", "反3", "正3", "反4", "正4"] + return alternating_sequence[progress % 8] + + def advance_stage(self): + """推进辩论阶段""" + current_config = self.stage_configs[self.context.current_stage] + + if self.context.stage_progress >= current_config["max_progress"] - 1: + # 当前阶段完成,进入下一阶段 + self._transition_to_next_stage() + else: + # 当前阶段继续 + self.context.stage_progress += 1 + + def _transition_to_next_stage(self): + """转换到下一阶段""" + stage_transitions = { + DebateStage.QI: DebateStage.CHENG, + DebateStage.CHENG: DebateStage.ZHUAN, + DebateStage.ZHUAN: DebateStage.HE, + DebateStage.HE: None # 辩论结束 + } + + next_stage = stage_transitions[self.context.current_stage] + if next_stage: + self.context.current_stage = next_stage + self.context.stage_progress = 0 + print(f"🎭 辩论进入{next_stage.value}阶段") + else: + print("🎉 辩论结束!") + + def record_speech(self, speaker: str, message: str): + """记录发言""" + speech_record = { + "timestamp": datetime.now().isoformat(), + "stage": self.context.current_stage.value, + "stage_progress": self.context.stage_progress, + "speaker": speaker, + "message": message, + "total_handoffs": self.context.total_handoffs + } + + self.context.debate_history.append(speech_record) + self.context.last_message = message + self.context.current_speaker = speaker + + # 更新记忆系统 + self.memory_system.store_speech(speaker, message, self.context) + + # 如果是转阶段,增加handoff计数 + if self.context.current_stage == DebateStage.ZHUAN: + self.context.total_handoffs += 1 + + def get_stage_info(self) -> Dict[str, Any]: + """获取当前阶段信息""" + stage = self.context.current_stage + config = self.stage_configs[stage] + + return { + "current_stage": stage.value, + "stage_progress": self.context.stage_progress, + "max_progress": config["max_progress"], + "description": config["description"], + "current_speaker": self.get_current_speaker(), + "total_handoffs": self.context.total_handoffs + } + + def save_debate_state(self, filename: str = "debate_state.json"): + """保存辩论状态""" + state_data = { + "context": { + "current_stage": self.context.current_stage.value, + "stage_progress": self.context.stage_progress, + "total_handoffs": self.context.total_handoffs, + "current_speaker": self.context.current_speaker, + "last_message": self.context.last_message + }, + "debate_history": self.context.debate_history, + "memory_data": self.memory_system.get_memory_data() + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(state_data, f, ensure_ascii=False, indent=2) + + print(f"💾 辩论状态已保存到 {filename}") + +# 旧的PriorityAlgorithm类已被EnhancedPriorityAlgorithm替换 + +class DebateMemorySystem: + """辩论记忆系统""" + + def __init__(self): + self.speaker_memories = {} + self.debate_memories = [] + + def store_speech(self, speaker: str, message: str, context: DebateContext): + """存储发言记忆""" + if speaker not in self.speaker_memories: + self.speaker_memories[speaker] = [] + + memory_entry = { + "timestamp": datetime.now().isoformat(), + "stage": context.current_stage.value, + "message": message, + "context": { + "stage_progress": context.stage_progress, + "total_handoffs": context.total_handoffs + } + } + + self.speaker_memories[speaker].append(memory_entry) + self.debate_memories.append(memory_entry) + + def get_speaker_memory(self, speaker: str, limit: int = 5) -> List[Dict]: + """获取发言者记忆""" + if speaker in self.speaker_memories: + return self.speaker_memories[speaker][-limit:] + return [] + + def get_memory_data(self) -> Dict[str, Any]: + """获取记忆数据""" + return { + "speaker_memories": self.speaker_memories, + "debate_memories": self.debate_memories + } + +def main(): + """主函数 - 测试起承转合辩论系统""" + print("🚀 太公心易 - 起承转合辩论系统") + print("=" * 60) + + # 创建辩论系统 + debate_system = QiChengZhuanHeDebateSystem() + + # 测试各阶段 + test_messages = [ + "起:八仙按先天八卦顺序阐述观点", + "承:雁阵式承接,总体阐述+讥讽", + "转:自由辩论,36次handoff", + "合:交替总结,最终论证" + ] + + for i, message in enumerate(test_messages): + stage_info = debate_system.get_stage_info() + current_speaker = debate_system.get_current_speaker() + + print(f"\n🎭 当前阶段: {stage_info['current_stage']}") + print(f"📊 进度: {stage_info['stage_progress'] + 1}/{stage_info['max_progress']}") + print(f"🗣️ 发言者: {current_speaker}") + print(f"💬 消息: {message}") + + # 记录发言 + debate_system.record_speech(current_speaker, message) + + # 推进阶段 + debate_system.advance_stage() + + # 保存状态 + debate_system.save_debate_state() + + print("\n✅ 起承转合辩论系统测试完成!") + +if __name__ == "__main__": + main() + diff --git a/jixia_academy/core/debate_system/engines/__init__.py b/jixia_academy/core/debate_system/engines/__init__.py new file mode 100644 index 0000000..d226a4a --- /dev/null +++ b/jixia_academy/core/debate_system/engines/__init__.py @@ -0,0 +1 @@ +# 稷下学宫引擎模块 \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/baxian_data_mapping.py b/jixia_academy/core/debate_system/engines/baxian_data_mapping.py new file mode 100644 index 0000000..8abdf90 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/baxian_data_mapping.py @@ -0,0 +1,43 @@ +# 设计八仙与数据源的智能映射 +immortal_data_mapping = { + '吕洞宾': { + 'specialty': 'technical_analysis', # 技术分析专家 + 'preferred_data_types': ['historical', 'price'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '何仙姑': { + 'specialty': 'risk_metrics', # 风险控制专家 + 'preferred_data_types': ['price', 'profile'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '张果老': { + 'specialty': 'historical_data', # 历史数据分析师 + 'preferred_data_types': ['historical'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '韩湘子': { + 'specialty': 'sector_analysis', # 新兴资产专家 + 'preferred_data_types': ['profile', 'news'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '汉钟离': { + 'specialty': 'market_movers', # 热点追踪 + 'preferred_data_types': ['news', 'price'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '蓝采和': { + 'specialty': 'value_discovery', # 潜力股发现 + 'preferred_data_types': ['screener', 'profile'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '铁拐李': { + 'specialty': 'contrarian_analysis', # 逆向思维专家 + 'preferred_data_types': ['profile', 'short_interest'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '曹国舅': { + 'specialty': 'macro_economics', # 宏观经济分析师 + 'preferred_data_types': ['profile', 'institutional_holdings'], + 'data_providers': ['OpenBB', 'RapidAPI'] + } +} \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/data_abstraction.py b/jixia_academy/core/debate_system/engines/data_abstraction.py new file mode 100644 index 0000000..57e5981 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/data_abstraction.py @@ -0,0 +1,38 @@ +from abc import ABC, abstractmethod +from typing import List, Optional +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews + +class DataProvider(ABC): + """金融数据提供商抽象基类""" + + @abstractmethod + def get_quote(self, symbol: str) -> Optional[StockQuote]: + """获取股票报价""" + pass + + @abstractmethod + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + """获取历史价格数据""" + pass + + @abstractmethod + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + """获取公司概况""" + pass + + @abstractmethod + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + """获取相关新闻""" + pass + + @property + @abstractmethod + def name(self) -> str: + """数据提供商名称""" + pass + + @property + @abstractmethod + def priority(self) -> int: + """优先级(数字越小优先级越高)""" + pass \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/data_abstraction_layer.py b/jixia_academy/core/debate_system/engines/data_abstraction_layer.py new file mode 100644 index 0000000..d8934a2 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/data_abstraction_layer.py @@ -0,0 +1,109 @@ + from typing import List, Optional +import asyncio +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +from src.jixia.engines.rapidapi_adapter import RapidAPIDataProvider +from src.jixia.engines.openbb_adapter import OpenBBDataProvider + +class DataAbstractionLayer: + """金融数据抽象层管理器""" + + def __init__(self): + self.providers: List[DataProvider] = [] + self._initialize_providers() + + def _initialize_providers(self): + """初始化所有可用的数据提供商""" + # 根据配置和环境动态加载适配器 + try: + self.providers.append(OpenBBDataProvider()) + except Exception as e: + print(f"警告: OpenBBDataProvider 初始化失败: {e}") + + try: + self.providers.append(RapidAPIDataProvider()) + except Exception as e: + print(f"警告: RapidAPIDataProvider 初始化失败: {e}") + + # 按优先级排序 + self.providers.sort(key=lambda p: p.priority) + print(f"数据抽象层初始化完成,已加载 {len(self.providers)} 个数据提供商") + for provider in self.providers: + print(f" - {provider.name} (优先级: {provider.priority})") + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + """获取股票报价(带故障转移)""" + for provider in self.providers: + try: + quote = provider.get_quote(symbol) + if quote: + print(f"✅ 通过 {provider.name} 获取到 {symbol} 的报价") + return quote + except Exception as e: + print(f"警告: {provider.name} 获取报价失败: {e}") + continue + print(f"❌ 所有数据提供商都无法获取 {symbol} 的报价") + return None + + async def get_quote_async(self, symbol: str) -> Optional[StockQuote]: + """异步获取股票报价(带故障转移)""" + for provider in self.providers: + try: + # 如果提供商支持异步方法,则使用异步方法 + if hasattr(provider, 'get_quote_async'): + quote = await provider.get_quote_async(symbol) + else: + # 否则在执行器中运行同步方法 + quote = await asyncio.get_event_loop().run_in_executor( + None, provider.get_quote, symbol + ) + if quote: + print(f"✅ 通过 {provider.name} 异步获取到 {symbol} 的报价") + return quote + except Exception as e: + print(f"警告: {provider.name} 异步获取报价失败: {e}") + continue + print(f"❌ 所有数据提供商都无法异步获取 {symbol} 的报价") + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + """获取历史价格数据(带故障转移)""" + for provider in self.providers: + try: + prices = provider.get_historical_prices(symbol, days) + if prices: + print(f"✅ 通过 {provider.name} 获取到 {symbol} 的历史价格数据") + return prices + except Exception as e: + print(f"警告: {provider.name} 获取历史价格失败: {e}") + continue + print(f"❌ 所有数据提供商都无法获取 {symbol} 的历史价格数据") + return [] + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + """获取公司概况(带故障转移)""" + for provider in self.providers: + try: + profile = provider.get_company_profile(symbol) + if profile: + print(f"✅ 通过 {provider.name} 获取到 {symbol} 的公司概况") + return profile + except Exception as e: + print(f"警告: {provider.name} 获取公司概况失败: {e}") + continue + print(f"❌ 所有数据提供商都无法获取 {symbol} 的公司概况") + return None + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + """获取相关新闻(带故障转移)""" + for provider in self.providers: + try: + news = provider.get_news(symbol, limit) + if news: + print(f"✅ 通过 {provider.name} 获取到 {symbol} 的相关新闻") + return news + except Exception as e: + print(f"警告: {provider.name} 获取新闻失败: {e}") + continue + print(f"❌ 所有数据提供商都无法获取 {symbol} 的相关新闻") + return [] \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/data_cache.py b/jixia_academy/core/debate_system/engines/data_cache.py new file mode 100644 index 0000000..7497d1e --- /dev/null +++ b/jixia_academy/core/debate_system/engines/data_cache.py @@ -0,0 +1,37 @@ +import time +from typing import Any, Optional +from functools import lru_cache + +class DataCache: + """金融数据缓存""" + + def __init__(self): + self._cache = {} + self._cache_times = {} + self.default_ttl = 60 # 默认缓存时间(秒) + + def get(self, key: str) -> Optional[Any]: + """获取缓存数据""" + if key in self._cache: + # 检查是否过期 + if time.time() - self._cache_times[key] < self.default_ttl: + return self._cache[key] + else: + # 删除过期缓存 + del self._cache[key] + del self._cache_times[key] + return None + + def set(self, key: str, value: Any, ttl: Optional[int] = None): + """设置缓存数据""" + self._cache[key] = value + self._cache_times[key] = time.time() + if ttl: + # 可以为特定数据设置不同的TTL + pass # 实际实现中需要更复杂的TTL管理机制 + + @lru_cache(maxsize=128) + def get_quote_cache(self, symbol: str) -> Optional[Any]: + """LRU缓存装饰器示例""" + # 这个方法将自动缓存最近128个调用的结果 + pass \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/data_quality_monitor.py b/jixia_academy/core/debate_system/engines/data_quality_monitor.py new file mode 100644 index 0000000..ed48d48 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/data_quality_monitor.py @@ -0,0 +1,49 @@ +from typing import Dict, Any +from datetime import datetime + +class DataQualityMonitor: + """数据质量监控""" + + def __init__(self): + self.provider_stats = {} + + def record_access(self, provider_name: str, success: bool, response_time: float, data_size: int): + """记录数据访问统计""" + if provider_name not in self.provider_stats: + self.provider_stats[provider_name] = { + 'total_requests': 0, + 'successful_requests': 0, + 'failed_requests': 0, + 'total_response_time': 0, + 'total_data_size': 0, + 'last_access': None + } + + stats = self.provider_stats[provider_name] + stats['total_requests'] += 1 + if success: + stats['successful_requests'] += 1 + else: + stats['failed_requests'] += 1 + stats['total_response_time'] += response_time + stats['total_data_size'] += data_size + stats['last_access'] = datetime.now() + + def get_provider_health(self, provider_name: str) -> Dict[str, Any]: + """获取提供商健康状况""" + if provider_name not in self.provider_stats: + return {'status': 'unknown'} + + stats = self.provider_stats[provider_name] + success_rate = stats['successful_requests'] / stats['total_requests'] if stats['total_requests'] > 0 else 0 + avg_response_time = stats['total_response_time'] / stats['total_requests'] if stats['total_requests'] > 0 else 0 + + status = 'healthy' if success_rate > 0.95 and avg_response_time < 2.0 else 'degraded' if success_rate > 0.8 else 'unhealthy' + + return { + 'status': status, + 'success_rate': success_rate, + 'avg_response_time': avg_response_time, + 'total_requests': stats['total_requests'], + 'last_access': stats['last_access'] + } \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/jixia_load_balancer.py b/jixia_academy/core/debate_system/engines/jixia_load_balancer.py new file mode 100644 index 0000000..1c9ae46 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/jixia_load_balancer.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫负载均衡器 +实现八仙论道的API负载分担策略 +""" + +import time +import random +import requests +from datetime import datetime, timezone +from typing import Dict, List, Any, Optional, Tuple +from dataclasses import dataclass +from collections import defaultdict +import json +import os + +@dataclass +class APIResult: + """API调用结果""" + success: bool + data: Dict[str, Any] + api_used: str + response_time: float + error: Optional[str] = None + cached: bool = False + +class RateLimiter: + """速率限制器""" + + def __init__(self): + self.api_calls = defaultdict(list) + self.limits = { + 'alpha_vantage': {'per_minute': 500, 'per_month': 500000}, + 'yahoo_finance_15': {'per_minute': 500, 'per_month': 500000}, + 'webull': {'per_minute': 500, 'per_month': 500000}, + 'seeking_alpha': {'per_minute': 500, 'per_month': 500000} + } + + def is_rate_limited(self, api_name: str) -> bool: + """检查是否达到速率限制""" + now = time.time() + calls = self.api_calls[api_name] + + # 清理1分钟前的记录 + self.api_calls[api_name] = [call_time for call_time in calls if now - call_time < 60] + + # 检查每分钟限制 + if len(self.api_calls[api_name]) >= self.limits[api_name]['per_minute'] * 0.9: # 90%阈值 + return True + + return False + + def record_call(self, api_name: str): + """记录API调用""" + self.api_calls[api_name].append(time.time()) + +class APIHealthChecker: + """API健康检查器""" + + def __init__(self): + self.health_status = { + 'alpha_vantage': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0}, + 'yahoo_finance_15': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0}, + 'webull': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0}, + 'seeking_alpha': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0} + } + self.check_interval = 300 # 5分钟检查一次 + + def is_healthy(self, api_name: str) -> bool: + """检查API是否健康""" + status = self.health_status[api_name] + now = time.time() + + # 如果距离上次检查超过间隔时间,进行健康检查 + if now - status['last_check'] > self.check_interval: + self._perform_health_check(api_name) + + return status['healthy'] + + def _perform_health_check(self, api_name: str): + """执行健康检查""" + # 这里可以实现具体的健康检查逻辑 + # 暂时简化为基于连续失败次数判断 + status = self.health_status[api_name] + status['last_check'] = time.time() + + # 如果连续失败超过3次,标记为不健康 + if status['consecutive_failures'] > 3: + status['healthy'] = False + else: + status['healthy'] = True + + def record_success(self, api_name: str): + """记录成功调用""" + self.health_status[api_name]['consecutive_failures'] = 0 + self.health_status[api_name]['healthy'] = True + + def record_failure(self, api_name: str): + """记录失败调用""" + self.health_status[api_name]['consecutive_failures'] += 1 + +class DataNormalizer: + """数据标准化处理器""" + + def normalize_stock_quote(self, raw_data: dict, api_source: str) -> dict: + """将不同API的股票报价数据标准化""" + try: + if api_source == 'alpha_vantage': + return self._normalize_alpha_vantage_quote(raw_data) + elif api_source == 'yahoo_finance_15': + return self._normalize_yahoo_quote(raw_data) + elif api_source == 'webull': + return self._normalize_webull_quote(raw_data) + elif api_source == 'seeking_alpha': + return self._normalize_seeking_alpha_quote(raw_data) + else: + return {'error': f'Unknown API source: {api_source}'} + except Exception as e: + return {'error': f'Data normalization failed: {str(e)}'} + + def _normalize_alpha_vantage_quote(self, data: dict) -> dict: + """标准化Alpha Vantage数据格式""" + global_quote = data.get('Global Quote', {}) + return { + 'symbol': global_quote.get('01. symbol'), + 'price': float(global_quote.get('05. price', 0)), + 'change': float(global_quote.get('09. change', 0)), + 'change_percent': global_quote.get('10. change percent', '0%'), + 'volume': int(global_quote.get('06. volume', 0)), + 'high': float(global_quote.get('03. high', 0)), + 'low': float(global_quote.get('04. low', 0)), + 'source': 'alpha_vantage', + 'timestamp': global_quote.get('07. latest trading day') + } + + def _normalize_yahoo_quote(self, data: dict) -> dict: + """标准化Yahoo Finance数据格式""" + body = data.get('body', {}) + return { + 'symbol': body.get('symbol'), + 'price': float(body.get('regularMarketPrice', 0)), + 'change': float(body.get('regularMarketChange', 0)), + 'change_percent': f"{body.get('regularMarketChangePercent', 0):.2f}%", + 'volume': int(body.get('regularMarketVolume', 0)), + 'high': float(body.get('regularMarketDayHigh', 0)), + 'low': float(body.get('regularMarketDayLow', 0)), + 'source': 'yahoo_finance_15', + 'timestamp': body.get('regularMarketTime') + } + + def _normalize_webull_quote(self, data: dict) -> dict: + """标准化Webull数据格式""" + if 'stocks' in data and len(data['stocks']) > 0: + stock = data['stocks'][0] + return { + 'symbol': stock.get('symbol'), + 'price': float(stock.get('close', 0)), + 'change': float(stock.get('change', 0)), + 'change_percent': f"{stock.get('changeRatio', 0):.2f}%", + 'volume': int(stock.get('volume', 0)), + 'high': float(stock.get('high', 0)), + 'low': float(stock.get('low', 0)), + 'source': 'webull', + 'timestamp': stock.get('timeStamp') + } + return {'error': 'No stock data found in Webull response'} + + def _normalize_seeking_alpha_quote(self, data: dict) -> dict: + """标准化Seeking Alpha数据格式""" + if 'data' in data and len(data['data']) > 0: + stock_data = data['data'][0] + attributes = stock_data.get('attributes', {}) + return { + 'symbol': attributes.get('slug'), + 'price': float(attributes.get('lastPrice', 0)), + 'change': float(attributes.get('dayChange', 0)), + 'change_percent': f"{attributes.get('dayChangePercent', 0):.2f}%", + 'volume': int(attributes.get('volume', 0)), + 'source': 'seeking_alpha', + 'market_cap': attributes.get('marketCap'), + 'pe_ratio': attributes.get('peRatio') + } + return {'error': 'No data found in Seeking Alpha response'} + +class JixiaLoadBalancer: + """稷下学宫负载均衡器""" + + def __init__(self, rapidapi_key: str): + self.rapidapi_key = rapidapi_key + self.rate_limiter = RateLimiter() + self.health_checker = APIHealthChecker() + self.data_normalizer = DataNormalizer() + self.cache = {} # 简单的内存缓存 + self.cache_ttl = 300 # 5分钟缓存 + + # API配置 + self.api_configs = { + 'alpha_vantage': { + 'host': 'alpha-vantage.p.rapidapi.com', + 'endpoints': { + 'stock_quote': '/query?function=GLOBAL_QUOTE&symbol={symbol}', + 'company_overview': '/query?function=OVERVIEW&symbol={symbol}', + 'earnings': '/query?function=EARNINGS&symbol={symbol}' + } + }, + 'yahoo_finance_15': { + 'host': 'yahoo-finance15.p.rapidapi.com', + 'endpoints': { + 'stock_quote': '/api/yahoo/qu/quote/{symbol}', + 'market_movers': '/api/yahoo/co/collections/day_gainers', + 'market_news': '/api/yahoo/ne/news' + } + }, + 'webull': { + 'host': 'webull.p.rapidapi.com', + 'endpoints': { + 'stock_quote': '/stock/search?keyword={symbol}', + 'market_movers': '/market/get-active-gainers' + } + }, + 'seeking_alpha': { + 'host': 'seeking-alpha.p.rapidapi.com', + 'endpoints': { + 'company_overview': '/symbols/get-profile?symbols={symbol}', + 'market_news': '/news/list?category=market-news' + } + } + } + + # 八仙API分配策略 + self.immortal_api_mapping = { + 'stock_quote': { + '吕洞宾': 'alpha_vantage', # 主力剑仙用最快的API + '何仙姑': 'yahoo_finance_15', # 风控专家用稳定的API + '张果老': 'webull', # 技术分析师用搜索强的API + '韩湘子': 'alpha_vantage', # 基本面研究用专业API + '汉钟离': 'yahoo_finance_15', # 量化专家用市场数据API + '蓝采和': 'webull', # 情绪分析师用活跃数据API + '曹国舅': 'seeking_alpha', # 宏观分析师用分析API + '铁拐李': 'alpha_vantage' # 逆向投资用基础数据API + }, + 'company_overview': { + '吕洞宾': 'alpha_vantage', + '何仙姑': 'seeking_alpha', + '张果老': 'alpha_vantage', + '韩湘子': 'seeking_alpha', + '汉钟离': 'alpha_vantage', + '蓝采和': 'seeking_alpha', + '曹国舅': 'seeking_alpha', + '铁拐李': 'alpha_vantage' + }, + 'market_movers': { + '吕洞宾': 'yahoo_finance_15', + '何仙姑': 'webull', + '张果老': 'yahoo_finance_15', + '韩湘子': 'webull', + '汉钟离': 'yahoo_finance_15', + '蓝采和': 'webull', + '曹国舅': 'yahoo_finance_15', + '铁拐李': 'webull' + }, + 'market_news': { + '吕洞宾': 'yahoo_finance_15', + '何仙姑': 'seeking_alpha', + '张果老': 'yahoo_finance_15', + '韩湘子': 'seeking_alpha', + '汉钟离': 'yahoo_finance_15', + '蓝采和': 'seeking_alpha', + '曹国舅': 'seeking_alpha', + '铁拐李': 'yahoo_finance_15' + } + } + + # 故障转移优先级 + self.failover_priority = { + 'alpha_vantage': ['webull', 'yahoo_finance_15'], + 'yahoo_finance_15': ['webull', 'alpha_vantage'], + 'webull': ['alpha_vantage', 'yahoo_finance_15'], + 'seeking_alpha': ['yahoo_finance_15', 'alpha_vantage'] + } + + def get_data_for_immortal(self, immortal_name: str, data_type: str, symbol: str = None) -> APIResult: + """为特定仙人获取数据""" + print(f"🎭 {immortal_name} 正在获取 {data_type} 数据...") + + # 检查缓存 + cache_key = f"{immortal_name}_{data_type}_{symbol}" + cached_result = self._get_cached_data(cache_key) + if cached_result: + print(f" 📦 使用缓存数据") + return cached_result + + # 获取该仙人的首选API + if data_type not in self.immortal_api_mapping: + return APIResult(False, {}, '', 0, f"Unsupported data type: {data_type}") + + preferred_api = self.immortal_api_mapping[data_type][immortal_name] + + # 尝试首选API + result = self._try_api(preferred_api, data_type, symbol) + if result.success: + self._cache_data(cache_key, result) + print(f" ✅ 成功从 {preferred_api} 获取数据 (响应时间: {result.response_time:.2f}s)") + return result + + # 故障转移到备用API + print(f" ⚠️ {preferred_api} 不可用,尝试备用API...") + backup_apis = self.failover_priority.get(preferred_api, []) + + for backup_api in backup_apis: + if data_type in self.api_configs[backup_api]['endpoints']: + result = self._try_api(backup_api, data_type, symbol) + if result.success: + self._cache_data(cache_key, result) + print(f" ✅ 成功从备用API {backup_api} 获取数据 (响应时间: {result.response_time:.2f}s)") + return result + + # 所有API都失败 + print(f" ❌ 所有API都不可用") + return APIResult(False, {}, '', 0, "All APIs failed") + + def _try_api(self, api_name: str, data_type: str, symbol: str = None) -> APIResult: + """尝试调用指定API""" + # 检查API健康状态和速率限制 + if not self.health_checker.is_healthy(api_name): + return APIResult(False, {}, api_name, 0, "API is unhealthy") + + if self.rate_limiter.is_rate_limited(api_name): + return APIResult(False, {}, api_name, 0, "Rate limited") + + # 构建请求 + config = self.api_configs[api_name] + if data_type not in config['endpoints']: + return APIResult(False, {}, api_name, 0, f"Endpoint {data_type} not supported") + + endpoint = config['endpoints'][data_type] + if symbol and '{symbol}' in endpoint: + endpoint = endpoint.format(symbol=symbol) + + url = f"https://{config['host']}{endpoint}" + headers = { + 'X-RapidAPI-Key': self.rapidapi_key, + 'X-RapidAPI-Host': config['host'] + } + + # 发起请求 + start_time = time.time() + try: + response = requests.get(url, headers=headers, timeout=10) + response_time = time.time() - start_time + + self.rate_limiter.record_call(api_name) + + if response.status_code == 200: + data = response.json() + + # 数据标准化 + if data_type == 'stock_quote': + normalized_data = self.data_normalizer.normalize_stock_quote(data, api_name) + else: + normalized_data = data + + self.health_checker.record_success(api_name) + return APIResult(True, normalized_data, api_name, response_time) + else: + error_msg = f"HTTP {response.status_code}: {response.text[:200]}" + self.health_checker.record_failure(api_name) + return APIResult(False, {}, api_name, response_time, error_msg) + + except Exception as e: + response_time = time.time() - start_time + self.health_checker.record_failure(api_name) + return APIResult(False, {}, api_name, response_time, str(e)) + + def _get_cached_data(self, cache_key: str) -> Optional[APIResult]: + """获取缓存数据""" + if cache_key in self.cache: + cached_item = self.cache[cache_key] + if time.time() - cached_item['timestamp'] < self.cache_ttl: + result = cached_item['result'] + result.cached = True + return result + else: + # 缓存过期,删除 + del self.cache[cache_key] + return None + + def _cache_data(self, cache_key: str, result: APIResult): + """缓存数据""" + self.cache[cache_key] = { + 'result': result, + 'timestamp': time.time() + } + + def get_load_distribution(self) -> dict: + """获取负载分布统计""" + api_calls = {} + total_calls = 0 + + for api_name, calls in self.rate_limiter.api_calls.items(): + call_count = len(calls) + api_calls[api_name] = call_count + total_calls += call_count + + if total_calls == 0: + return {} + + distribution = {} + for api_name, call_count in api_calls.items(): + health_status = self.health_checker.health_status[api_name] + distribution[api_name] = { + 'calls': call_count, + 'percentage': (call_count / total_calls) * 100, + 'healthy': health_status['healthy'], + 'consecutive_failures': health_status['consecutive_failures'] + } + + return distribution + + def conduct_immortal_debate(self, topic_symbol: str) -> Dict[str, APIResult]: + """进行八仙论道,每个仙人获取不同的数据""" + print(f"\n🏛️ 稷下学宫八仙论道开始 - 主题: {topic_symbol}") + print("=" * 60) + + immortals = ['吕洞宾', '何仙姑', '张果老', '韩湘子', '汉钟离', '蓝采和', '曹国舅', '铁拐李'] + debate_results = {} + + # 每个仙人获取股票报价数据 + for immortal in immortals: + result = self.get_data_for_immortal(immortal, 'stock_quote', topic_symbol) + debate_results[immortal] = result + + if result.success: + data = result.data + if 'price' in data: + print(f" 💰 {immortal}: ${data['price']:.2f} ({data.get('change_percent', 'N/A')}) via {result.api_used}") + + time.sleep(0.2) # 避免过快请求 + + print("\n📊 负载分布统计:") + distribution = self.get_load_distribution() + for api_name, stats in distribution.items(): + print(f" {api_name}: {stats['calls']} 次调用 ({stats['percentage']:.1f}%) - {'健康' if stats['healthy'] else '异常'}") + + return debate_results + +# 使用示例 +if __name__ == "__main__": + # 从环境变量获取API密钥 + rapidapi_key = os.getenv('RAPIDAPI_KEY') + if not rapidapi_key: + print("❌ 请设置RAPIDAPI_KEY环境变量") + exit(1) + + # 创建负载均衡器 + load_balancer = JixiaLoadBalancer(rapidapi_key) + + # 进行八仙论道 + results = load_balancer.conduct_immortal_debate('TSLA') + + print("\n🎉 八仙论道完成!") \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/openbb_adapter.py b/jixia_academy/core/debate_system/engines/openbb_adapter.py new file mode 100644 index 0000000..33ae949 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/openbb_adapter.py @@ -0,0 +1,75 @@ +from typing import List, Optional +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +from src.jixia.engines.openbb_engine import OpenBBEngine + +class OpenBBDataProvider(DataProvider): + """OpenBB引擎适配器""" + + def __init__(self): + self.engine = OpenBBEngine() + self._name = "OpenBB" + self._priority = 1 # 最高优先级 + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + result = self.engine.get_immortal_data("吕洞宾", "price", symbol) + if result.success and result.data: + # 解析OpenBB返回的数据并转换为StockQuote + # 注意:这里需要根据OpenBB实际返回的数据结构进行调整 + data = result.data + if isinstance(data, list) and len(data) > 0: + item = data[0] # 取第一条数据 + elif hasattr(data, '__dict__'): + item = data + else: + item = {} + + # 提取价格信息(根据openbb_stock_data.py中的字段) + price = 0 + if hasattr(item, 'close'): + price = float(item.close) + elif isinstance(item, dict) and 'close' in item: + price = float(item['close']) + + volume = 0 + if hasattr(item, 'volume'): + volume = int(item.volume) + elif isinstance(item, dict) and 'volume' in item: + volume = int(item['volume']) + + # 日期处理 + timestamp = None + if hasattr(item, 'date'): + timestamp = item.date + elif isinstance(item, dict) and 'date' in item: + timestamp = item['date'] + + return StockQuote( + symbol=symbol, + price=price, + change=0, # 需要计算 + change_percent=0, # 需要计算 + volume=volume, + timestamp=timestamp + ) + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + # 实现历史价格数据获取逻辑 + pass + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + # 实现公司概况获取逻辑 + pass + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + # 实现新闻获取逻辑 + pass + + @property + def name(self) -> str: + return self._name + + @property + def priority(self) -> int: + return self._priority \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/openbb_engine.py b/jixia_academy/core/debate_system/engines/openbb_engine.py new file mode 100644 index 0000000..87febfd --- /dev/null +++ b/jixia_academy/core/debate_system/engines/openbb_engine.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +""" +OpenBB 集成引擎 +为八仙论道提供更丰富的金融数据支撑 +""" + +from typing import Dict, List, Any, Optional +from dataclasses import dataclass + + +@dataclass +class ImmortalConfig: + """八仙配置数据类""" + primary: str + specialty: str + +@dataclass +class APIResult: + """API调用结果数据类""" + success: bool + data: Optional[Any] = None + provider_used: Optional[str] = None + error: Optional[str] = None + +class OpenBBEngine: + """OpenBB 集成引擎""" + + def __init__(self): + """ + 初始化 OpenBB 引擎 + """ + # 延迟导入 OpenBB,避免未安装时报错 + self._obb = None + + # 八仙专属数据源分配 + self.immortal_sources: Dict[str, ImmortalConfig] = { + '吕洞宾': ImmortalConfig( # 乾-技术分析专家 + primary='yfinance', + specialty='technical_analysis' + ), + '何仙姑': ImmortalConfig( # 坤-风险控制专家 + primary='yfinance', + specialty='risk_metrics' + ), + '张果老': ImmortalConfig( # 兑-历史数据分析师 + primary='yfinance', + specialty='historical_data' + ), + '韩湘子': ImmortalConfig( # 艮-新兴资产专家 + primary='yfinance', + specialty='sector_analysis' + ), + '汉钟离': ImmortalConfig( # 离-热点追踪 + primary='yfinance', + specialty='market_movers' + ), + '蓝采和': ImmortalConfig( # 坎-潜力股发现 + primary='yfinance', + specialty='screener' + ), + '曹国舅': ImmortalConfig( # 震-机构分析 + primary='yfinance', + specialty='institutional_holdings' + ), + '铁拐李': ImmortalConfig( # 巽-逆向投资 + primary='yfinance', + specialty='short_interest' + ) + } + + print("✅ OpenBB 引擎初始化完成") + + def _ensure_openbb(self): + """Lazy import OpenBB v4 obb router.""" + if self._obb is not None: + return True + try: + from openbb import obb # type: ignore + self._obb = obb + return True + except Exception: + self._obb = None + return False + + def get_immortal_data(self, immortal_name: str, data_type: str, symbol: str = 'AAPL') -> APIResult: + """ + 为特定八仙获取专属数据 + + Args: + immortal_name: 八仙名称 + data_type: 数据类型 + symbol: 股票代码 + + Returns: + API调用结果 + """ + if immortal_name not in self.immortal_sources: + return APIResult(success=False, error=f'Unknown immortal: {immortal_name}') + + immortal_config = self.immortal_sources[immortal_name] + + print(f"🧙‍♂️ {immortal_name} 请求 {data_type} 数据 (股票: {symbol})") + + # 根据数据类型调用不同的 OpenBB 函数 + try: + if not self._ensure_openbb(): + return APIResult(success=False, error='OpenBB 未安装,请先安装 openbb>=4 并在 requirements.txt 启用') + obb = self._obb + if data_type == 'price': + result = obb.equity.price.quote(symbol=symbol, provider=immortal_config.primary) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + elif data_type == 'historical': + result = obb.equity.price.historical(symbol=symbol, provider=immortal_config.primary) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + elif data_type == 'profile': + result = obb.equity.profile(symbol=symbol, provider=immortal_config.primary) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + elif data_type == 'news': + result = obb.news.company(symbol=symbol) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used='news_api' + ) + elif data_type == 'earnings': + result = obb.equity.earnings.earnings_historical(symbol=symbol, provider=immortal_config.primary) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + elif data_type == 'dividends': + result = obb.equity.fundamental.dividend(symbol=symbol, provider=immortal_config.primary) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + elif data_type == 'screener': + # 使用简单的筛选器作为替代 + result = obb.equity.screener.etf( + provider=immortal_config.primary + ) + return APIResult( + success=True, + data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()), + provider_used=immortal_config.primary + ) + else: + return APIResult(success=False, error=f'Unsupported data type: {data_type}') + + except Exception as e: + return APIResult(success=False, error=f'OpenBB 调用失败: {str(e)}') + + def simulate_jixia_debate(self, topic_symbol: str = 'TSLA') -> Dict[str, APIResult]: + """ + 模拟稷下学宫八仙论道 + + Args: + topic_symbol: 辩论主题股票代码 + + Returns: + 八仙辩论结果 + """ + print(f"🏛️ 稷下学宫八仙论道 - 主题: {topic_symbol} (OpenBB 版本)") + print("=" * 60) + + debate_results: Dict[str, APIResult] = {} + + # 数据类型映射 + data_type_mapping = { + 'technical_analysis': 'historical', # 技术分析使用历史价格数据 + 'risk_metrics': 'price', # 风险控制使用当前价格数据 + 'historical_data': 'historical', # 历史数据分析使用历史价格数据 + 'sector_analysis': 'profile', # 新兴资产分析使用公司概况 + 'market_movers': 'news', # 热点追踪使用新闻 + 'screener': 'screener', # 潜力股发现使用筛选器 + 'institutional_holdings': 'profile', # 机构分析使用公司概况 + 'short_interest': 'profile' # 逆向投资使用公司概况 + } + + # 八仙依次发言 + for immortal_name, config in self.immortal_sources.items(): + print(f"\n🎭 {immortal_name} ({config.specialty}) 发言:") + + data_type = data_type_mapping.get(config.specialty, 'price') + result = self.get_immortal_data(immortal_name, data_type, topic_symbol) + + if result.success: + debate_results[immortal_name] = result + print(f" 💬 观点: 基于{result.provider_used}数据的{config.specialty}分析") + # 显示部分数据示例 + if result.data: + if isinstance(result.data, list) and len(result.data) > 0: + sample = result.data[0] + print(f" 📊 数据示例: {sample}") + elif hasattr(result.data, '__dict__'): + # 如果是对象,显示前几个属性 + attrs = vars(result.data) + sample = {k: v for k, v in list(attrs.items())[:3]} + print(f" 📊 数据示例: {sample}") + else: + print(f" 📊 数据示例: {result.data}") + else: + print(f" 😔 暂时无法获取数据: {result.error}") + + return debate_results + +if __name__ == "__main__": + # 测试 OpenBB 引擎 + print("🧪 OpenBB 引擎测试") + engine = OpenBBEngine() + engine.simulate_jixia_debate('AAPL') \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/openbb_stock_data.py b/jixia_academy/core/debate_system/engines/openbb_stock_data.py new file mode 100644 index 0000000..e0d9202 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/openbb_stock_data.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +""" +OpenBB 股票数据获取模块 +""" + +from datetime import datetime, timedelta +from typing import List, Dict, Any, Optional + +def get_stock_data(symbol: str, days: int = 90) -> Optional[List[Dict[str, Any]]]: + """ + 获取指定股票在指定天数内的历史数据 + + Args: + symbol (str): 股票代码 (如 'AAPL') + days (int): 时间窗口(天),默认90天 + + Returns: + List[Dict[str, Any]]: 股票历史数据列表,如果失败则返回None + """ + try: + # 计算开始日期 + end_date = datetime.now() + start_date = end_date - timedelta(days=days) + + print(f"🔍 正在获取 {symbol} 近 {days} 天的数据...") + print(f" 时间范围: {start_date.strftime('%Y-%m-%d')} 到 {end_date.strftime('%Y-%m-%d')}") + + # 使用OpenBB获取数据(延迟导入) + try: + from openbb import obb # type: ignore + except Exception as e: + print(f"⚠️ OpenBB 未安装或导入失败: {e}") + return None + + result = obb.equity.price.historical( + symbol=symbol, + provider='yfinance', + start_date=start_date.strftime('%Y-%m-%d'), + end_date=end_date.strftime('%Y-%m-%d') + ) + + results = getattr(result, 'results', None) + if results: + print(f"✅ 成功获取 {len(results)} 条记录") + return results + else: + print("❌ 未获取到数据") + return None + + except Exception as e: + print(f"❌ 获取数据时出错: {str(e)}") + return None + +def get_etf_data(symbol: str, days: int = 90) -> Optional[List[Dict[str, Any]]]: + """ + 获取指定ETF在指定天数内的历史数据 + + Args: + symbol (str): ETF代码 (如 'SPY') + days (int): 时间窗口(天),默认90天 + + Returns: + List[Dict[str, Any]]: ETF历史数据列表,如果失败则返回None + """ + try: + # 计算开始日期 + end_date = datetime.now() + start_date = end_date - timedelta(days=days) + + print(f"🔍 正在获取 {symbol} 近 {days} 天的数据...") + print(f" 时间范围: {start_date.strftime('%Y-%m-%d')} 到 {end_date.strftime('%Y-%m-%d')}") + + # 使用OpenBB获取数据(延迟导入) + try: + from openbb import obb # type: ignore + except Exception as e: + print(f"⚠️ OpenBB 未安装或导入失败: {e}") + return None + + result = obb.etf.price.historical( + symbol=symbol, + provider='yfinance', + start_date=start_date.strftime('%Y-%m-%d'), + end_date=end_date.strftime('%Y-%m-%d') + ) + + results = getattr(result, 'results', None) + if results: + print(f"✅ 成功获取 {len(results)} 条记录") + return results + else: + print("❌ 未获取到数据") + return None + + except Exception as e: + print(f"❌ 获取数据时出错: {str(e)}") + return None + +def format_stock_data(data: List[Dict[str, Any]]) -> None: + """ + 格式化并打印股票数据 + + Args: + data (List[Dict[str, Any]]): 股票数据列表 + """ + if not data: + print("😔 没有数据可显示") + return + + print(f"\n📊 股票数据预览 (显示最近5条记录):") + print("-" * 80) + print(f"{'日期':<12} {'开盘':<10} {'最高':<10} {'最低':<10} {'收盘':<10} {'成交量':<15}") + print("-" * 80) + + # 只显示最近5条记录 + for item in data[-5:]: + print(f"{str(item.date):<12} {item.open:<10.2f} {item.high:<10.2f} {item.low:<10.2f} {item.close:<10.2f} {item.volume:<15,}") + +def format_etf_data(data: List[Dict[str, Any]]) -> None: + """ + 格式化并打印ETF数据 + + Args: + data (List[Dict[str, Any]]): ETF数据列表 + """ + if not data: + print("😔 没有数据可显示") + return + + print(f"\n📊 ETF数据预览 (显示最近5条记录):") + print("-" * 80) + print(f"{'日期':<12} {'开盘':<10} {'最高':<10} {'最低':<10} {'收盘':<10} {'成交量':<15}") + print("-" * 80) + + # 只显示最近5条记录 + for item in data[-5:]: + print(f"{str(item.date):<12} {item.open:<10.2f} {item.high:<10.2f} {item.low:<10.2f} {item.close:<10.2f} {item.volume:<15,}") + +def main(): + """主函数""" + # 示例:获取AAPL股票和SPY ETF的数据 + symbols = [("AAPL", "stock"), ("SPY", "etf")] + time_windows = [90, 720] + + for symbol, asset_type in symbols: + for days in time_windows: + print(f"\n{'='*60}") + print(f"获取 {symbol} {days} 天数据") + print(f"{'='*60}") + + if asset_type == "stock": + data = get_stock_data(symbol, days) + if data: + format_stock_data(data) + else: + data = get_etf_data(symbol, days) + if data: + format_etf_data(data) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/perpetual_engine.py b/jixia_academy/core/debate_system/engines/perpetual_engine.py new file mode 100644 index 0000000..cfdef17 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/perpetual_engine.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +""" +稷下学宫永动机引擎 +为八仙论道提供无限数据支撑 + +重构版本: +- 移除硬编码密钥 +- 添加类型注解 +- 改进错误处理 +- 统一配置管理 +""" + +import requests +import time +from datetime import datetime +from typing import Dict, List, Any, Optional +from dataclasses import dataclass + +@dataclass +class ImmortalConfig: + """八仙配置数据类""" + primary: str + backup: List[str] + specialty: str + +@dataclass +class APIResult: + """API调用结果数据类""" + success: bool + data: Optional[Dict[str, Any]] = None + api_used: Optional[str] = None + usage_count: Optional[int] = None + error: Optional[str] = None + +class JixiaPerpetualEngine: + """稷下学宫永动机引擎""" + + def __init__(self, rapidapi_key: str): + """ + 初始化永动机引擎 + + Args: + rapidapi_key: RapidAPI密钥,从环境变量或Doppler获取 + """ + if not rapidapi_key: + raise ValueError("RapidAPI密钥不能为空") + + self.rapidapi_key = rapidapi_key + + # 八仙专属API分配 - 基于4个可用API优化 + self.immortal_apis: Dict[str, ImmortalConfig] = { + '吕洞宾': ImmortalConfig( # 乾-技术分析专家 + primary='alpha_vantage', + backup=['yahoo_finance_1'], + specialty='comprehensive_analysis' + ), + '何仙姑': ImmortalConfig( # 坤-风险控制专家 + primary='yahoo_finance_1', + backup=['webull'], + specialty='risk_management' + ), + '张果老': ImmortalConfig( # 兑-历史数据分析师 + primary='seeking_alpha', + backup=['alpha_vantage'], + specialty='fundamental_analysis' + ), + '韩湘子': ImmortalConfig( # 艮-新兴资产专家 + primary='webull', + backup=['yahoo_finance_1'], + specialty='emerging_trends' + ), + '汉钟离': ImmortalConfig( # 离-热点追踪 + primary='yahoo_finance_1', + backup=['webull'], + specialty='hot_trends' + ), + '蓝采和': ImmortalConfig( # 坎-潜力股发现 + primary='webull', + backup=['alpha_vantage'], + specialty='undervalued_stocks' + ), + '曹国舅': ImmortalConfig( # 震-机构分析 + primary='seeking_alpha', + backup=['alpha_vantage'], + specialty='institutional_analysis' + ), + '铁拐李': ImmortalConfig( # 巽-逆向投资 + primary='alpha_vantage', + backup=['seeking_alpha'], + specialty='contrarian_analysis' + ) + } + + # API池配置 - 只保留4个可用的API + self.api_configs: Dict[str, str] = { + 'alpha_vantage': 'alpha-vantage.p.rapidapi.com', # 1.26s ⚡ + 'webull': 'webull.p.rapidapi.com', # 1.56s ⚡ + 'yahoo_finance_1': 'yahoo-finance15.p.rapidapi.com', # 2.07s + 'seeking_alpha': 'seeking-alpha.p.rapidapi.com' # 3.32s + } + + # 使用统计 + self.usage_tracker: Dict[str, int] = {api: 0 for api in self.api_configs.keys()} + + def get_immortal_data(self, immortal_name: str, data_type: str, symbol: str = 'AAPL') -> APIResult: + """ + 为特定八仙获取专属数据 + + Args: + immortal_name: 八仙名称 + data_type: 数据类型 + symbol: 股票代码 + + Returns: + API调用结果 + """ + if immortal_name not in self.immortal_apis: + return APIResult(success=False, error=f'Unknown immortal: {immortal_name}') + + immortal_config = self.immortal_apis[immortal_name] + + print(f"🧙‍♂️ {immortal_name} 请求 {data_type} 数据 (股票: {symbol})") + + # 尝试主要API + result = self._call_api(immortal_config.primary, data_type, symbol) + if result.success: + print(f" ✅ 使用主要API: {immortal_config.primary}") + return result + + # 故障转移到备用API + for backup_api in immortal_config.backup: + print(f" 🔄 故障转移到: {backup_api}") + result = self._call_api(backup_api, data_type, symbol) + if result.success: + print(f" ✅ 备用API成功: {backup_api}") + return result + + print(f" ❌ 所有API都失败了") + return APIResult(success=False, error='All APIs failed') + + def _call_api(self, api_name: str, data_type: str, symbol: str) -> APIResult: + """ + 调用指定API + + Args: + api_name: API名称 + data_type: 数据类型 + symbol: 股票代码 + + Returns: + API调用结果 + """ + if api_name not in self.api_configs: + return APIResult(success=False, error=f'API {api_name} not configured') + + host = self.api_configs[api_name] + headers = { + 'X-RapidAPI-Key': self.rapidapi_key, + 'X-RapidAPI-Host': host, + 'Content-Type': 'application/json' + } + + endpoint = self._get_endpoint(api_name, data_type, symbol) + if not endpoint: + return APIResult(success=False, error=f'No endpoint for {data_type} on {api_name}') + + url = f"https://{host}{endpoint}" + + try: + response = requests.get(url, headers=headers, timeout=8) + self.usage_tracker[api_name] += 1 + + if response.status_code == 200: + return APIResult( + success=True, + data=response.json(), + api_used=api_name, + usage_count=self.usage_tracker[api_name] + ) + else: + return APIResult( + success=False, + error=f'HTTP {response.status_code}: {response.text[:100]}' + ) + except requests.exceptions.Timeout: + return APIResult(success=False, error='Request timeout') + except requests.exceptions.RequestException as e: + return APIResult(success=False, error=f'Request error: {str(e)}') + except Exception as e: + return APIResult(success=False, error=f'Unexpected error: {str(e)}') + + def _get_endpoint(self, api_name: str, data_type: str, symbol: str) -> Optional[str]: + """ + 根据API和数据类型返回合适的端点 + + Args: + api_name: API名称 + data_type: 数据类型 + symbol: 股票代码 + + Returns: + API端点路径 + """ + endpoint_mapping = { + 'alpha_vantage': { + 'quote': f'/query?function=GLOBAL_QUOTE&symbol={symbol}', + 'overview': f'/query?function=OVERVIEW&symbol={symbol}', + 'earnings': f'/query?function=EARNINGS&symbol={symbol}', + 'profile': f'/query?function=OVERVIEW&symbol={symbol}', + 'analysis': f'/query?function=OVERVIEW&symbol={symbol}' + }, + 'yahoo_finance_1': { + 'quote': f'/api/yahoo/qu/quote/{symbol}', + 'gainers': '/api/yahoo/co/collections/day_gainers', + 'losers': '/api/yahoo/co/collections/day_losers', + 'search': f'/api/yahoo/qu/quote/{symbol}', + 'analysis': f'/api/yahoo/qu/quote/{symbol}', + 'profile': f'/api/yahoo/qu/quote/{symbol}' + }, + 'seeking_alpha': { + 'profile': f'/symbols/get-profile?symbols={symbol}', + 'news': '/news/list?category=market-news', + 'analysis': f'/symbols/get-profile?symbols={symbol}', + 'quote': f'/symbols/get-profile?symbols={symbol}' + }, + 'webull': { + 'search': f'/stock/search?keyword={symbol}', + 'quote': f'/stock/search?keyword={symbol}', + 'analysis': f'/stock/search?keyword={symbol}', + 'gainers': '/market/get-active-gainers', + 'profile': f'/stock/search?keyword={symbol}' + } + } + + api_endpoints = endpoint_mapping.get(api_name, {}) + return api_endpoints.get(data_type, api_endpoints.get('quote')) + + def simulate_jixia_debate(self, topic_symbol: str = 'TSLA') -> Dict[str, APIResult]: + """ + 模拟稷下学宫八仙论道 + + Args: + topic_symbol: 辩论主题股票代码 + + Returns: + 八仙辩论结果 + """ + print(f"🏛️ 稷下学宫八仙论道 - 主题: {topic_symbol}") + print("=" * 60) + + debate_results: Dict[str, APIResult] = {} + + # 数据类型映射 + data_type_mapping = { + 'comprehensive_analysis': 'overview', + 'etf_tracking': 'quote', + 'fundamental_analysis': 'profile', + 'emerging_trends': 'news', + 'hot_trends': 'gainers', + 'undervalued_stocks': 'search', + 'institutional_analysis': 'profile', + 'contrarian_analysis': 'analysis' + } + + # 八仙依次发言 + for immortal_name, config in self.immortal_apis.items(): + print(f"\n🎭 {immortal_name} ({config.specialty}) 发言:") + + data_type = data_type_mapping.get(config.specialty, 'quote') + result = self.get_immortal_data(immortal_name, data_type, topic_symbol) + + if result.success: + debate_results[immortal_name] = result + print(f" 💬 观点: 基于{result.api_used}数据的{config.specialty}分析") + else: + print(f" 😔 暂时无法获取数据: {result.error}") + + time.sleep(0.5) # 避免过快请求 + + return debate_results + + def get_usage_stats(self) -> Dict[str, Any]: + """ + 获取使用统计信息 + + Returns: + 统计信息字典 + """ + total_calls = sum(self.usage_tracker.values()) + active_apis = len([api for api, count in self.usage_tracker.items() if count > 0]) + unused_apis = [api for api, count in self.usage_tracker.items() if count == 0] + + return { + 'total_calls': total_calls, + 'active_apis': active_apis, + 'total_apis': len(self.api_configs), + 'average_calls_per_api': total_calls / len(self.api_configs) if self.api_configs else 0, + 'usage_by_api': {api: count for api, count in self.usage_tracker.items() if count > 0}, + 'unused_apis': unused_apis, + 'unused_count': len(unused_apis) + } + + def print_perpetual_stats(self) -> None: + """打印永动机统计信息""" + stats = self.get_usage_stats() + + print(f"\n📊 永动机运行统计:") + print("=" * 60) + print(f"总API调用次数: {stats['total_calls']}") + print(f"活跃API数量: {stats['active_apis']}/{stats['total_apis']}") + print(f"平均每API调用: {stats['average_calls_per_api']:.1f}次") + + if stats['usage_by_api']: + print(f"\n各API使用情况:") + for api, count in stats['usage_by_api'].items(): + print(f" {api}: {count}次") + + print(f"\n🎯 未使用的API储备: {stats['unused_count']}个") + if stats['unused_apis']: + unused_display = ', '.join(stats['unused_apis'][:5]) + if len(stats['unused_apis']) > 5: + unused_display += '...' + print(f"储备API: {unused_display}") + + print(f"\n💡 永动机效果:") + print(f" • {stats['total_apis']}个API订阅,智能调度") + print(f" • 智能故障转移,永不断线") + print(f" • 八仙专属API,个性化数据") + print(f" • 成本优化,效果最大化!") \ No newline at end of file diff --git a/jixia_academy/core/debate_system/engines/rapidapi_adapter.py b/jixia_academy/core/debate_system/engines/rapidapi_adapter.py new file mode 100644 index 0000000..02458a2 --- /dev/null +++ b/jixia_academy/core/debate_system/engines/rapidapi_adapter.py @@ -0,0 +1,48 @@ +from typing import List, Optional +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine +from config.settings import get_rapidapi_key + +class RapidAPIDataProvider(DataProvider): + """RapidAPI永动机引擎适配器""" + + def __init__(self): + self.engine = JixiaPerpetualEngine(get_rapidapi_key()) + self._name = "RapidAPI" + self._priority = 2 # 中等优先级 + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + result = self.engine.get_immortal_data("吕洞宾", "quote", symbol) + if result.success and result.data: + # 解析RapidAPI返回的数据并转换为StockQuote + # 这里需要根据实际API返回的数据结构进行调整 + return StockQuote( + symbol=symbol, + price=result.data.get("price", 0), + change=result.data.get("change", 0), + change_percent=result.data.get("change_percent", 0), + volume=result.data.get("volume", 0), + timestamp=result.data.get("timestamp") + ) + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + # 实现历史价格数据获取逻辑 + pass + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + # 实现公司概况获取逻辑 + pass + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + # 实现新闻获取逻辑 + pass + + @property + def name(self) -> str: + return self._name + + @property + def priority(self) -> int: + return self._priority \ No newline at end of file diff --git a/jixia_academy/core/debate_system/intervention/human_intervention_system.py b/jixia_academy/core/debate_system/intervention/human_intervention_system.py new file mode 100644 index 0000000..c6b6083 --- /dev/null +++ b/jixia_academy/core/debate_system/intervention/human_intervention_system.py @@ -0,0 +1,929 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Human干预系统 +监控辩论健康度并在必要时触发人工干预 +""" + +import asyncio +import json +import logging +from typing import Dict, List, Any, Optional, Callable, Tuple +from dataclasses import dataclass, field +from enum import Enum +from datetime import datetime, timedelta +import statistics +import re + +class HealthStatus(Enum): + """健康状态""" + EXCELLENT = "优秀" # 90-100分 + GOOD = "良好" # 70-89分 + FAIR = "一般" # 50-69分 + POOR = "较差" # 30-49分 + CRITICAL = "危险" # 0-29分 + +class InterventionLevel(Enum): + """干预级别""" + NONE = (0, "无需干预") + GENTLE_REMINDER = (1, "温和提醒") + MODERATE_GUIDANCE = (2, "适度引导") + STRONG_INTERVENTION = (3, "强力干预") + EMERGENCY_STOP = (4, "紧急停止") + + def __init__(self, level, description): + self.level = level + self.description = description + + @property + def value(self): + return self.description + + def __ge__(self, other): + if isinstance(other, InterventionLevel): + return self.level >= other.level + return NotImplemented + + def __gt__(self, other): + if isinstance(other, InterventionLevel): + return self.level > other.level + return NotImplemented + + def __le__(self, other): + if isinstance(other, InterventionLevel): + return self.level <= other.level + return NotImplemented + + def __lt__(self, other): + if isinstance(other, InterventionLevel): + return self.level < other.level + return NotImplemented + +class AlertType(Enum): + """警报类型""" + QUALITY_DECLINE = "质量下降" + TOXIC_BEHAVIOR = "有害行为" + REPETITIVE_CONTENT = "重复内容" + OFF_TOPIC = "偏离主题" + EMOTIONAL_ESCALATION = "情绪升级" + PARTICIPATION_IMBALANCE = "参与不平衡" + TECHNICAL_ERROR = "技术错误" + TIME_VIOLATION = "时间违规" + +@dataclass +class HealthMetric: + """健康指标""" + name: str + value: float + weight: float + threshold_critical: float + threshold_poor: float + threshold_fair: float + threshold_good: float + description: str + last_updated: datetime = field(default_factory=datetime.now) + +@dataclass +class InterventionAlert: + """干预警报""" + id: str + alert_type: AlertType + severity: InterventionLevel + message: str + affected_participants: List[str] + metrics: Dict[str, float] + timestamp: datetime + resolved: bool = False + resolution_notes: str = "" + human_notified: bool = False + +@dataclass +class InterventionAction: + """干预动作""" + id: str + action_type: str + description: str + target_participants: List[str] + parameters: Dict[str, Any] + executed_at: datetime + success: bool = False + result_message: str = "" + +class DebateHealthMonitor: + """辩论健康度监控器""" + + def __init__(self): + self.health_metrics: Dict[str, HealthMetric] = {} + self.active_alerts: List[InterventionAlert] = [] + self.intervention_history: List[InterventionAction] = [] + self.monitoring_enabled = True + self.logger = logging.getLogger(__name__) + + # 初始化健康指标 + self._initialize_health_metrics() + + # 事件处理器 + self.event_handlers: Dict[str, List[Callable]] = {} + + # 监控配置 + self.monitoring_config = { + "check_interval_seconds": 30, + "alert_cooldown_minutes": 5, + "auto_intervention_enabled": True, + "human_notification_threshold": InterventionLevel.STRONG_INTERVENTION + } + + def _initialize_health_metrics(self): + """初始化健康指标""" + metrics_config = [ + { + "name": "content_quality", + "weight": 0.25, + "thresholds": {"critical": 20, "poor": 40, "fair": 60, "good": 80}, + "description": "内容质量评分" + }, + { + "name": "participation_balance", + "weight": 0.20, + "thresholds": {"critical": 30, "poor": 50, "fair": 70, "good": 85}, + "description": "参与平衡度" + }, + { + "name": "emotional_stability", + "weight": 0.20, + "thresholds": {"critical": 25, "poor": 45, "fair": 65, "good": 80}, + "description": "情绪稳定性" + }, + { + "name": "topic_relevance", + "weight": 0.15, + "thresholds": {"critical": 35, "poor": 55, "fair": 70, "good": 85}, + "description": "主题相关性" + }, + { + "name": "interaction_civility", + "weight": 0.10, + "thresholds": {"critical": 20, "poor": 40, "fair": 60, "good": 80}, + "description": "互动文明度" + }, + { + "name": "technical_stability", + "weight": 0.10, + "thresholds": {"critical": 40, "poor": 60, "fair": 75, "good": 90}, + "description": "技术稳定性" + } + ] + + for config in metrics_config: + metric = HealthMetric( + name=config["name"], + value=100.0, # 初始值 + weight=config["weight"], + threshold_critical=config["thresholds"]["critical"], + threshold_poor=config["thresholds"]["poor"], + threshold_fair=config["thresholds"]["fair"], + threshold_good=config["thresholds"]["good"], + description=config["description"] + ) + self.health_metrics[config["name"]] = metric + + async def analyze_debate_health(self, debate_data: Dict[str, Any]) -> Tuple[float, HealthStatus]: + """分析辩论健康度""" + if not self.monitoring_enabled: + return 100.0, HealthStatus.EXCELLENT + + # 更新各项健康指标 + await self._update_content_quality(debate_data) + await self._update_participation_balance(debate_data) + await self._update_emotional_stability(debate_data) + await self._update_topic_relevance(debate_data) + await self._update_interaction_civility(debate_data) + await self._update_technical_stability(debate_data) + + # 计算综合健康分数 + total_score = 0.0 + total_weight = 0.0 + + for metric in self.health_metrics.values(): + total_score += metric.value * metric.weight + total_weight += metric.weight + + overall_score = total_score / total_weight if total_weight > 0 else 0.0 + + # 确定健康状态 + if overall_score >= 90: + status = HealthStatus.EXCELLENT + elif overall_score >= 70: + status = HealthStatus.GOOD + elif overall_score >= 50: + status = HealthStatus.FAIR + elif overall_score >= 30: + status = HealthStatus.POOR + else: + status = HealthStatus.CRITICAL + + # 检查是否需要发出警报 + await self._check_for_alerts(overall_score, status) + + self.logger.info(f"辩论健康度分析完成: {overall_score:.1f}分 ({status.value})") + + return overall_score, status + + async def _update_content_quality(self, debate_data: Dict[str, Any]): + """更新内容质量指标""" + messages = debate_data.get("recent_messages", []) + if not messages: + return + + quality_scores = [] + + for message in messages[-10:]: # 分析最近10条消息 + content = message.get("content", "") + + # 内容长度评分 + length_score = min(len(content) / 100 * 50, 50) # 最多50分 + + # 词汇丰富度评分 + words = content.split() + unique_words = len(set(words)) + vocabulary_score = min(unique_words / len(words) * 30, 30) if words else 0 + + # 逻辑结构评分(简单检测) + logic_indicators = ["因为", "所以", "但是", "然而", "首先", "其次", "最后", "总之"] + logic_score = min(sum(1 for indicator in logic_indicators if indicator in content) * 5, 20) + + total_score = length_score + vocabulary_score + logic_score + quality_scores.append(total_score) + + avg_quality = statistics.mean(quality_scores) if quality_scores else 50 + self.health_metrics["content_quality"].value = avg_quality + self.health_metrics["content_quality"].last_updated = datetime.now() + + async def _update_participation_balance(self, debate_data: Dict[str, Any]): + """更新参与平衡度指标""" + messages = debate_data.get("recent_messages", []) + if not messages: + return + + # 统计各参与者的发言次数 + speaker_counts = {} + for message in messages[-20:]: # 分析最近20条消息 + speaker = message.get("sender", "") + speaker_counts[speaker] = speaker_counts.get(speaker, 0) + 1 + + if not speaker_counts: + return + + # 计算参与平衡度 + counts = list(speaker_counts.values()) + if len(counts) <= 1: + balance_score = 100 + else: + # 使用标准差来衡量平衡度 + mean_count = statistics.mean(counts) + std_dev = statistics.stdev(counts) + + # 标准差越小,平衡度越高 + balance_score = max(0, 100 - (std_dev / mean_count * 100)) + + self.health_metrics["participation_balance"].value = balance_score + self.health_metrics["participation_balance"].last_updated = datetime.now() + + async def _update_emotional_stability(self, debate_data: Dict[str, Any]): + """更新情绪稳定性指标""" + messages = debate_data.get("recent_messages", []) + if not messages: + return + + emotional_scores = [] + + # 情绪关键词 + negative_emotions = ["愤怒", "生气", "讨厌", "恶心", "愚蠢", "白痴", "垃圾"] + positive_emotions = ["赞同", "支持", "优秀", "精彩", "同意", "认可"] + + for message in messages[-15:]: + content = message.get("content", "") + + # 检测负面情绪 + negative_count = sum(1 for word in negative_emotions if word in content) + positive_count = sum(1 for word in positive_emotions if word in content) + + # 检测大写字母比例(可能表示情绪激动) + if content: + caps_ratio = sum(1 for c in content if c.isupper()) / len(content) + else: + caps_ratio = 0 + + # 检测感叹号数量 + exclamation_count = content.count("!") + + # 计算情绪稳定性分数 + emotion_score = 100 + emotion_score -= negative_count * 15 # 负面情绪扣分 + emotion_score += positive_count * 5 # 正面情绪加分 + emotion_score -= caps_ratio * 30 # 大写字母扣分 + emotion_score -= min(exclamation_count * 5, 20) # 感叹号扣分 + + emotional_scores.append(max(0, emotion_score)) + + avg_emotional_stability = statistics.mean(emotional_scores) if emotional_scores else 80 + self.health_metrics["emotional_stability"].value = avg_emotional_stability + self.health_metrics["emotional_stability"].last_updated = datetime.now() + + async def _update_topic_relevance(self, debate_data: Dict[str, Any]): + """更新主题相关性指标""" + messages = debate_data.get("recent_messages", []) + topic_keywords = debate_data.get("topic_keywords", []) + + if not messages or not topic_keywords: + return + + relevance_scores = [] + + for message in messages[-10:]: + content = message.get("content", "") + + # 计算主题关键词匹配度 + keyword_matches = sum(1 for keyword in topic_keywords if keyword in content) + relevance_score = min(keyword_matches / len(topic_keywords) * 100, 100) if topic_keywords else 50 + + relevance_scores.append(relevance_score) + + avg_relevance = statistics.mean(relevance_scores) if relevance_scores else 70 + self.health_metrics["topic_relevance"].value = avg_relevance + self.health_metrics["topic_relevance"].last_updated = datetime.now() + + async def _update_interaction_civility(self, debate_data: Dict[str, Any]): + """更新互动文明度指标""" + messages = debate_data.get("recent_messages", []) + if not messages: + return + + civility_scores = [] + + # 不文明行为关键词 + uncivil_patterns = [ + r"你.*蠢", r".*白痴.*", r".*垃圾.*", r"闭嘴", r"滚.*", + r".*傻.*", r".*笨.*", r".*废物.*" + ] + + # 文明行为关键词 + civil_patterns = [ + r"请.*", r"谢谢", r"不好意思", r"抱歉", r"尊重", r"理解" + ] + + for message in messages[-15:]: + content = message.get("content", "") + + civility_score = 100 + + # 检测不文明行为 + for pattern in uncivil_patterns: + if re.search(pattern, content): + civility_score -= 20 + + # 检测文明行为 + for pattern in civil_patterns: + if re.search(pattern, content): + civility_score += 5 + + civility_scores.append(max(0, min(100, civility_score))) + + avg_civility = statistics.mean(civility_scores) if civility_scores else 85 + self.health_metrics["interaction_civility"].value = avg_civility + self.health_metrics["interaction_civility"].last_updated = datetime.now() + + async def _update_technical_stability(self, debate_data: Dict[str, Any]): + """更新技术稳定性指标""" + system_status = debate_data.get("system_status", {}) + + stability_score = 100 + + # 检查错误率 + error_rate = system_status.get("error_rate", 0) + stability_score -= error_rate * 100 + + # 检查响应时间 + response_time = system_status.get("avg_response_time", 0) + if response_time > 2.0: # 超过2秒 + stability_score -= (response_time - 2.0) * 10 + + # 检查系统负载 + system_load = system_status.get("system_load", 0) + if system_load > 0.8: # 负载超过80% + stability_score -= (system_load - 0.8) * 50 + + self.health_metrics["technical_stability"].value = max(0, stability_score) + self.health_metrics["technical_stability"].last_updated = datetime.now() + + async def _check_for_alerts(self, overall_score: float, status: HealthStatus): + """检查是否需要发出警报""" + current_time = datetime.now() + + # 检查各项指标是否触发警报 + for metric_name, metric in self.health_metrics.items(): + alert_level = self._determine_alert_level(metric) + + if alert_level != InterventionLevel.NONE: + # 检查是否在冷却期内 + recent_alerts = [ + alert for alert in self.active_alerts + if alert.alert_type.value == metric_name and + (current_time - alert.timestamp).total_seconds() < + self.monitoring_config["alert_cooldown_minutes"] * 60 + ] + + if not recent_alerts: + await self._create_alert(metric_name, metric, alert_level) + + # 检查整体健康状态 + if status in [HealthStatus.POOR, HealthStatus.CRITICAL]: + await self._create_system_alert(overall_score, status) + + def _determine_alert_level(self, metric: HealthMetric) -> InterventionLevel: + """确定警报级别""" + if metric.value <= metric.threshold_critical: + return InterventionLevel.EMERGENCY_STOP + elif metric.value <= metric.threshold_poor: + return InterventionLevel.STRONG_INTERVENTION + elif metric.value <= metric.threshold_fair: + return InterventionLevel.MODERATE_GUIDANCE + elif metric.value <= metric.threshold_good: + return InterventionLevel.GENTLE_REMINDER + else: + return InterventionLevel.NONE + + async def _create_alert(self, metric_name: str, metric: HealthMetric, level: InterventionLevel): + """创建警报""" + alert_type_map = { + "content_quality": AlertType.QUALITY_DECLINE, + "participation_balance": AlertType.PARTICIPATION_IMBALANCE, + "emotional_stability": AlertType.EMOTIONAL_ESCALATION, + "topic_relevance": AlertType.OFF_TOPIC, + "interaction_civility": AlertType.TOXIC_BEHAVIOR, + "technical_stability": AlertType.TECHNICAL_ERROR + } + + alert = InterventionAlert( + id=f"alert_{datetime.now().timestamp()}", + alert_type=alert_type_map.get(metric_name, AlertType.QUALITY_DECLINE), + severity=level, + message=f"{metric.description}指标异常: {metric.value:.1f}分", + affected_participants=[], + metrics={metric_name: metric.value}, + timestamp=datetime.now() + ) + + self.active_alerts.append(alert) + + # 触发事件处理 + await self._trigger_event_handlers("alert_created", alert) + + # 检查是否需要自动干预 + if self.monitoring_config["auto_intervention_enabled"]: + await self._execute_auto_intervention(alert) + + # 检查是否需要通知Human + if level >= self.monitoring_config["human_notification_threshold"]: + await self._notify_human(alert) + + self.logger.warning(f"创建警报: {alert.alert_type.value} - {alert.message}") + + async def _create_system_alert(self, score: float, status: HealthStatus): + """创建系统级警报""" + level = InterventionLevel.STRONG_INTERVENTION if status == HealthStatus.POOR else InterventionLevel.EMERGENCY_STOP + + alert = InterventionAlert( + id=f"system_alert_{datetime.now().timestamp()}", + alert_type=AlertType.QUALITY_DECLINE, + severity=level, + message=f"系统整体健康度异常: {score:.1f}分 ({status.value})", + affected_participants=[], + metrics={"overall_score": score}, + timestamp=datetime.now() + ) + + self.active_alerts.append(alert) + await self._trigger_event_handlers("system_alert_created", alert) + + if self.monitoring_config["auto_intervention_enabled"]: + await self._execute_auto_intervention(alert) + + await self._notify_human(alert) + + self.logger.critical(f"系统级警报: {alert.message}") + + async def _execute_auto_intervention(self, alert: InterventionAlert): + """执行自动干预""" + intervention_strategies = { + AlertType.QUALITY_DECLINE: self._intervene_quality_decline, + AlertType.TOXIC_BEHAVIOR: self._intervene_toxic_behavior, + AlertType.EMOTIONAL_ESCALATION: self._intervene_emotional_escalation, + AlertType.PARTICIPATION_IMBALANCE: self._intervene_participation_imbalance, + AlertType.OFF_TOPIC: self._intervene_off_topic, + AlertType.TECHNICAL_ERROR: self._intervene_technical_error + } + + strategy = intervention_strategies.get(alert.alert_type) + if strategy: + action = await strategy(alert) + if action: + self.intervention_history.append(action) + await self._trigger_event_handlers("intervention_executed", action) + + async def _intervene_quality_decline(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预质量下降""" + action = InterventionAction( + id=f"quality_intervention_{datetime.now().timestamp()}", + action_type="quality_guidance", + description="发送质量提升指导", + target_participants=["all"], + parameters={ + "message": "💡 建议:请提供更详细的论证和具体的例证来支持您的观点。", + "guidance_type": "quality_improvement" + }, + executed_at=datetime.now(), + success=True, + result_message="质量提升指导已发送" + ) + + self.logger.info(f"执行质量干预: {action.description}") + return action + + async def _intervene_toxic_behavior(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预有害行为""" + action = InterventionAction( + id=f"toxicity_intervention_{datetime.now().timestamp()}", + action_type="behavior_warning", + description="发送行为规范提醒", + target_participants=["all"], + parameters={ + "message": "⚠️ 请保持文明讨论,避免使用攻击性语言。让我们专注于观点的交流。", + "warning_level": "moderate" + }, + executed_at=datetime.now(), + success=True, + result_message="行为规范提醒已发送" + ) + + self.logger.warning(f"执行行为干预: {action.description}") + return action + + async def _intervene_emotional_escalation(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预情绪升级""" + action = InterventionAction( + id=f"emotion_intervention_{datetime.now().timestamp()}", + action_type="emotion_cooling", + description="发送情绪缓解建议", + target_participants=["all"], + parameters={ + "message": "🧘 让我们暂停一下,深呼吸。理性的讨论更有助于达成共识。", + "cooling_period": 60 # 秒 + }, + executed_at=datetime.now(), + success=True, + result_message="情绪缓解建议已发送" + ) + + self.logger.info(f"执行情绪干预: {action.description}") + return action + + async def _intervene_participation_imbalance(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预参与不平衡""" + action = InterventionAction( + id=f"balance_intervention_{datetime.now().timestamp()}", + action_type="participation_encouragement", + description="鼓励平衡参与", + target_participants=["all"], + parameters={ + "message": "🤝 鼓励所有参与者分享观点,让讨论更加丰富多元。", + "encouragement_type": "participation_balance" + }, + executed_at=datetime.now(), + success=True, + result_message="参与鼓励消息已发送" + ) + + self.logger.info(f"执行参与平衡干预: {action.description}") + return action + + async def _intervene_off_topic(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预偏离主题""" + action = InterventionAction( + id=f"topic_intervention_{datetime.now().timestamp()}", + action_type="topic_redirect", + description="引导回归主题", + target_participants=["all"], + parameters={ + "message": "🎯 让我们回到主要讨论话题,保持讨论的焦点和深度。", + "redirect_type": "topic_focus" + }, + executed_at=datetime.now(), + success=True, + result_message="主题引导消息已发送" + ) + + self.logger.info(f"执行主题干预: {action.description}") + return action + + async def _intervene_technical_error(self, alert: InterventionAlert) -> Optional[InterventionAction]: + """干预技术错误""" + action = InterventionAction( + id=f"tech_intervention_{datetime.now().timestamp()}", + action_type="technical_support", + description="提供技术支持", + target_participants=["system"], + parameters={ + "message": "🔧 检测到技术问题,正在进行系统优化...", + "support_type": "system_optimization" + }, + executed_at=datetime.now(), + success=True, + result_message="技术支持已启动" + ) + + self.logger.error(f"执行技术干预: {action.description}") + return action + + async def _notify_human(self, alert: InterventionAlert): + """通知Human""" + if alert.human_notified: + return + + notification = { + "type": "human_intervention_required", + "alert_id": alert.id, + "severity": alert.severity.value, + "message": alert.message, + "timestamp": alert.timestamp.isoformat(), + "metrics": alert.metrics, + "recommended_actions": self._get_recommended_actions(alert) + } + + # 触发Human通知事件 + await self._trigger_event_handlers("human_notification", notification) + + alert.human_notified = True + + self.logger.critical(f"Human通知已发送: {alert.message}") + + def _get_recommended_actions(self, alert: InterventionAlert) -> List[str]: + """获取推荐的干预动作""" + recommendations = { + AlertType.QUALITY_DECLINE: [ + "提供写作指导", + "分享优秀案例", + "调整讨论节奏" + ], + AlertType.TOXIC_BEHAVIOR: [ + "发出警告", + "暂时禁言", + "私下沟通" + ], + AlertType.EMOTIONAL_ESCALATION: [ + "暂停讨论", + "引导冷静", + "转移话题" + ], + AlertType.PARTICIPATION_IMBALANCE: [ + "邀请发言", + "限制发言频率", + "分组讨论" + ], + AlertType.OFF_TOPIC: [ + "重申主题", + "引导回归", + "设置议程" + ], + AlertType.TECHNICAL_ERROR: [ + "重启系统", + "检查日志", + "联系技术支持" + ] + } + + return recommendations.get(alert.alert_type, ["人工评估", "采取适当措施"]) + + async def _trigger_event_handlers(self, event_type: str, data: Any): + """触发事件处理器""" + if event_type in self.event_handlers: + for handler in self.event_handlers[event_type]: + try: + await handler(data) + except Exception as e: + self.logger.error(f"事件处理器错误: {e}") + + def add_event_handler(self, event_type: str, handler: Callable): + """添加事件处理器""" + if event_type not in self.event_handlers: + self.event_handlers[event_type] = [] + self.event_handlers[event_type].append(handler) + + def update_metrics(self, metrics_data: Dict[str, float]): + """更新健康指标(兼容性方法)""" + for metric_name, value in metrics_data.items(): + if metric_name in self.health_metrics: + self.health_metrics[metric_name].value = value + self.health_metrics[metric_name].last_updated = datetime.now() + + def get_health_status(self) -> HealthStatus: + """获取当前健康状态(兼容性方法)""" + # 计算整体分数 + total_score = 0.0 + total_weight = 0.0 + + for metric in self.health_metrics.values(): + total_score += metric.value * metric.weight + total_weight += metric.weight + + overall_score = total_score / total_weight if total_weight > 0 else 0.0 + + # 确定状态 + if overall_score >= 90: + return HealthStatus.EXCELLENT + elif overall_score >= 70: + return HealthStatus.GOOD + elif overall_score >= 50: + return HealthStatus.FAIR + elif overall_score >= 30: + return HealthStatus.POOR + else: + return HealthStatus.CRITICAL + + def get_health_report(self) -> Dict[str, Any]: + """获取健康报告""" + # 计算整体分数 + total_score = 0.0 + total_weight = 0.0 + + for metric in self.health_metrics.values(): + total_score += metric.value * metric.weight + total_weight += metric.weight + + overall_score = total_score / total_weight if total_weight > 0 else 0.0 + + # 确定状态 + if overall_score >= 90: + status = HealthStatus.EXCELLENT + elif overall_score >= 70: + status = HealthStatus.GOOD + elif overall_score >= 50: + status = HealthStatus.FAIR + elif overall_score >= 30: + status = HealthStatus.POOR + else: + status = HealthStatus.CRITICAL + + report = { + "overall_score": round(overall_score, 1), + "health_status": status.value, + "metrics": { + name: { + "value": round(metric.value, 1), + "weight": metric.weight, + "description": metric.description, + "last_updated": metric.last_updated.isoformat() + } + for name, metric in self.health_metrics.items() + }, + "active_alerts": len(self.active_alerts), + "recent_interventions": len([a for a in self.intervention_history + if (datetime.now() - a.executed_at).total_seconds() < 3600]), + "monitoring_enabled": self.monitoring_enabled, + "last_check": datetime.now().isoformat() + } + + return report + + def resolve_alert(self, alert_id: str, resolution_notes: str = ""): + """解决警报""" + for alert in self.active_alerts: + if alert.id == alert_id: + alert.resolved = True + alert.resolution_notes = resolution_notes + self.logger.info(f"警报已解决: {alert_id} - {resolution_notes}") + return True + return False + + def clear_resolved_alerts(self): + """清理已解决的警报""" + before_count = len(self.active_alerts) + self.active_alerts = [alert for alert in self.active_alerts if not alert.resolved] + after_count = len(self.active_alerts) + + cleared_count = before_count - after_count + if cleared_count > 0: + self.logger.info(f"清理了 {cleared_count} 个已解决的警报") + + def enable_monitoring(self): + """启用监控""" + self.monitoring_enabled = True + self.logger.info("健康监控已启用") + + def disable_monitoring(self): + """禁用监控""" + self.monitoring_enabled = False + self.logger.info("健康监控已禁用") + + def save_monitoring_data(self, filename: str = "monitoring_data.json"): + """保存监控数据""" + # 序列化监控配置,处理InterventionLevel枚举 + serialized_config = self.monitoring_config.copy() + serialized_config["human_notification_threshold"] = self.monitoring_config["human_notification_threshold"].value + + data = { + "health_metrics": { + name: { + "name": metric.name, + "value": metric.value, + "weight": metric.weight, + "threshold_critical": metric.threshold_critical, + "threshold_poor": metric.threshold_poor, + "threshold_fair": metric.threshold_fair, + "threshold_good": metric.threshold_good, + "description": metric.description, + "last_updated": metric.last_updated.isoformat() + } + for name, metric in self.health_metrics.items() + }, + "active_alerts": [ + { + "id": alert.id, + "alert_type": alert.alert_type.value, + "severity": alert.severity.value, + "message": alert.message, + "affected_participants": alert.affected_participants, + "metrics": alert.metrics, + "timestamp": alert.timestamp.isoformat(), + "resolved": alert.resolved, + "resolution_notes": alert.resolution_notes, + "human_notified": alert.human_notified + } + for alert in self.active_alerts + ], + "intervention_history": [ + { + "id": action.id, + "action_type": action.action_type, + "description": action.description, + "target_participants": action.target_participants, + "parameters": action.parameters, + "executed_at": action.executed_at.isoformat(), + "success": action.success, + "result_message": action.result_message + } + for action in self.intervention_history + ], + "monitoring_config": serialized_config, + "monitoring_enabled": self.monitoring_enabled, + "export_time": datetime.now().isoformat() + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + self.logger.info(f"监控数据已保存到 {filename}") + +# 使用示例 +async def main(): + """使用示例""" + monitor = DebateHealthMonitor() + + # 模拟辩论数据 + debate_data = { + "recent_messages": [ + {"sender": "正1", "content": "AI投资确实具有巨大潜力,我们可以从以下几个方面来分析..."}, + {"sender": "反1", "content": "但是风险也不容忽视!!!这些投资可能导致泡沫!"}, + {"sender": "正2", "content": "好的"}, + {"sender": "反2", "content": "你们这些观点太愚蠢了,完全没有逻辑!"}, + ], + "topic_keywords": ["AI", "投资", "风险", "收益", "技术"], + "system_status": { + "error_rate": 0.02, + "avg_response_time": 1.5, + "system_load": 0.6 + } + } + + # 分析健康度 + score, status = await monitor.analyze_debate_health(debate_data) + + print(f"\n📊 辩论健康度分析结果:") + print(f"综合得分: {score:.1f}分") + print(f"健康状态: {status.value}") + + # 获取详细报告 + report = monitor.get_health_report() + print(f"\n📋 详细健康报告:") + print(f"活跃警报数: {report['active_alerts']}") + print(f"近期干预数: {report['recent_interventions']}") + + print(f"\n📈 各项指标:") + for name, metric in report['metrics'].items(): + print(f" {metric['description']}: {metric['value']}分 (权重: {metric['weight']})") + + # 保存数据 + monitor.save_monitoring_data() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/jixia_ollama_swarm.py b/jixia_academy/core/debate_system/jixia_ollama_swarm.py new file mode 100644 index 0000000..f7ddb7b --- /dev/null +++ b/jixia_academy/core/debate_system/jixia_ollama_swarm.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫本地版 - 基于Ollama的四仙辩论系统 +使用本地Ollama服务,无需API密钥 +""" + +import asyncio +import json +from datetime import datetime +from swarm import Swarm, Agent +from typing import Dict, List, Any, Optional +import random + +class JixiaOllamaSwarm: + """稷下学宫本地版 - 使用Ollama的四仙辩论系统""" + + def __init__(self): + # Ollama配置 + self.ollama_base_url = "http://100.99.183.38:11434" + self.model_name = "gemma3n:e4b" # 使用你指定的模型 + + # 初始化Swarm客户端,使用Ollama + from openai import OpenAI + openai_client = OpenAI( + api_key="ollama", # Ollama不需要真实的API密钥 + base_url=f"{self.ollama_base_url}/v1" + ) + self.client = Swarm(client=openai_client) + + print(f"🦙 使用本地Ollama服务: {self.ollama_base_url}") + print(f"🤖 使用模型: {self.model_name}") + + # 四仙配置 + self.immortals = { + '吕洞宾': { + 'role': '技术分析专家', + 'stance': 'positive', + 'specialty': '技术分析和图表解读', + 'style': '犀利直接,一剑封喉' + }, + '何仙姑': { + 'role': '风险控制专家', + 'stance': 'negative', + 'specialty': '风险评估和资金管理', + 'style': '温和坚定,关注风险' + }, + '张果老': { + 'role': '历史数据分析师', + 'stance': 'positive', + 'specialty': '历史回测和趋势分析', + 'style': '博古通今,从历史找规律' + }, + '铁拐李': { + 'role': '逆向投资大师', + 'stance': 'negative', + 'specialty': '逆向思维和危机发现', + 'style': '不拘一格,挑战共识' + } + } + + # 创建智能体 + self.agents = self.create_agents() + + def create_agents(self) -> Dict[str, Agent]: + """创建四仙智能体""" + agents = {} + + # 吕洞宾 - 技术分析专家 + agents['吕洞宾'] = Agent( + name="LuDongbin", + instructions=""" + 你是吕洞宾,八仙之首,技术分析专家。 + + 你的特点: + - 擅长技术分析和图表解读 + - 立场:看涨派,善于发现投资机会 + - 风格:犀利直接,一剑封喉 + + 在辩论中: + 1. 从技术分析角度分析市场 + 2. 使用具体的技术指标支撑观点(如RSI、MACD、均线等) + 3. 保持看涨的乐观态度 + 4. 发言以"吕洞宾曰:"开头 + 5. 发言控制在100字以内,简洁有力 + 6. 发言完毕后说"请何仙姑继续论道" + + 请用古雅但现代的语言风格,结合专业的技术分析。 + """, + functions=[self.to_hexiangu] + ) + + # 何仙姑 - 风险控制专家 + agents['何仙姑'] = Agent( + name="HeXiangu", + instructions=""" + 你是何仙姑,八仙中唯一的女仙,风险控制专家。 + + 你的特点: + - 擅长风险评估和资金管理 + - 立场:看跌派,关注投资风险 + - 风格:温和坚定,关注风险控制 + + 在辩论中: + 1. 从风险控制角度分析市场 + 2. 指出潜在的投资风险和危险信号 + 3. 保持谨慎的态度,强调风险管理 + 4. 发言以"何仙姑曰:"开头 + 5. 发言控制在100字以内,温和但坚定 + 6. 发言完毕后说"请张果老继续论道" + + 请用温和但专业的语调,体现女性的细致和关怀。 + """, + functions=[self.to_zhangguolao] + ) + + # 张果老 - 历史数据分析师 + agents['张果老'] = Agent( + name="ZhangGuoLao", + instructions=""" + 你是张果老,历史数据分析师。 + + 你的特点: + - 擅长历史回测和趋势分析 + - 立场:看涨派,从历史中寻找机会 + - 风格:博古通今,从历史中找规律 + + 在辩论中: + 1. 从历史数据角度分析市场 + 2. 引用具体的历史案例和数据 + 3. 保持乐观的投资态度 + 4. 发言以"张果老曰:"开头 + 5. 发言控制在100字以内,引经据典 + 6. 发言完毕后说"请铁拐李继续论道" + + 请用博学的语调,多引用历史数据和案例。 + """, + functions=[self.to_tieguaili] + ) + + # 铁拐李 - 逆向投资大师 + agents['铁拐李'] = Agent( + name="TieGuaiLi", + instructions=""" + 你是铁拐李,逆向投资大师。 + + 你的特点: + - 擅长逆向思维和危机发现 + - 立场:看跌派,挑战主流观点 + - 风格:不拘一格,敢于质疑 + + 在辩论中: + 1. 从逆向投资角度分析市场 + 2. 挑战前面三位仙人的观点 + 3. 寻找市场的潜在危机和泡沫 + 4. 发言以"铁拐李曰:"开头 + 5. 作为最后发言者,要总结四仙观点并给出结论 + 6. 发言控制在150字以内,包含总结 + + 请用直率犀利的语言,体现逆向思维的独特视角。 + """, + functions=[] # 最后一个,不需要转换 + ) + + return agents + + def to_hexiangu(self): + """转到何仙姑""" + return self.agents['何仙姑'] + + def to_zhangguolao(self): + """转到张果老""" + return self.agents['张果老'] + + def to_tieguaili(self): + """转到铁拐李""" + return self.agents['铁拐李'] + + async def conduct_debate(self, topic: str, context: Dict[str, Any] = None) -> Dict[str, Any]: + """进行四仙辩论""" + print("🏛️ 稷下学宫四仙论道开始!") + print("=" * 60) + print(f"🎯 论道主题: {topic}") + print(f"⏰ 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"🦙 使用本地Ollama: {self.ollama_base_url}") + print() + + # 构建初始提示 + prompt = self.build_prompt(topic, context) + + try: + print("⚔️ 吕洞宾仙长请先发言...") + print("-" * 40) + + # 开始辩论 + response = self.client.run( + agent=self.agents['吕洞宾'], + messages=[{"role": "user", "content": prompt}], + max_turns=8, # 四仙各发言一次,加上可能的交互 + model_override=self.model_name + ) + + print("\n" + "=" * 60) + print("🎊 四仙论道圆满结束!") + + # 处理结果 + result = self.process_result(response, topic, context) + self.display_summary(result) + + return result + + except Exception as e: + print(f"❌ 论道过程中出错: {e}") + import traceback + traceback.print_exc() + return None + + def build_prompt(self, topic: str, context: Dict[str, Any] = None) -> str: + """构建辩论提示""" + context_str = "" + if context: + context_str = f"\n📊 市场背景:\n{json.dumps(context, indent=2, ensure_ascii=False)}\n" + + prompt = f""" + 🏛️ 稷下学宫四仙论道正式开始! + + 📜 论道主题: {topic} + {context_str} + + 🎭 论道规则: + 1. 四仙按序发言:吕洞宾 → 何仙姑 → 张果老 → 铁拐李 + 2. 正反方交替:吕洞宾(看涨) → 何仙姑(看跌) → 张果老(看涨) → 铁拐李(看跌) + 3. 每位仙人从专业角度分析,提供具体数据支撑 + 4. 可以质疑前面仙人的观点,但要有理有据 + 5. 保持仙风道骨的表达风格,但要专业 + 6. 每次发言简洁有力,控制在100字以内 + 7. 铁拐李作为最后发言者要总结观点 + + 🗡️ 请吕洞宾仙长首先发言! + 记住:你是技术分析专家,要从技术面找到投资机会! + 发言要简洁有力,一剑封喉! + """ + return prompt + + def process_result(self, response, topic: str, context: Dict[str, Any]) -> Dict[str, Any]: + """处理辩论结果""" + messages = response.messages if hasattr(response, 'messages') else [] + + debate_messages = [] + for msg in messages: + if msg.get('role') == 'assistant' and msg.get('content'): + content = msg['content'] + speaker = self.extract_speaker(content) + + debate_messages.append({ + 'speaker': speaker, + 'content': content, + 'timestamp': datetime.now().isoformat(), + 'stance': self.immortals.get(speaker, {}).get('stance', 'unknown') + }) + + return { + "debate_id": f"jixia_ollama_{datetime.now().strftime('%Y%m%d_%H%M%S')}", + "topic": topic, + "context": context, + "messages": debate_messages, + "final_output": debate_messages[-1]['content'] if debate_messages else "", + "timestamp": datetime.now().isoformat(), + "framework": "OpenAI Swarm + Ollama", + "model": self.model_name, + "ollama_url": self.ollama_base_url + } + + def extract_speaker(self, content: str) -> str: + """从内容中提取发言者""" + for name in self.immortals.keys(): + if f"{name}曰" in content: + return name + return "未知仙人" + + def display_summary(self, result: Dict[str, Any]): + """显示辩论总结""" + print("\n🌟 四仙论道总结") + print("=" * 60) + print(f"📜 主题: {result['topic']}") + print(f"⏰ 时间: {result['timestamp']}") + print(f"🔧 框架: {result['framework']}") + print(f"🤖 模型: {result['model']}") + print(f"💬 发言数: {len(result['messages'])}条") + + # 统计正反方观点 + positive_count = len([m for m in result['messages'] if m.get('stance') == 'positive']) + negative_count = len([m for m in result['messages'] if m.get('stance') == 'negative']) + + print(f"📊 观点分布: 看涨{positive_count}条, 看跌{negative_count}条") + + print("\n🏆 最终总结:") + print("-" * 40) + if result['messages']: + print(result['final_output']) + + print("\n✨ 本地辩论特色:") + print("🦙 使用本地Ollama,无需API密钥") + print("🗡️ 四仙各展所长,观点多元") + print("⚖️ 正反方交替,辩论激烈") + print("🚀 基于Swarm,性能优越") + print("🔒 完全本地运行,数据安全") + +# 主函数 +async def main(): + """主函数""" + print("🏛️ 稷下学宫本地版 - Ollama + Swarm") + print("🦙 使用本地Ollama服务,无需API密钥") + print("🚀 四仙论道,完全本地运行") + print() + + # 创建辩论系统 + academy = JixiaOllamaSwarm() + + # 辩论主题 + topics = [ + "英伟达股价走势:AI泡沫还是技术革命?", + "美联储2024年货币政策:加息还是降息?", + "比特币vs黄金:谁是更好的避险资产?", + "中国房地产市场:触底反弹还是继续下行?", + "特斯拉股价:马斯克效应还是基本面支撑?" + ] + + # 随机选择主题 + topic = random.choice(topics) + + # 市场背景 + context = { + "market_sentiment": "谨慎乐观", + "volatility": "中等", + "key_events": ["财报季", "央行会议", "地缘政治"], + "technical_indicators": { + "RSI": 65, + "MACD": "金叉", + "MA20": "上穿" + } + } + + # 开始辩论 + result = await academy.conduct_debate(topic, context) + + if result: + print(f"\n🎉 辩论成功!ID: {result['debate_id']}") + print(f"📁 使用模型: {result['model']}") + print(f"🌐 Ollama服务: {result['ollama_url']}") + else: + print("❌ 辩论失败") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/jixia_swarm_complete.py b/jixia_academy/core/debate_system/jixia_swarm_complete.py new file mode 100644 index 0000000..1b4e9b3 --- /dev/null +++ b/jixia_academy/core/debate_system/jixia_swarm_complete.py @@ -0,0 +1,557 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫完整版 - 基于OpenAI Swarm的八仙辩论系统 +实现完整的八仙论道 + 三清决策 +""" + +import os +import asyncio +import json +import subprocess +from datetime import datetime +from swarm import Swarm, Agent +from typing import Dict, List, Any, Optional +import random + +class JixiaSwarmAcademy: + """稷下学宫 - 完整的八仙辩论系统""" + + def __init__(self): + # 从Doppler获取API密钥 + self.api_key = self.get_secure_api_key() + + # 设置环境变量 + if self.api_key: + os.environ["OPENAI_API_KEY"] = self.api_key + os.environ["OPENAI_BASE_URL"] = "https://openrouter.ai/api/v1" + + # 初始化Swarm客户端,传入配置 + from openai import OpenAI + openai_client = OpenAI( + api_key=self.api_key, + base_url="https://openrouter.ai/api/v1", + default_headers={ + "HTTP-Referer": "https://github.com/ben/cauldron", + "X-Title": "Jixia Academy Debate System" # 避免中文字符 + } + ) + self.client = Swarm(client=openai_client) + else: + print("❌ 无法获取有效的API密钥") + self.client = None + + # 八仙配置 - 完整版 + self.immortals_config = { + '吕洞宾': { + 'role': '剑仙投资顾问', + 'gua_position': '乾☰', + 'specialty': '技术分析', + 'stance': 'positive', + 'style': '一剑封喉,直指要害', + 'personality': '犀利直接,善于识破市场迷雾', + 'weapon': '纯阳剑', + 'next': '何仙姑' + }, + '何仙姑': { + 'role': '慈悲风控专家', + 'gua_position': '坤☷', + 'specialty': '风险控制', + 'stance': 'negative', + 'style': '荷花在手,全局在胸', + 'personality': '温和坚定,关注风险控制', + 'weapon': '荷花', + 'next': '张果老' + }, + '张果老': { + 'role': '历史数据分析师', + 'gua_position': '艮☶', + 'specialty': '历史回测', + 'stance': 'positive', + 'style': '倒骑毛驴,逆向思维', + 'personality': '博古通今,从历史中寻找规律', + 'weapon': '鱼鼓', + 'next': '韩湘子' + }, + '韩湘子': { + 'role': '市场情绪分析师', + 'gua_position': '兑☱', + 'specialty': '情绪分析', + 'stance': 'negative', + 'style': '笛声悠扬,感知人心', + 'personality': '敏感细腻,善于捕捉市场情绪', + 'weapon': '洞箫', + 'next': '汉钟离' + }, + '汉钟离': { + 'role': '宏观经济分析师', + 'gua_position': '离☲', + 'specialty': '宏观分析', + 'stance': 'positive', + 'style': '扇子一挥,大局明了', + 'personality': '气度恢宏,关注宏观大势', + 'weapon': '芭蕉扇', + 'next': '蓝采和' + }, + '蓝采和': { + 'role': '量化交易专家', + 'gua_position': '巽☴', + 'specialty': '量化模型', + 'stance': 'negative', + 'style': '花篮一抛,数据飞舞', + 'personality': '逻辑严密,依赖数学模型', + 'weapon': '花篮', + 'next': '曹国舅' + }, + '曹国舅': { + 'role': '价值投资专家', + 'gua_position': '坎☵', + 'specialty': '基本面分析', + 'stance': 'positive', + 'style': '玉板一敲,价值显现', + 'personality': '稳重踏实,注重内在价值', + 'weapon': '玉板', + 'next': '铁拐李' + }, + '铁拐李': { + 'role': '逆向投资大师', + 'gua_position': '震☳', + 'specialty': '逆向投资', + 'stance': 'negative', + 'style': '铁拐一点,危机毕现', + 'personality': '不拘一格,挑战主流观点', + 'weapon': '铁拐杖', + 'next': 'summary' + } + } + + # 三清决策层配置 + self.sanqing_config = { + '元始天尊': { + 'role': '最终决策者', + 'specialty': '综合决策', + 'style': '无极生太极,一言定乾坤' + }, + '灵宝天尊': { + 'role': '风险评估师', + 'specialty': '风险量化', + 'style': '太极生两仪,阴阳定风险' + }, + '道德天尊': { + 'role': '合规审查员', + 'specialty': '合规检查', + 'style': '两仪生四象,四象定规矩' + } + } + + # 创建智能体 + self.immortal_agents = self.create_immortal_agents() + self.sanqing_agents = self.create_sanqing_agents() + + # 辩论历史 + self.debate_history = [] + self.current_round = 0 + self.max_rounds = 2 # 每个仙人最多发言2轮 + + def get_secure_api_key(self): + """获取API密钥 - 支持多种方式""" + # 从环境变量获取API密钥 + available_keys = [ + os.getenv("OPENROUTER_API_KEY_1"), + os.getenv("OPENROUTER_API_KEY_2"), + os.getenv("OPENROUTER_API_KEY_3"), + os.getenv("OPENROUTER_API_KEY_4") + ] + # 过滤掉None值 + available_keys = [key for key in available_keys if key] + + # 直接使用第一个密钥进行测试 + test_key = available_keys[0] + print(f"🔑 直接使用测试密钥: {test_key[:20]}...") + return test_key + + + + def create_immortal_agents(self) -> Dict[str, Agent]: + """创建八仙智能体""" + agents = {} + + for name, config in self.immortals_config.items(): + # 创建转换函数 - 使用英文名称避免特殊字符问题 + next_immortal = config['next'] + if next_immortal == 'summary': + transfer_func = self.transfer_to_sanqing + else: + # 创建一个简单的转换函数,避免lambda的问题 + def create_transfer_func(next_name): + def transfer(): + return self.transfer_to_immortal(next_name) + transfer.__name__ = f"transfer_to_{self.get_english_name(next_name)}" + return transfer + transfer_func = create_transfer_func(next_immortal) + + # 构建详细的指令 + instructions = self.build_immortal_instructions(name, config) + + agents[name] = Agent( + name=name, + instructions=instructions, + functions=[transfer_func] + ) + + return agents + + def create_sanqing_agents(self) -> Dict[str, Agent]: + """创建三清决策层智能体""" + agents = {} + + # 元始天尊 - 最终决策者 + agents['元始天尊'] = Agent( + name="元始天尊", + instructions=""" + 你是元始天尊,道教三清之首,稷下学宫的最终决策者。 + + 你的使命: + 1. 综合八仙的所有观点,做出最终投资决策 + 2. 平衡正反两方的观点,寻找最优解 + 3. 给出具体的投资建议和操作指导 + 4. 评估决策的风险等级和预期收益 + + 你的风格: + - 高屋建瓴,统揽全局 + - 言简意赅,一锤定音 + - 既不偏向乐观,也不偏向悲观 + - 以数据和逻辑为准绳 + + 请以"元始天尊曰"开头,给出最终决策。 + 决策格式: + - 投资建议:买入/持有/卖出 + - 风险等级:低/中/高 + - 预期收益:具体百分比 + - 操作建议:具体的操作指导 + - 决策依据:主要的决策理由 + """, + functions=[] + ) + + return agents + + def build_immortal_instructions(self, name: str, config: Dict) -> str: + """构建仙人的详细指令""" + stance_desc = "看涨派,倾向于发现投资机会" if config['stance'] == 'positive' else "看跌派,倾向于发现投资风险" + + instructions = f""" + 你是{name},八仙之一,{config['role']}。 + + 你的身份特征: + - 位居{config['gua_position']}之位,代表{self.get_gua_meaning(config['gua_position'])} + - 持有{config['weapon']},{config['style']} + - 擅长{config['specialty']},{config['personality']} + - 立场倾向:{stance_desc} + + 在稷下学宫辩论中,你要: + + 1. **专业分析**:从{config['specialty']}角度深入分析 + 2. **立场鲜明**:作为{stance_desc},要有明确的观点 + 3. **数据支撑**:用具体的数据、图表、历史案例支撑观点 + 4. **互动辩论**:可以质疑前面仙人的观点,但要有理有据 + 5. **仙风道骨**:保持古雅的表达风格,但不影响专业性 + 6. **承上启下**:总结前面的观点,为后面的仙人铺垫 + + 发言格式: + - 以"{name}曰:"开头 + - 先简要回应前面仙人的观点(如果有) + - 然后从你的专业角度进行分析 + - 最后明确表达你的投资倾向 + - 结尾时说"请{config['next']}仙长继续论道"(如果不是最后一个) + + 记住:你是{stance_desc},要体现这个立场,但也要保持专业和客观。 + """ + + return instructions + + def get_gua_meaning(self, gua: str) -> str: + """获取卦象含义""" + meanings = { + '乾☰': '天行健,自强不息', + '坤☷': '地势坤,厚德载物', + '艮☶': '艮为山,止于至善', + '兑☱': '兑为泽,和悦致祥', + '离☲': '离为火,光明磊落', + '巽☴': '巽为风,随风而化', + '坎☵': '坎为水,智慧如水', + '震☳': '震为雷,威震四方' + } + return meanings.get(gua, '神秘莫测') + + def transfer_to_hexiangu(self): + """转到何仙姑""" + return self.immortal_agents.get('何仙姑') + + def transfer_to_zhangguolao(self): + """转到张果老""" + return self.immortal_agents.get('张果老') + + def transfer_to_hanxiangzi(self): + """转到韩湘子""" + return self.immortal_agents.get('韩湘子') + + def transfer_to_hanzhongli(self): + """转到汉钟离""" + return self.immortal_agents.get('汉钟离') + + def transfer_to_lancaihe(self): + """转到蓝采和""" + return self.immortal_agents.get('蓝采和') + + def transfer_to_caoguojiu(self): + """转到曹国舅""" + return self.immortal_agents.get('曹国舅') + + def transfer_to_tieguaili(self): + """转到铁拐李""" + return self.immortal_agents.get('铁拐李') + + def transfer_to_sanqing(self): + """转到三清决策层""" + return self.sanqing_agents['元始天尊'] + + async def conduct_full_debate(self, topic: str, context: Dict[str, Any] = None) -> Dict[str, Any]: + """进行完整的稷下学宫辩论""" + if not self.api_key or not self.client: + print("❌ 无法获取API密钥或初始化客户端,无法进行论道") + return None + + print("🏛️ 稷下学宫八仙论道正式开始!") + print("=" * 80) + print(f"🎯 论道主题: {topic}") + print(f"⏰ 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print() + + # 构建初始提示 + initial_prompt = self.build_debate_prompt(topic, context) + + try: + # 从吕洞宾开始论道 + print("⚔️ 吕洞宾仙长请先发言...") + print("-" * 60) + + response = self.client.run( + agent=self.immortal_agents['吕洞宾'], + messages=[{"role": "user", "content": initial_prompt}], + max_turns=20 # 允许多轮对话 + ) + + print("\n" + "=" * 80) + print("🎊 稷下学宫八仙论道圆满结束!") + print("📊 三清决策已生成") + + # 处理辩论结果 + debate_result = self.process_debate_result(response, topic, context) + + # 显示辩论总结 + self.display_debate_summary(debate_result) + + return debate_result + + except Exception as e: + print(f"❌ 论道过程中出错: {e}") + import traceback + traceback.print_exc() + return None + + def build_debate_prompt(self, topic: str, context: Dict[str, Any] = None) -> str: + """构建辩论提示""" + context_str = "" + if context: + context_str = f"\n📊 市场背景:\n{json.dumps(context, indent=2, ensure_ascii=False)}\n" + + # 随机选择一些市场数据作为背景 + market_context = self.generate_market_context(topic) + + prompt = f""" + 🏛️ 稷下学宫八仙论道正式开始! + + 📜 论道主题: {topic} + {context_str} + 📈 当前市场环境: + {market_context} + + 🎭 论道规则: + 1. 八仙按序发言:吕洞宾 → 何仙姑 → 张果老 → 韩湘子 → 汉钟离 → 蓝采和 → 曹国舅 → 铁拐李 + 2. 正反方交替:正方(看涨) vs 反方(看跌) + 3. 每位仙人从专业角度分析,必须提供数据支撑 + 4. 可以质疑前面仙人的观点,但要有理有据 + 5. 保持仙风道骨的表达风格 + 6. 最后由三清做出最终决策 + + 🗡️ 请吕洞宾仙长首先发言,展现剑仙的犀利分析! + 记住:你是看涨派,要从技术分析角度找到投资机会! + """ + return prompt + + def generate_market_context(self, topic: str) -> str: + """生成模拟的市场背景数据""" + # 这里可以集成真实的市场数据,现在先用模拟数据 + contexts = { + "英伟达": "NVDA当前价格$120,P/E比率65,市值$3T,AI芯片需求旺盛", + "比特币": "BTC当前价格$43,000,24h涨幅+2.3%,机构持续买入", + "美联储": "联邦基金利率5.25%,通胀率3.2%,就业数据强劲", + "中国股市": "上证指数3100点,外资流入放缓,政策支持预期" + } + + # 根据主题选择相关背景 + for key, context in contexts.items(): + if key in topic: + return context + + return "市场情绪谨慎,波动率上升,投资者观望情绪浓厚" + + def process_debate_result(self, response, topic: str, context: Dict[str, Any]) -> Dict[str, Any]: + """处理辩论结果""" + # 提取所有消息 + all_messages = response.messages if hasattr(response, 'messages') else [] + + # 分析发言者和内容 + debate_messages = [] + speakers = [] + + for msg in all_messages: + if msg.get('role') == 'assistant' and msg.get('content'): + content = msg['content'] + speaker = self.extract_speaker_from_content(content) + + debate_messages.append({ + 'speaker': speaker, + 'content': content, + 'timestamp': datetime.now().isoformat(), + 'stance': self.get_speaker_stance(speaker) + }) + + if speaker not in speakers: + speakers.append(speaker) + + # 提取最终决策(通常是最后一条消息) + final_decision = "" + if debate_messages: + final_decision = debate_messages[-1]['content'] + + # 构建结果 + result = { + "debate_id": f"jixia_debate_{datetime.now().strftime('%Y%m%d_%H%M%S')}", + "topic": topic, + "context": context, + "participants": speakers, + "messages": debate_messages, + "final_decision": final_decision, + "summary": self.generate_debate_summary(debate_messages), + "timestamp": datetime.now().isoformat(), + "framework": "OpenAI Swarm", + "academy": "稷下学宫" + } + + self.debate_history.append(result) + return result + + def extract_speaker_from_content(self, content: str) -> str: + """从内容中提取发言者""" + for name in list(self.immortals_config.keys()) + list(self.sanqing_config.keys()): + if f"{name}曰" in content or name in content[:20]: + return name + return "未知仙人" + + def get_speaker_stance(self, speaker: str) -> str: + """获取发言者立场""" + if speaker in self.immortals_config: + return self.immortals_config[speaker]['stance'] + elif speaker in self.sanqing_config: + return 'neutral' + return 'unknown' + + def generate_debate_summary(self, messages: List[Dict]) -> str: + """生成辩论摘要""" + positive_count = len([m for m in messages if m.get('stance') == 'positive']) + negative_count = len([m for m in messages if m.get('stance') == 'negative']) + + summary = f""" + 📊 辩论统计: + - 参与仙人: {len(set(m['speaker'] for m in messages))}位 + - 看涨观点: {positive_count}条 + - 看跌观点: {negative_count}条 + - 总发言数: {len(messages)}条 + + 🎯 观点倾向: {'偏向看涨' if positive_count > negative_count else '偏向看跌' if negative_count > positive_count else '观点平衡'} + """ + + return summary + + def display_debate_summary(self, result: Dict[str, Any]): + """显示辩论总结""" + print("\n🌟 稷下学宫辩论总结") + print("=" * 80) + print(f"📜 主题: {result['topic']}") + print(f"🎭 参与仙人: {', '.join(result['participants'])}") + print(f"⏰ 辩论时间: {result['timestamp']}") + print(f"🔧 技术框架: {result['framework']}") + + print(result['summary']) + + print("\n🏆 最终决策:") + print("-" * 40) + print(result['final_decision']) + + print("\n✨ 稷下学宫辩论特色:") + print("🗡️ 八仙各展所长,观点多元化") + print("⚖️ 正反方交替发言,辩论更激烈") + print("🧠 三清最终决策,权威性更强") + print("🔄 基于Swarm框架,性能更优越") + +# 主函数和测试 +async def main(): + """主函数 - 演示完整的稷下学宫辩论""" + print("🏛️ 稷下学宫 - OpenAI Swarm完整版") + print("🔐 使用Doppler安全管理API密钥") + print("🚀 八仙论道 + 三清决策的完整体验") + print() + + # 创建学宫 + academy = JixiaSwarmAcademy() + + if not academy.api_key: + print("❌ 无法获取API密钥,请检查Doppler配置或环境变量") + return + + # 辩论主题列表 + topics = [ + "英伟达股价走势:AI泡沫还是技术革命?", + "美联储2024年货币政策:加息还是降息?", + "比特币vs黄金:谁是更好的避险资产?", + "中国房地产市场:触底反弹还是继续下行?", + "特斯拉股价:马斯克效应还是基本面支撑?" + ] + + # 随机选择主题 + topic = random.choice(topics) + + # 构建市场背景 + context = { + "market_sentiment": "谨慎乐观", + "volatility": "中等", + "major_events": ["美联储会议", "财报季", "地缘政治紧张"], + "technical_indicators": { + "RSI": 65, + "MACD": "金叉", + "MA20": "上穿" + } + } + + # 开始辩论 + result = await academy.conduct_full_debate(topic, context) + + if result: + print(f"\n🎉 辩论成功完成!辩论ID: {result['debate_id']}") + else: + print("❌ 辩论失败") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/main.py b/jixia_academy/core/debate_system/main.py new file mode 100644 index 0000000..e45ea49 --- /dev/null +++ b/jixia_academy/core/debate_system/main.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +稷下学宫AI辩论系统 - 核心引擎 +Jixia Academy AI Debate System - Core Engine +""" + +import asyncio +import logging +from typing import List, Dict, Any, Optional +from pathlib import Path + +from jixia_academy.core.memory_bank.factory import get_memory_backend +from jixia_academy.agents.baxian.baxian_coordinator import BaxianCoordinator +from jixia_academy.agents.host.debate_master import DebateMaster + + +class JixiaAcademy: + """稷下学宫AI辩论系统主类""" + + def __init__(self): + self.memory_bank = None + self.baxian_coordinator = None + self.debate_master = None + self.initialized = False + + async def initialize(self): + """初始化系统""" + if self.initialized: + return + + print("🚀 初始化稷下学宫AI辩论系统...") + + # 初始化记忆银行 + self.memory_bank = get_memory_backend() + await self.memory_bank.initialize() + + # 初始化八仙协调器 + self.baxian_coordinator = BaxianCoordinator(memory_bank=self.memory_bank) + await self.baxian_coordinator.initialize() + + # 初始化辩论主持人 + self.debate_master = DebateMaster(memory_bank=self.memory_bank) + await self.debate_master.initialize() + + self.initialized = True + print("✅ 稷下学宫AI辩论系统初始化完成") + + async def close(self): + """关闭系统资源""" + if self.memory_bank: + await self.memory_bank.close() + if self.baxian_coordinator: + await self.baxian_coordinator.close() + if self.debate_master: + await self.debate_master.close() + + self.initialized = False + print("🛑 系统已关闭") + + async def run_baxian_debate( + self, + topic: str, + rounds: int = 3, + participants: List[str] = None + ): + """运行八仙论道辩论""" + if not self.initialized: + await self.initialize() + + if participants is None: + participants = [ + "铁拐李", "吕洞宾", "何仙姑", "张果老", + "蓝采和", "汉钟离", "韩湘子", "曹国舅" + ] + + print(f"\n🏛️ 稷下学宫八仙论道") + print(f"📋 辩论主题: {topic}") + print(f"🎭 参与八仙: {', '.join(participants)}") + print(f"🔄 辩论轮数: {rounds}") + print("=" * 50) + + # 创建辩论会话 + debate_id = await self.memory_bank.create_debate_session( + topic=topic, + participants=participants, + debate_type="baxian" + ) + + # 运行辩论 + await self.baxian_coordinator.conduct_debate( + topic=topic, + participants=participants, + rounds=rounds, + debate_id=debate_id + ) + + # 生成辩论总结 + summary = await self.debate_master.summarize_debate(debate_id) + print(f"\n📊 辩论总结: {summary}") + + async def run_memory_enhanced_debate( + self, + topic: str, + engine: str = "adk" + ): + """运行记忆增强辩论""" + if not self.initialized: + await self.initialize() + + print(f"\n🧠 记忆增强辩论") + print(f"📋 主题: {topic}") + print(f"⚙️ 引擎: {engine}") + print("=" * 50) + + # 获取历史记忆 + historical_context = await self.memory_bank.get_related_memories(topic) + + # 创建增强辩论会话 + debate_id = await self.memory_bank.create_debate_session( + topic=topic, + participants=["记忆增强AI"], + debate_type="memory_enhanced", + context=historical_context + ) + + # 运行辩论(根据引擎类型) + if engine == "adk": + await self._run_adk_memory_debate(topic, debate_id) + elif engine == "swarm": + await self._run_swarm_memory_debate(topic, debate_id) + + async def _run_adk_memory_debate(self, topic: str, debate_id: str): + """运行ADK记忆增强辩论""" + from jixia_academy.integrations.adk.adk_client import ADKClient + + client = ADKClient(memory_bank=self.memory_bank) + await client.run_debate(topic, debate_id) + + async def _run_swarm_memory_debate(self, topic: str, debate_id: str): + """运行Swarm记忆增强辩论""" + from jixia_academy.integrations.adk.swarm_client import SwarmClient + + client = SwarmClient(memory_bank=self.memory_bank) + await client.run_debate(topic, debate_id) + + async def get_system_status(self) -> Dict[str, Any]: + """获取系统状态""" + status = { + "initialized": self.initialized, + "memory_bank": "connected" if self.memory_bank else "disconnected", + "baxian_coordinator": "ready" if self.baxian_coordinator else "not_ready", + "debate_master": "ready" if self.debate_master else "not_ready" + } + + if self.memory_bank: + stats = await self.memory_bank.get_stats() + status["memory_stats"] = stats + + return status + + +async def main(): + """测试主函数""" + academy = JixiaAcademy() + await academy.initialize() + + # 获取系统状态 + status = await academy.get_system_status() + print("系统状态:", status) + + await academy.close() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/memory/base_memory_bank.py b/jixia_academy/core/debate_system/memory/base_memory_bank.py new file mode 100644 index 0000000..9244514 --- /dev/null +++ b/jixia_academy/core/debate_system/memory/base_memory_bank.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +通用记忆银行抽象,便于插入不同后端(Vertex、Cloudflare AutoRAG等) +""" +from __future__ import annotations + +from typing import Dict, List, Any, Optional, Protocol, runtime_checkable + + +@runtime_checkable +class MemoryBankProtocol(Protocol): + async def create_memory_bank(self, agent_name: str, display_name: Optional[str] = None) -> str: ... + + async def add_memory( + self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Optional[Dict[str, Any]] = None, + ) -> str: ... + + async def search_memories( + self, + agent_name: str, + query: str, + memory_type: Optional[str] = None, + limit: int = 10, + ) -> List[Dict[str, Any]]: ... + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: ... + + async def save_debate_session( + self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Optional[Dict[str, Any]] = None, + ) -> None: ... diff --git a/jixia_academy/core/debate_system/memory/cloudflare_memory_bank.py b/jixia_academy/core/debate_system/memory/cloudflare_memory_bank.py new file mode 100644 index 0000000..2c284c6 --- /dev/null +++ b/jixia_academy/core/debate_system/memory/cloudflare_memory_bank.py @@ -0,0 +1,454 @@ +#!/usr/bin/env python3 +""" +Cloudflare AutoRAG Vectorize 记忆银行实现 +为稷下学宫AI辩论系统提供Cloudflare后端的记忆功能 +""" + +import os +import json +from typing import Dict, List, Optional, Any +from dataclasses import dataclass +from datetime import datetime +import aiohttp +from config.settings import get_cloudflare_config + + +@dataclass +class MemoryEntry: + """记忆条目数据结构""" + id: str + content: str + metadata: Dict[str, Any] + timestamp: str # ISO format string + agent_name: str + debate_topic: str + memory_type: str # "conversation", "preference", "knowledge", "strategy" + + +class CloudflareMemoryBank: + """ + Cloudflare AutoRAG Vectorize 记忆银行管理器 + 利用Cloudflare Vectorize索引和Workers AI进行向量检索增强生成 + """ + + def __init__(self): + """初始化Cloudflare Memory Bank""" + self.config = get_cloudflare_config() + self.account_id = self.config['account_id'] + self.api_token = self.config['api_token'] + self.vectorize_index = self.config['vectorize_index'] + self.embed_model = self.config['embed_model'] + self.autorag_domain = self.config['autorag_domain'] + + # 构建API基础URL + self.base_url = f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}" + self.headers = { + "Authorization": f"Bearer {self.api_token}", + "Content-Type": "application/json" + } + + # 八仙智能体名称映射 + self.baxian_agents = { + "tieguaili": "铁拐李", + "hanzhongli": "汉钟离", + "zhangguolao": "张果老", + "lancaihe": "蓝采和", + "hexiangu": "何仙姑", + "lvdongbin": "吕洞宾", + "hanxiangzi": "韩湘子", + "caoguojiu": "曹国舅" + } + + async def _get_session(self) -> aiohttp.ClientSession: + """获取aiohttp会话""" + return aiohttp.ClientSession() + + async def create_memory_bank(self, agent_name: str, display_name: str = None) -> str: + """ + 为指定智能体创建记忆空间(在Cloudflare中通过命名空间或元数据实现) + + Args: + agent_name: 智能体名称 (如 "tieguaili") + display_name: 显示名称 (如 "铁拐李的记忆银行") + + Returns: + 记忆空间标识符 (这里用agent_name作为标识符) + """ + # Cloudflare Vectorize使用统一的索引,通过元数据区分不同智能体的记忆 + # 所以这里不需要实际创建,只需要返回标识符 + if not display_name: + display_name = self.baxian_agents.get(agent_name, agent_name) + + print(f"✅ 为 {display_name} 准备Cloudflare记忆空间") + return f"cf_memory_{agent_name}" + + async def add_memory(self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Dict[str, Any] = None) -> str: + """ + 添加记忆到Cloudflare Vectorize索引 + + Args: + agent_name: 智能体名称 + content: 记忆内容 + memory_type: 记忆类型 ("conversation", "preference", "knowledge", "strategy") + debate_topic: 辩论主题 + metadata: 额外元数据 + + Returns: + 记忆ID + """ + if metadata is None: + metadata = {} + + # 生成记忆ID + memory_id = f"mem_{agent_name}_{int(datetime.now().timestamp() * 1000000)}" + + # 构建记忆条目 + memory_entry = MemoryEntry( + id=memory_id, + content=content, + metadata={ + **metadata, + "agent_name": agent_name, + "chinese_name": self.baxian_agents.get(agent_name, agent_name), + "memory_type": memory_type, + "debate_topic": debate_topic, + "system": "jixia_academy" + }, + timestamp=datetime.now().isoformat(), + agent_name=agent_name, + debate_topic=debate_topic, + memory_type=memory_type + ) + + # 将记忆条目转换为JSON字符串,用于存储和检索 + memory_data = { + "id": memory_id, + "values": [], # 向量值将在嵌入时填充 + "metadata": memory_entry.metadata + } + + try: + # 1. 使用Workers AI生成嵌入向量 + embedding = await self._generate_embedding(content) + memory_data["values"] = embedding + + # 2. 将记忆插入Vectorize索引 + async with await self._get_session() as session: + url = f"{self.base_url}/vectorize/indexes/{self.vectorize_index}/upsert" + payload = { + "vectors": [memory_data] + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + print(f"✅ 为 {self.baxian_agents.get(agent_name)} 添加记忆: {memory_type}") + return memory_id + else: + error_text = await response.text() + raise Exception(f"Failed to upsert memory: {response.status} - {error_text}") + + except Exception as e: + print(f"❌ 添加记忆失败: {e}") + raise + + async def _generate_embedding(self, text: str) -> List[float]: + """ + 使用Cloudflare Workers AI生成文本嵌入 + + Args: + text: 要嵌入的文本 + + Returns: + 嵌入向量 + """ + async with await self._get_session() as session: + url = f"{self.base_url}/ai/run/{self.embed_model}" + payload = { + "text": [text] # Workers AI embeddings API expects a list of texts + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + # 提取嵌入向量 (通常是 result["result"]["data"][0]["embedding"]) + if "result" in result and "data" in result["result"] and len(result["result"]["data"]) > 0: + return result["result"]["data"][0]["embedding"] + else: + raise Exception(f"Unexpected embedding response format: {result}") + else: + error_text = await response.text() + raise Exception(f"Failed to generate embedding: {response.status} - {error_text}") + + async def search_memories(self, + agent_name: str, + query: str, + memory_type: str = None, + limit: int = 10) -> List[Dict[str, Any]]: + """ + 使用向量相似性搜索智能体的相关记忆 + + Args: + agent_name: 智能体名称 + query: 搜索查询 + memory_type: 记忆类型过滤 + limit: 返回结果数量限制 + + Returns: + 相关记忆列表 + """ + try: + # 1. 为查询生成嵌入向量 + query_embedding = await self._generate_embedding(query) + + # 2. 构建过滤条件 + filters = { + "agent_name": agent_name + } + if memory_type: + filters["memory_type"] = memory_type + + # 3. 执行向量搜索 + async with await self._get_session() as session: + url = f"{self.base_url}/vectorize/indexes/{self.vectorize_index}/query" + payload = { + "vector": query_embedding, + "topK": limit, + "filter": filters, + "returnMetadata": True + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + matches = result.get("result", {}).get("matches", []) + + # 格式化返回结果 + memories = [] + for match in matches: + memory_data = { + "content": match["metadata"].get("content", ""), + "metadata": match["metadata"], + "relevance_score": match["score"] + } + memories.append(memory_data) + + return memories + else: + error_text = await response.text() + raise Exception(f"Failed to search memories: {response.status} - {error_text}") + + except Exception as e: + print(f"❌ 搜索记忆失败: {e}") + return [] + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: + """ + 获取智能体在特定辩论主题下的上下文记忆 + + Args: + agent_name: 智能体名称 + debate_topic: 辩论主题 + + Returns: + 格式化的上下文字符串 + """ + # 搜索相关记忆 + conversation_memories = await self.search_memories( + agent_name, debate_topic, "conversation", limit=5 + ) + preference_memories = await self.search_memories( + agent_name, debate_topic, "preference", limit=3 + ) + strategy_memories = await self.search_memories( + agent_name, debate_topic, "strategy", limit=3 + ) + + # 构建上下文 + context_parts = [] + + if conversation_memories: + context_parts.append("## 历史对话记忆") + for mem in conversation_memories: + context_parts.append(f"- {mem['content']}") + + if preference_memories: + context_parts.append("\n## 偏好记忆") + for mem in preference_memories: + context_parts.append(f"- {mem['content']}") + + if strategy_memories: + context_parts.append("\n## 策略记忆") + for mem in strategy_memories: + context_parts.append(f"- {mem['content']}") + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + if context_parts: + return f"# {chinese_name}的记忆上下文\n\n" + "\n".join(context_parts) + else: + return f"# {chinese_name}的记忆上下文\n\n暂无相关记忆。" + + async def save_debate_session(self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Dict[str, Any] = None) -> None: + """ + 保存完整的辩论会话到各参与者的记忆银行 + + Args: + debate_topic: 辩论主题 + participants: 参与者列表 + conversation_history: 对话历史 + outcomes: 辩论结果和洞察 + """ + for agent_name in participants: + if agent_name not in self.baxian_agents: + continue + + # 保存对话历史 + conversation_summary = self._summarize_conversation( + conversation_history, agent_name + ) + await self.add_memory( + agent_name=agent_name, + content=conversation_summary, + memory_type="conversation", + debate_topic=debate_topic, + metadata={ + "participants": participants, + "session_length": len(conversation_history) + } + ) + + # 保存策略洞察 + if outcomes: + strategy_insight = self._extract_strategy_insight( + outcomes, agent_name + ) + if strategy_insight: + await self.add_memory( + agent_name=agent_name, + content=strategy_insight, + memory_type="strategy", + debate_topic=debate_topic, + metadata={"session_outcome": outcomes} + ) + + def _summarize_conversation(self, + conversation_history: List[Dict[str, str]], + agent_name: str) -> str: + """ + 为特定智能体总结对话历史 + + Args: + conversation_history: 对话历史 + agent_name: 智能体名称 + + Returns: + 对话总结 + """ + agent_messages = [ + msg for msg in conversation_history + if msg.get("agent") == agent_name + ] + + if not agent_messages: + return "本次辩论中未发言" + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + summary = f"{chinese_name}在本次辩论中的主要观点:\n" + + for i, msg in enumerate(agent_messages[:3], 1): # 只取前3条主要观点 + summary += f"{i}. {msg.get('content', '')[:100]}...\n" + + return summary + + def _extract_strategy_insight(self, + outcomes: Dict[str, Any], + agent_name: str) -> Optional[str]: + """ + 从辩论结果中提取策略洞察 + + Args: + outcomes: 辩论结果 + agent_name: 智能体名称 + + Returns: + 策略洞察或None + """ + # 这里可以根据实际的outcomes结构来提取洞察 + # 暂时返回一个简单的示例 + chinese_name = self.baxian_agents.get(agent_name, agent_name) + + if "winner" in outcomes and outcomes["winner"] == agent_name: + return f"{chinese_name}在本次辩论中获胜,其论证策略值得保持。" + elif "insights" in outcomes and agent_name in outcomes["insights"]: + return outcomes["insights"][agent_name] + + return None + + +# 便捷函数 +async def initialize_baxian_memory_banks() -> CloudflareMemoryBank: + """ + 初始化所有八仙智能体的Cloudflare记忆空间 + + Returns: + 配置好的CloudflareMemoryBank实例 + """ + memory_bank = CloudflareMemoryBank() + + print("🏛️ 正在为稷下学宫八仙创建Cloudflare记忆空间...") + + for agent_key, chinese_name in memory_bank.baxian_agents.items(): + try: + await memory_bank.create_memory_bank(agent_key) + except Exception as e: + print(f"⚠️ 创建 {chinese_name} 记忆空间时出错: {e}") + + print("✅ 八仙Cloudflare记忆空间初始化完成") + return memory_bank + + +if __name__ == "__main__": + import asyncio + + async def test_memory_bank(): + """测试Cloudflare Memory Bank功能""" + try: + # 创建Memory Bank实例 + memory_bank = CloudflareMemoryBank() + + # 测试创建记忆空间 + await memory_bank.create_memory_bank("tieguaili") + + # 测试添加记忆 + await memory_bank.add_memory( + agent_name="tieguaili", + content="在讨论NVIDIA股票时,我倾向于逆向思维,关注潜在风险。", + memory_type="preference", + debate_topic="NVIDIA投资分析" + ) + + # 测试搜索记忆 + results = await memory_bank.search_memories( + agent_name="tieguaili", + query="NVIDIA", + limit=5 + ) + + print(f"搜索结果: {len(results)} 条记忆") + for result in results: + print(f"- {result['content']}") + + except Exception as e: + print(f"❌ 测试失败: {e}") + + # 运行测试 + asyncio.run(test_memory_bank()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/memory/factory.py b/jixia_academy/core/debate_system/memory/factory.py new file mode 100644 index 0000000..b968d24 --- /dev/null +++ b/jixia_academy/core/debate_system/memory/factory.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +记忆银行工厂:根据配置创建不同后端实现(Vertex AI 或 Cloudflare AutoRAG) +""" +from __future__ import annotations + +import os +from typing import Optional + +from .base_memory_bank import MemoryBankProtocol +from .vertex_memory_bank import VertexMemoryBank +# 新增 Cloudflare 实现 +from .cloudflare_memory_bank import CloudflareMemoryBank + + +def get_memory_backend(prefer: Optional[str] = None) -> MemoryBankProtocol: + """ + 强制使用 Vertex AI 作为记忆后端。 + 'prefer' 参数将被忽略。 + """ + # 强制使用 Vertex AI 后端 + try: + mem = VertexMemoryBank.from_config() + print("🧠 使用 Vertex AI 作为记忆后端") + return mem + except Exception as e: + # 不可用时抛错 + raise RuntimeError( + "未能创建 Vertex 记忆后端:请配置 Vertex (GOOGLE_*) 环境变量" + ) from e diff --git a/jixia_academy/core/debate_system/memory/vertex_memory_bank.py b/jixia_academy/core/debate_system/memory/vertex_memory_bank.py new file mode 100644 index 0000000..63e0621 --- /dev/null +++ b/jixia_academy/core/debate_system/memory/vertex_memory_bank.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +""" +Vertex AI Memory Bank 集成模块 +为稷下学宫AI辩论系统提供记忆银行功能 +""" + +import os +from typing import Dict, List, Optional, Any +from dataclasses import dataclass +from datetime import datetime +import json + +try: + from google.cloud import aiplatform + # Memory Bank 功能可能还在预览版中,先使用基础功能 + VERTEX_AI_AVAILABLE = True +except ImportError: + VERTEX_AI_AVAILABLE = False + print("⚠️ Google Cloud AI Platform 未安装,Memory Bank功能不可用") + print("安装命令: pip install google-cloud-aiplatform") + +from config.settings import get_google_genai_config + + +@dataclass +class MemoryEntry: + """记忆条目数据结构""" + content: str + metadata: Dict[str, Any] + timestamp: datetime + agent_name: str + debate_topic: str + memory_type: str # "conversation", "preference", "knowledge", "strategy" + + +class VertexMemoryBank: + """ + Vertex AI Memory Bank 管理器 + 为八仙辩论系统提供智能记忆功能 + """ + + def __init__(self, project_id: str, location: str = "us-central1"): + """ + 初始化Memory Bank + + Args: + project_id: Google Cloud项目ID + location: 部署区域 + """ + if not VERTEX_AI_AVAILABLE: + print("⚠️ Google Cloud AI Platform 未安装,使用本地模拟模式") + # 不抛出异常,允许使用本地模拟模式 + + self.project_id = project_id + self.location = location + self.memory_banks = {} # 存储不同智能体的记忆银行 + self.local_memories = {} # 本地记忆存储 (临时方案) + + # 初始化AI Platform + try: + aiplatform.init(project=project_id, location=location) + print(f"✅ Vertex AI 初始化成功: {project_id} @ {location}") + except Exception as e: + print(f"⚠️ Vertex AI 初始化失败,使用本地模拟模式: {e}") + + # 八仙智能体名称映射 + self.baxian_agents = { + "tieguaili": "铁拐李", + "hanzhongli": "汉钟离", + "zhangguolao": "张果老", + "lancaihe": "蓝采和", + "hexiangu": "何仙姑", + "lvdongbin": "吕洞宾", + "hanxiangzi": "韩湘子", + "caoguojiu": "曹国舅" + } + + @classmethod + def from_config(cls) -> 'VertexMemoryBank': + """ + 从配置创建Memory Bank实例 + + Returns: + VertexMemoryBank实例 + """ + config = get_google_genai_config() + project_id = config.get('project_id') + location = config.get('location', 'us-central1') + + if not project_id: + raise ValueError("Google Cloud Project ID 未配置,请设置 GOOGLE_CLOUD_PROJECT_ID") + + return cls(project_id=project_id, location=location) + + async def create_memory_bank(self, agent_name: str, display_name: str = None) -> str: + """ + 为指定智能体创建记忆银行 + + Args: + agent_name: 智能体名称 (如 "tieguaili") + display_name: 显示名称 (如 "铁拐李的记忆银行") + + Returns: + 记忆银行ID + """ + if not display_name: + chinese_name = self.baxian_agents.get(agent_name, agent_name) + display_name = f"{chinese_name}的记忆银行" + + try: + # 使用本地存储模拟记忆银行 (临时方案) + memory_bank_id = f"memory_bank_{agent_name}_{self.project_id}" + + # 初始化本地记忆存储 + if agent_name not in self.local_memories: + self.local_memories[agent_name] = [] + + self.memory_banks[agent_name] = memory_bank_id + print(f"✅ 为 {display_name} 创建记忆银行: {memory_bank_id}") + + return memory_bank_id + + except Exception as e: + print(f"❌ 创建记忆银行失败: {e}") + raise + + async def add_memory(self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Dict[str, Any] = None) -> str: + """ + 添加记忆到指定智能体的记忆银行 + + Args: + agent_name: 智能体名称 + content: 记忆内容 + memory_type: 记忆类型 ("conversation", "preference", "knowledge", "strategy") + debate_topic: 辩论主题 + metadata: 额外元数据 + + Returns: + 记忆ID + """ + if agent_name not in self.memory_banks: + await self.create_memory_bank(agent_name) + + if metadata is None: + metadata = {} + + # 构建记忆条目 + memory_entry = MemoryEntry( + content=content, + metadata={ + **metadata, + "agent_name": agent_name, + "chinese_name": self.baxian_agents.get(agent_name, agent_name), + "memory_type": memory_type, + "debate_topic": debate_topic, + "system": "jixia_academy" + }, + timestamp=datetime.now(), + agent_name=agent_name, + debate_topic=debate_topic, + memory_type=memory_type + ) + + try: + # 使用本地存储添加记忆 (临时方案) + memory_id = f"memory_{agent_name}_{len(self.local_memories[agent_name])}" + + # 添加到本地存储 + memory_data = { + "id": memory_id, + "content": content, + "metadata": memory_entry.metadata, + "timestamp": memory_entry.timestamp.isoformat(), + "memory_type": memory_type, + "debate_topic": debate_topic + } + + self.local_memories[agent_name].append(memory_data) + + print(f"✅ 为 {self.baxian_agents.get(agent_name)} 添加记忆: {memory_type}") + return memory_id + + except Exception as e: + print(f"❌ 添加记忆失败: {e}") + raise + + async def search_memories(self, + agent_name: str, + query: str, + memory_type: str = None, + limit: int = 10) -> List[Dict[str, Any]]: + """ + 搜索智能体的相关记忆 + + Args: + agent_name: 智能体名称 + query: 搜索查询 + memory_type: 记忆类型过滤 + limit: 返回结果数量限制 + + Returns: + 相关记忆列表 + """ + if agent_name not in self.memory_banks: + return [] + + try: + # 使用本地存储搜索记忆 (临时方案) + if agent_name not in self.local_memories: + return [] + + memories = self.local_memories[agent_name] + results = [] + + # 简单的文本匹配搜索 + query_lower = query.lower() + + for memory in memories: + # 检查记忆类型过滤 + if memory_type and memory.get("memory_type") != memory_type: + continue + + # 检查内容匹配 + content_lower = memory["content"].lower() + debate_topic_lower = memory.get("debate_topic", "").lower() + + # 在内容或辩论主题中搜索 + if query_lower in content_lower or query_lower in debate_topic_lower: + # 计算简单的相关性分数 + content_matches = content_lower.count(query_lower) + topic_matches = debate_topic_lower.count(query_lower) + total_words = len(content_lower.split()) + len(debate_topic_lower.split()) + + relevance_score = (content_matches + topic_matches) / max(total_words, 1) + + results.append({ + "content": memory["content"], + "metadata": memory["metadata"], + "relevance_score": relevance_score + }) + + # 按相关性排序并限制结果数量 + results.sort(key=lambda x: x["relevance_score"], reverse=True) + return results[:limit] + + except Exception as e: + print(f"❌ 搜索记忆失败: {e}") + return [] + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: + """ + 获取智能体在特定辩论主题下的上下文记忆 + + Args: + agent_name: 智能体名称 + debate_topic: 辩论主题 + + Returns: + 格式化的上下文字符串 + """ + # 搜索相关记忆 + conversation_memories = await self.search_memories( + agent_name, debate_topic, "conversation", limit=5 + ) + preference_memories = await self.search_memories( + agent_name, debate_topic, "preference", limit=3 + ) + strategy_memories = await self.search_memories( + agent_name, debate_topic, "strategy", limit=3 + ) + + # 构建上下文 + context_parts = [] + + if conversation_memories: + context_parts.append("## 历史对话记忆") + for mem in conversation_memories: + context_parts.append(f"- {mem['content']}") + + if preference_memories: + context_parts.append("\n## 偏好记忆") + for mem in preference_memories: + context_parts.append(f"- {mem['content']}") + + if strategy_memories: + context_parts.append("\n## 策略记忆") + for mem in strategy_memories: + context_parts.append(f"- {mem['content']}") + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + if context_parts: + return f"# {chinese_name}的记忆上下文\n\n" + "\n".join(context_parts) + else: + return f"# {chinese_name}的记忆上下文\n\n暂无相关记忆。" + + async def save_debate_session(self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Dict[str, Any] = None) -> None: + """ + 保存完整的辩论会话到各参与者的记忆银行 + + Args: + debate_topic: 辩论主题 + participants: 参与者列表 + conversation_history: 对话历史 + outcomes: 辩论结果和洞察 + """ + for agent_name in participants: + if agent_name not in self.baxian_agents: + continue + + # 保存对话历史 + conversation_summary = self._summarize_conversation( + conversation_history, agent_name + ) + await self.add_memory( + agent_name=agent_name, + content=conversation_summary, + memory_type="conversation", + debate_topic=debate_topic, + metadata={ + "participants": participants, + "session_length": len(conversation_history) + } + ) + + # 保存策略洞察 + if outcomes: + strategy_insight = self._extract_strategy_insight( + outcomes, agent_name + ) + if strategy_insight: + await self.add_memory( + agent_name=agent_name, + content=strategy_insight, + memory_type="strategy", + debate_topic=debate_topic, + metadata={"session_outcome": outcomes} + ) + + def _summarize_conversation(self, + conversation_history: List[Dict[str, str]], + agent_name: str) -> str: + """ + 为特定智能体总结对话历史 + + Args: + conversation_history: 对话历史 + agent_name: 智能体名称 + + Returns: + 对话总结 + """ + agent_messages = [ + msg for msg in conversation_history + if msg.get("agent") == agent_name + ] + + if not agent_messages: + return "本次辩论中未发言" + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + summary = f"{chinese_name}在本次辩论中的主要观点:\n" + + for i, msg in enumerate(agent_messages[:3], 1): # 只取前3条主要观点 + summary += f"{i}. {msg.get('content', '')[:100]}...\n" + + return summary + + def _extract_strategy_insight(self, + outcomes: Dict[str, Any], + agent_name: str) -> Optional[str]: + """ + 从辩论结果中提取策略洞察 + + Args: + outcomes: 辩论结果 + agent_name: 智能体名称 + + Returns: + 策略洞察或None + """ + # 这里可以根据实际的outcomes结构来提取洞察 + # 暂时返回一个简单的示例 + chinese_name = self.baxian_agents.get(agent_name, agent_name) + + if "winner" in outcomes and outcomes["winner"] == agent_name: + return f"{chinese_name}在本次辩论中获胜,其论证策略值得保持。" + elif "insights" in outcomes and agent_name in outcomes["insights"]: + return outcomes["insights"][agent_name] + + return None + + +# 便捷函数 +async def initialize_baxian_memory_banks(project_id: str, location: str = "us-central1") -> VertexMemoryBank: + """ + 初始化所有八仙智能体的记忆银行 + + Args: + project_id: Google Cloud项目ID + location: 部署区域 + + Returns: + 配置好的VertexMemoryBank实例 + """ + memory_bank = VertexMemoryBank(project_id, location) + + print("🏛️ 正在为稷下学宫八仙创建记忆银行...") + + for agent_key, chinese_name in memory_bank.baxian_agents.items(): + try: + await memory_bank.create_memory_bank(agent_key) + except Exception as e: + print(f"⚠️ 创建 {chinese_name} 记忆银行时出错: {e}") + + print("✅ 八仙记忆银行初始化完成") + return memory_bank + + +if __name__ == "__main__": + import asyncio + + async def test_memory_bank(): + """测试Memory Bank功能""" + try: + # 从配置创建Memory Bank + memory_bank = VertexMemoryBank.from_config() + + # 测试创建记忆银行 + await memory_bank.create_memory_bank("tieguaili") + + # 测试添加记忆 + await memory_bank.add_memory( + agent_name="tieguaili", + content="在讨论NVIDIA股票时,我倾向于逆向思维,关注潜在风险。", + memory_type="preference", + debate_topic="NVIDIA投资分析" + ) + + # 测试搜索记忆 + results = await memory_bank.search_memories( + agent_name="tieguaili", + query="NVIDIA", + limit=5 + ) + + print(f"搜索结果: {len(results)} 条记忆") + for result in results: + print(f"- {result['content']}") + + except Exception as e: + print(f"❌ 测试失败: {e}") + + # 运行测试 + asyncio.run(test_memory_bank()) diff --git a/jixia_academy/core/debate_system/models/financial_data_models.py b/jixia_academy/core/debate_system/models/financial_data_models.py new file mode 100644 index 0000000..e4c53c2 --- /dev/null +++ b/jixia_academy/core/debate_system/models/financial_data_models.py @@ -0,0 +1,521 @@ +# 金融数据抽象层设计 + +## 概述 + +在"炼妖壶-稷下学宫AI辩论系统"中,我们需要构建一个统一的金融数据抽象层,以支持多种数据源(包括现有的RapidAPI永动机引擎、新增的OpenBB集成引擎,以及未来可能添加的其他数据提供商)。该抽象层将为上层AI智能体提供一致的数据接口,同时隐藏底层数据源的具体实现细节。 + +## 设计目标 + +1. **统一接口**:为所有金融数据访问提供一致的API +2. **可扩展性**:易于添加新的数据提供商 +3. **容错性**:当主数据源不可用时,能够自动切换到备用数据源 +4. **性能优化**:支持缓存和异步数据获取 +5. **类型安全**:使用Python类型注解确保数据结构的一致性 + +## 核心组件 + +### 1. 数据模型 (Data Models) + +定义标准化的金融数据结构: + +```python +# src/jixia/models/financial_data_models.py + +from dataclasses import dataclass +from typing import Optional, List +from datetime import datetime + +@dataclass +class StockQuote: + symbol: str + price: float + change: float + change_percent: float + volume: int + timestamp: datetime + +@dataclass +class HistoricalPrice: + date: datetime + open: float + high: float + low: float + close: float + volume: int + +@dataclass +class CompanyProfile: + symbol: str + name: str + industry: str + sector: str + market_cap: float + pe_ratio: Optional[float] + dividend_yield: Optional[float] + +@dataclass +class FinancialNews: + title: str + summary: str + url: str + timestamp: datetime + sentiment: Optional[float] # -1 (负面) to 1 (正面) +``` + +### 2. 抽象基类 (Abstract Base Class) + +定义数据提供商的通用接口: + +```python +# src/jixia/engines/data_abstraction.py + +from abc import ABC, abstractmethod +from typing import List, Optional +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews + +class DataProvider(ABC): + """金融数据提供商抽象基类""" + + @abstractmethod + def get_quote(self, symbol: str) -> Optional[StockQuote]: + """获取股票报价""" + pass + + @abstractmethod + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + """获取历史价格数据""" + pass + + @abstractmethod + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + """获取公司概况""" + pass + + @abstractmethod + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + """获取相关新闻""" + pass + + @property + @abstractmethod + def name(self) -> str: + """数据提供商名称""" + pass + + @property + @abstractmethod + def priority(self) -> int: + """优先级(数字越小优先级越高)""" + pass +``` + +### 3. Provider适配器 (Provider Adapters) + +为每个具体的数据源实现适配器: + +#### RapidAPI永动机引擎适配器 + +```python +# src/jixia/engines/rapidapi_adapter.py + +from typing import List, Optional +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine +from config.settings import get_rapidapi_key + +class RapidAPIDataProvider(DataProvider): + """RapidAPI永动机引擎适配器""" + + def __init__(self): + self.engine = JixiaPerpetualEngine(get_rapidapi_key()) + self._name = "RapidAPI" + self._priority = 2 # 中等优先级 + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + result = self.engine.get_immortal_data("吕洞宾", "quote", symbol) + if result.success and result.data: + # 解析RapidAPI返回的数据并转换为StockQuote + # 这里需要根据实际API返回的数据结构进行调整 + return StockQuote( + symbol=symbol, + price=result.data.get("price", 0), + change=result.data.get("change", 0), + change_percent=result.data.get("change_percent", 0), + volume=result.data.get("volume", 0), + timestamp=result.data.get("timestamp") + ) + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + # 实现历史价格数据获取逻辑 + pass + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + # 实现公司概况获取逻辑 + pass + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + # 实现新闻获取逻辑 + pass + + @property + def name(self) -> str: + return self._name + + @property + def priority(self) -> int: + return self._priority +``` + +#### OpenBB引擎适配器 + +```python +# src/jixia/engines/openbb_adapter.py + +from typing import List, Optional +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +from src.jixia.engines.openbb_engine import OpenBBEngine + +class OpenBBDataProvider(DataProvider): + """OpenBB引擎适配器""" + + def __init__(self): + self.engine = OpenBBEngine() + self._name = "OpenBB" + self._priority = 1 # 最高优先级 + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + result = self.engine.get_immortal_data("吕洞宾", "price", symbol) + if result.success and result.data: + # 解析OpenBB返回的数据并转换为StockQuote + return StockQuote( + symbol=symbol, + price=result.data.get("close", 0), + change=0, # 需要计算 + change_percent=0, # 需要计算 + volume=result.data.get("volume", 0), + timestamp=result.data.get("date") + ) + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + # 实现历史价格数据获取逻辑 + pass + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + # 实现公司概况获取逻辑 + pass + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + # 实现新闻获取逻辑 + pass + + @property + def name(self) -> str: + return self._name + + @property + def priority(self) -> int: + return self._priority +``` + +### 4. 数据抽象层管理器 (Data Abstraction Layer Manager) + +管理多个数据提供商并提供统一接口: + +```python +# src/jixia/engines/data_abstraction_layer.py + +from typing import List, Optional +from src.jixia.engines.data_abstraction import DataProvider +from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews +import asyncio + +class DataAbstractionLayer: + """金融数据抽象层管理器""" + + def __init__(self): + self.providers: List[DataProvider] = [] + self._initialize_providers() + + def _initialize_providers(self): + """初始化所有可用的数据提供商""" + # 根据配置和环境动态加载适配器 + try: + from src.jixia.engines.rapidapi_adapter import RapidAPIDataProvider + self.providers.append(RapidAPIDataProvider()) + except ImportError: + pass # RapidAPI引擎不可用 + + try: + from src.jixia.engines.openbb_adapter import OpenBBDataProvider + self.providers.append(OpenBBDataProvider()) + except ImportError: + pass # OpenBB引擎不可用 + + # 按优先级排序 + self.providers.sort(key=lambda p: p.priority) + + def get_quote(self, symbol: str) -> Optional[StockQuote]: + """获取股票报价(带故障转移)""" + for provider in self.providers: + try: + quote = provider.get_quote(symbol) + if quote: + return quote + except Exception as e: + print(f"警告: {provider.name} 获取报价失败: {e}") + continue + return None + + async def get_quote_async(self, symbol: str) -> Optional[StockQuote]: + """异步获取股票报价(带故障转移)""" + for provider in self.providers: + try: + # 如果提供商支持异步方法,则使用异步方法 + if hasattr(provider, 'get_quote_async'): + quote = await provider.get_quote_async(symbol) + else: + # 否则在执行器中运行同步方法 + quote = await asyncio.get_event_loop().run_in_executor( + None, provider.get_quote, symbol + ) + if quote: + return quote + except Exception as e: + print(f"警告: {provider.name} 获取报价失败: {e}") + continue + return None + + def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]: + """获取历史价格数据(带故障转移)""" + for provider in self.providers: + try: + prices = provider.get_historical_prices(symbol, days) + if prices: + return prices + except Exception as e: + print(f"警告: {provider.name} 获取历史价格失败: {e}") + continue + return [] + + def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]: + """获取公司概况(带故障转移)""" + for provider in self.providers: + try: + profile = provider.get_company_profile(symbol) + if profile: + return profile + except Exception as e: + print(f"警告: {provider.name} 获取公司概况失败: {e}") + continue + return None + + def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]: + """获取相关新闻(带故障转移)""" + for provider in self.providers: + try: + news = provider.get_news(symbol, limit) + if news: + return news + except Exception as e: + print(f"警告: {provider.name} 获取新闻失败: {e}") + continue + return [] +``` + +## 八仙与数据源的智能映射 + +```python +# src/jixia/engines/baxian_data_mapping.py + +# 设计八仙与数据源的智能映射 +immortal_data_mapping = { + '吕洞宾': { + 'specialty': 'technical_analysis', # 技术分析专家 + 'preferred_data_types': ['historical', 'price'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '何仙姑': { + 'specialty': 'risk_metrics', # 风险控制专家 + 'preferred_data_types': ['price', 'profile'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '张果老': { + 'specialty': 'historical_data', # 历史数据分析师 + 'preferred_data_types': ['historical'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '韩湘子': { + 'specialty': 'sector_analysis', # 新兴资产专家 + 'preferred_data_types': ['profile', 'news'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '汉钟离': { + 'specialty': 'market_movers', # 热点追踪 + 'preferred_data_types': ['news', 'price'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '蓝采和': { + 'specialty': 'value_discovery', # 潜力股发现 + 'preferred_data_types': ['screener', 'profile'], + 'data_providers': ['OpenBB', 'RapidAPI'] + }, + '铁拐李': { + 'specialty': 'contrarian_analysis', # 逆向思维专家 + 'preferred_data_types': ['profile', 'short_interest'], + 'data_providers': ['RapidAPI', 'OpenBB'] + }, + '曹国舅': { + 'specialty': 'macro_economics', # 宏观经济分析师 + 'preferred_data_types': ['profile', 'institutional_holdings'], + 'data_providers': ['OpenBB', 'RapidAPI'] + } +} +``` + +## 缓存策略 + +为了提高性能,我们将实现多级缓存策略: + +```python +# src/jixia/engines/data_cache.py + +import time +from typing import Any, Optional +from functools import lru_cache + +class DataCache: + """金融数据缓存""" + + def __init__(self): + self._cache = {} + self._cache_times = {} + self.default_ttl = 60 # 默认缓存时间(秒) + + def get(self, key: str) -> Optional[Any]: + """获取缓存数据""" + if key in self._cache: + # 检查是否过期 + if time.time() - self._cache_times[key] < self.default_ttl: + return self._cache[key] + else: + # 删除过期缓存 + del self._cache[key] + del self._cache_times[key] + return None + + def set(self, key: str, value: Any, ttl: Optional[int] = None): + """设置缓存数据""" + self._cache[key] = value + self._cache_times[key] = time.time() + if ttl: + # 可以为特定数据设置不同的TTL + pass # 实际实现中需要更复杂的TTL管理机制 + + @lru_cache(maxsize=128) + def get_quote_cache(self, symbol: str) -> Optional[Any]: + """LRU缓存装饰器示例""" + # 这个方法将自动缓存最近128个调用的结果 + pass +``` + +## 数据质量监控机制 + +为了确保数据的准确性和可靠性,我们将实现数据质量监控: + +```python +# src/jixia/engines/data_quality_monitor.py + +from typing import Dict, Any +from datetime import datetime + +class DataQualityMonitor: + """数据质量监控""" + + def __init__(self): + self.provider_stats = {} + + def record_access(self, provider_name: str, success: bool, response_time: float, data_size: int): + """记录数据访问统计""" + if provider_name not in self.provider_stats: + self.provider_stats[provider_name] = { + 'total_requests': 0, + 'successful_requests': 0, + 'failed_requests': 0, + 'total_response_time': 0, + 'total_data_size': 0, + 'last_access': None + } + + stats = self.provider_stats[provider_name] + stats['total_requests'] += 1 + if success: + stats['successful_requests'] += 1 + else: + stats['failed_requests'] += 1 + stats['total_response_time'] += response_time + stats['total_data_size'] += data_size + stats['last_access'] = datetime.now() + + def get_provider_health(self, provider_name: str) -> Dict[str, Any]: + """获取提供商健康状况""" + if provider_name not in self.provider_stats: + return {'status': 'unknown'} + + stats = self.provider_stats[provider_name] + success_rate = stats['successful_requests'] / stats['total_requests'] if stats['total_requests'] > 0 else 0 + avg_response_time = stats['total_response_time'] / stats['total_requests'] if stats['total_requests'] > 0 else 0 + + status = 'healthy' if success_rate > 0.95 and avg_response_time < 2.0 else 'degraded' if success_rate > 0.8 else 'unhealthy' + + return { + 'status': status, + 'success_rate': success_rate, + 'avg_response_time': avg_response_time, + 'total_requests': stats['total_requests'], + 'last_access': stats['last_access'] + } +``` + +## 使用示例 + +```python +# 示例:在智能体中使用数据抽象层 +from src.jixia.engines.data_abstraction_layer import DataAbstractionLayer +from src.jixia.models.financial_data_models import StockQuote + +# 初始化数据抽象层 +dal = DataAbstractionLayer() + +# 获取股票报价 +quote = dal.get_quote("AAPL") +if quote: + print(f"Apple股价: ${quote.price}") +else: + print("无法获取股价数据") + +# 异步获取报价 +import asyncio +async def async_example(): + quote = await dal.get_quote_async("GOOGL") + if quote: + print(f"Google股价: ${quote.price}") + +# asyncio.run(async_example()) +``` + +## 总结 + +这个金融数据抽象层设计提供了以下优势: + +1. **统一接口**:所有智能体都可以通过相同的接口访问任何数据源 +2. **故障转移**:当主数据源不可用时,自动切换到备用数据源 +3. **可扩展性**:可以轻松添加新的数据提供商适配器 +4. **性能优化**:通过缓存机制提高数据访问速度 +5. **质量监控**:实时监控各数据源的健康状况 +6. **文化融合**:通过八仙与数据源的智能映射,保持项目的文化特色 + +这将为"炼妖壶-稷下学宫AI辩论系统"提供一个强大、可靠且可扩展的金融数据基础。 \ No newline at end of file diff --git a/jixia_academy/core/debate_system/mongodb_swarm_example.py b/jixia_academy/core/debate_system/mongodb_swarm_example.py new file mode 100644 index 0000000..f839af9 --- /dev/null +++ b/jixia_academy/core/debate_system/mongodb_swarm_example.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +""" +MongoDB Swarm集成使用示例 + +这个示例展示了如何将MongoDB MCP服务器与Swarm框架集成使用。 +""" + +import asyncio +import json +from typing import Dict, Any, List +from datetime import datetime + +# 模拟Swarm框架(实际使用时导入真实的Swarm) +class MockSwarm: + def __init__(self): + self.agents = {} + + def add_agent(self, agent): + self.agents[agent.name] = agent + print(f"✅ 代理 '{agent.name}' 已添加到Swarm") + + async def run(self, agent_name: str, message: str) -> str: + if agent_name not in self.agents: + return f"❌ 代理 '{agent_name}' 不存在" + + agent = self.agents[agent_name] + print(f"🤖 代理 '{agent_name}' 正在处理: {message}") + + # 模拟代理处理逻辑 + if "查询" in message or "查找" in message: + return await agent.handle_query(message) + elif "插入" in message or "添加" in message: + return await agent.handle_insert(message) + elif "统计" in message: + return await agent.handle_stats(message) + else: + return f"📝 代理 '{agent_name}' 收到消息: {message}" + +class MockMongoDBAgent: + def __init__(self, name: str, mongodb_client): + self.name = name + self.mongodb_client = mongodb_client + self.functions = [ + "mongodb_query", + "mongodb_insert", + "mongodb_update", + "mongodb_delete", + "mongodb_stats", + "mongodb_collections" + ] + + async def handle_query(self, message: str) -> str: + try: + # 模拟查询操作 + result = await self.mongodb_client.query_documents( + collection="users", + filter_query={}, + limit=5 + ) + return f"📊 查询结果: 找到 {len(result.get('documents', []))} 条记录" + except Exception as e: + return f"❌ 查询失败: {str(e)}" + + async def handle_insert(self, message: str) -> str: + try: + # 模拟插入操作 + sample_doc = { + "name": "示例用户", + "email": "user@example.com", + "created_at": datetime.now().isoformat(), + "tags": ["swarm", "mongodb"] + } + result = await self.mongodb_client.insert_document( + collection="users", + document=sample_doc + ) + return f"✅ 插入成功: 文档ID {result.get('inserted_id', 'unknown')}" + except Exception as e: + return f"❌ 插入失败: {str(e)}" + + async def handle_stats(self, message: str) -> str: + try: + # 模拟统计操作 + result = await self.mongodb_client.get_database_stats() + return f"📈 数据库统计: {json.dumps(result, indent=2, ensure_ascii=False)}" + except Exception as e: + return f"❌ 获取统计失败: {str(e)}" + +# 模拟MongoDB MCP客户端 +class MockMongoDBClient: + def __init__(self, mcp_server_url: str, default_database: str): + self.mcp_server_url = mcp_server_url + self.default_database = default_database + self.connected = False + + async def connect(self) -> bool: + print(f"🔌 连接到MongoDB MCP服务器: {self.mcp_server_url}") + print(f"📁 默认数据库: {self.default_database}") + self.connected = True + return True + + async def query_documents(self, collection: str, filter_query: Dict, limit: int = 100) -> Dict[str, Any]: + if not self.connected: + raise Exception("未连接到MongoDB服务器") + + print(f"🔍 查询集合 '{collection}', 过滤条件: {filter_query}, 限制: {limit}") + # 模拟查询结果 + return { + "documents": [ + {"_id": "507f1f77bcf86cd799439011", "name": "用户1", "email": "user1@example.com"}, + {"_id": "507f1f77bcf86cd799439012", "name": "用户2", "email": "user2@example.com"}, + {"_id": "507f1f77bcf86cd799439013", "name": "用户3", "email": "user3@example.com"} + ], + "count": 3 + } + + async def insert_document(self, collection: str, document: Dict[str, Any]) -> Dict[str, Any]: + if not self.connected: + raise Exception("未连接到MongoDB服务器") + + print(f"📝 向集合 '{collection}' 插入文档: {json.dumps(document, ensure_ascii=False, indent=2)}") + # 模拟插入结果 + return { + "inserted_id": "507f1f77bcf86cd799439014", + "acknowledged": True + } + + async def get_database_stats(self) -> Dict[str, Any]: + if not self.connected: + raise Exception("未连接到MongoDB服务器") + + print(f"📊 获取数据库 '{self.default_database}' 统计信息") + # 模拟统计结果 + return { + "database": self.default_database, + "collections": 5, + "documents": 1250, + "avgObjSize": 512, + "dataSize": 640000, + "storageSize": 1024000, + "indexes": 8, + "indexSize": 32768 + } + + async def disconnect(self): + print("🔌 断开MongoDB MCP连接") + self.connected = False + +async def main(): + print("🚀 MongoDB Swarm集成示例") + print("=" * 50) + + # 1. 创建MongoDB MCP客户端 + print("\n📋 步骤1: 创建MongoDB MCP客户端") + mongodb_client = MockMongoDBClient( + mcp_server_url="http://localhost:8080", + default_database="swarm_data" + ) + + # 2. 连接到MongoDB + print("\n📋 步骤2: 连接到MongoDB") + await mongodb_client.connect() + + # 3. 创建Swarm实例 + print("\n📋 步骤3: 创建Swarm实例") + swarm = MockSwarm() + + # 4. 创建MongoDB代理 + print("\n📋 步骤4: 创建MongoDB代理") + mongodb_agent = MockMongoDBAgent("mongodb_agent", mongodb_client) + swarm.add_agent(mongodb_agent) + + # 5. 演示各种操作 + print("\n📋 步骤5: 演示MongoDB操作") + print("-" * 30) + + # 查询操作 + print("\n🔍 演示查询操作:") + result = await swarm.run("mongodb_agent", "查询所有用户数据") + print(f"结果: {result}") + + # 插入操作 + print("\n📝 演示插入操作:") + result = await swarm.run("mongodb_agent", "插入一个新用户") + print(f"结果: {result}") + + # 统计操作 + print("\n📊 演示统计操作:") + result = await swarm.run("mongodb_agent", "获取数据库统计信息") + print(f"结果: {result}") + + # 6. 清理资源 + print("\n📋 步骤6: 清理资源") + await mongodb_client.disconnect() + + print("\n✅ 示例完成!") + print("\n💡 实际使用说明:") + print("1. 启动MongoDB和MCP服务器: docker-compose up -d") + print("2. 使用真实的SwarmMongoDBClient替换MockMongoDBClient") + print("3. 导入真实的Swarm框架") + print("4. 根据需要配置代理的instructions和functions") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/ollama_swarm_mongodb_integration.py b/jixia_academy/core/debate_system/ollama_swarm_mongodb_integration.py new file mode 100644 index 0000000..9a32931 --- /dev/null +++ b/jixia_academy/core/debate_system/ollama_swarm_mongodb_integration.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Ollama Swarm + MongoDB RSS 集成示例 +展示如何使用基于 Ollama 的 Swarm 调用 MongoDB 中的 RSS 数据 +包含向量化搜索的实现方案 +""" + +import asyncio +import json +from datetime import datetime +from typing import Dict, List, Any, Optional +from swarm import Swarm, Agent +from openai import OpenAI + +# 导入 MongoDB MCP 客户端 +try: + from src.mcp.swarm_mongodb_client import SwarmMongoDBClient +except ImportError: + print("警告: 无法导入 SwarmMongoDBClient,将使用模拟客户端") + SwarmMongoDBClient = None + +class OllamaSwarmMongoDBIntegration: + """ + Ollama Swarm + MongoDB RSS 集成系统 + + 功能: + 1. 使用 Ollama 本地模型进行 AI 推理 + 2. 通过 MCP 连接 MongoDB 获取 RSS 数据 + 3. 支持向量化搜索(可选) + 4. 四仙辩论系统集成 + """ + + def __init__(self): + # Ollama 配置 + self.ollama_base_url = "http://100.99.183.38:11434" + self.model_name = "qwen3:8b" # 使用支持工具调用的模型 + # 初始化 OpenAI 客户端(连接到 Ollama) + self.openai_client = OpenAI( + api_key="ollama", # Ollama 不需要真实 API 密钥 + base_url=f"{self.ollama_base_url}/v1" + ) + + # 初始化 Swarm + self.swarm = Swarm(client=self.openai_client) + + # 初始化 MongoDB 客户端 + self.mongodb_client = None + self.init_mongodb_client() + + # 创建代理 + self.agents = self.create_agents() + + print(f"🦙 Ollama 服务: {self.ollama_base_url}") + print(f"🤖 使用模型: {self.model_name}") + print(f"📊 MongoDB 连接: {'已连接' if self.mongodb_client else '未连接'}") + + def init_mongodb_client(self): + """初始化 MongoDB 客户端""" + try: + if SwarmMongoDBClient: + self.mongodb_client = SwarmMongoDBClient( + mcp_server_url="http://localhost:8080", + default_database="taigong" + ) + # 连接到数据库 + result = self.mongodb_client.connect("taigong") + if result.get("success"): + print("✅ MongoDB MCP 连接成功") + else: + print(f"❌ MongoDB MCP 连接失败: {result.get('error')}") + self.mongodb_client = None + else: + print("⚠️ 使用模拟 MongoDB 客户端") + self.mongodb_client = MockMongoDBClient() + except Exception as e: + print(f"❌ MongoDB 初始化失败: {e}") + self.mongodb_client = MockMongoDBClient() + + def get_rss_articles(self, query: Optional[str] = None, limit: int = 10) -> List[Dict]: + """获取 RSS 文章数据""" + if not self.mongodb_client: + return [] + + try: + # 构建查询条件 + filter_query = {} + if query: + # 简单的文本搜索 + filter_query = { + "$or": [ + {"title": {"$regex": query, "$options": "i"}}, + {"description": {"$regex": query, "$options": "i"}} + ] + } + + # 查询文档 + result = self.mongodb_client.find_documents( + collection_name="articles", + query=filter_query, + limit=limit, + sort={"published_time": -1} # 按发布时间倒序 + ) + + if result.get("success"): + return result.get("documents", []) + else: + print(f"查询失败: {result.get('error')}") + return [] + + except Exception as e: + print(f"获取 RSS 文章失败: {e}") + return [] + + def create_agents(self) -> Dict[str, Agent]: + """创建四仙代理""" + + def get_rss_news(query: str = "", limit: int = 5) -> str: + """获取 RSS 新闻的工具函数""" + articles = self.get_rss_articles(query, limit) + if not articles: + return "未找到相关新闻文章" + + result = f"找到 {len(articles)} 篇相关文章:\n\n" + for i, article in enumerate(articles, 1): + title = article.get('title', '无标题') + published = article.get('published_time', '未知时间') + result += f"{i}. {title}\n 发布时间: {published}\n\n" + + return result + + def analyze_market_sentiment(topic: str) -> str: + """分析市场情绪的工具函数""" + articles = self.get_rss_articles(topic, 10) + if not articles: + return f"未找到关于 '{topic}' 的相关新闻" + + # 简单的情绪分析(实际应用中可以使用更复杂的 NLP 模型) + positive_keywords = ['上涨', '增长', '利好', '突破', '创新高'] + negative_keywords = ['下跌', '下降', '利空', '暴跌', '风险'] + + positive_count = 0 + negative_count = 0 + + for article in articles: + title = article.get('title', '').lower() + for keyword in positive_keywords: + if keyword in title: + positive_count += 1 + for keyword in negative_keywords: + if keyword in title: + negative_count += 1 + + sentiment = "中性" + if positive_count > negative_count: + sentiment = "偏乐观" + elif negative_count > positive_count: + sentiment = "偏悲观" + + return f"基于 {len(articles)} 篇新闻分析,'{topic}' 的市场情绪: {sentiment}\n" \ + f"正面信号: {positive_count}, 负面信号: {negative_count}" + + # 创建四仙代理 + agents = { + "吕洞宾": Agent( + name="吕洞宾", + model=self.model_name, + instructions=""" + 你是吕洞宾,技术分析专家。 + - 专长:技术分析和图表解读 + - 性格:犀利直接,一剑封喉 + - 立场:偏向积极乐观 + - 使用 get_rss_news 获取最新财经新闻 + - 使用 analyze_market_sentiment 分析市场情绪 + """, + functions=[get_rss_news, analyze_market_sentiment] + ), + + "何仙姑": Agent( + name="何仙姑", + model=self.model_name, + instructions=""" + 你是何仙姑,风险控制专家。 + - 专长:风险评估和资金管理 + - 性格:温和坚定,关注风险 + - 立场:偏向谨慎保守 + - 使用 get_rss_news 获取风险相关新闻 + - 使用 analyze_market_sentiment 评估市场风险 + """, + functions=[get_rss_news, analyze_market_sentiment] + ), + + "张果老": Agent( + name="张果老", + model=self.model_name, + instructions=""" + 你是张果老,历史数据分析师。 + - 专长:历史数据分析和趋势预测 + - 性格:博学深沉,引经据典 + - 立场:基于历史数据的客观分析 + - 使用 get_rss_news 获取历史相关新闻 + - 使用 analyze_market_sentiment 分析长期趋势 + """, + functions=[get_rss_news, analyze_market_sentiment] + ), + + "铁拐李": Agent( + name="铁拐李", + model=self.model_name, + instructions=""" + 你是铁拐李,逆向思维大师。 + - 专长:逆向思维和另类观点 + - 性格:特立独行,敢于质疑 + - 立场:挑战主流观点 + - 使用 get_rss_news 寻找被忽视的信息 + - 使用 analyze_market_sentiment 提出反向观点 + """, + functions=[get_rss_news, analyze_market_sentiment] + ) + } + + return agents + + async def start_debate(self, topic: str, rounds: int = 3) -> Dict[str, Any]: + """开始四仙辩论""" + print(f"\n🎭 开始四仙辩论: {topic}") + print("=" * 50) + + debate_history = [] + + # 获取相关新闻作为背景 + background_articles = self.get_rss_articles(topic, 5) + background_info = "\n".join([f"- {article.get('title', '')}" for article in background_articles]) + + agent_names = list(self.agents.keys()) + + for round_num in range(rounds): + print(f"\n📢 第 {round_num + 1} 轮辩论") + print("-" * 30) + + for agent_name in agent_names: + agent = self.agents[agent_name] + + # 构建消息 + if round_num == 0: + message = f"""请基于以下背景信息对 '{topic}' 发表你的观点: + +背景新闻: +{background_info} + +请使用你的专业工具获取更多信息并给出分析。""" + else: + # 后续轮次包含之前的辩论历史 + history_summary = "\n".join([f"{h['agent']}: {h['response'][:100]}..." for h in debate_history[-3:]]) + message = f"""基于之前的辩论内容,请继续阐述你对 '{topic}' 的观点: + +之前的观点: +{history_summary} + +请使用工具获取最新信息并回应其他仙友的观点。""" + + try: + # 调用代理 + response = self.swarm.run( + agent=agent, + messages=[{"role": "user", "content": message}] + ) + + agent_response = response.messages[-1]["content"] + + print(f"\n{agent_name}: {agent_response}") + + debate_history.append({ + "round": round_num + 1, + "agent": agent_name, + "response": agent_response, + "timestamp": datetime.now().isoformat() + }) + + except Exception as e: + print(f"❌ {agent_name} 发言失败: {e}") + continue + + return { + "topic": topic, + "rounds": rounds, + "debate_history": debate_history, + "background_articles": background_articles + } + + def get_vector_search_recommendation(self) -> str: + """获取向量化搜索的建议""" + return """ +🔍 向量化搜索建议: + +当前 RSS 数据结构: +- _id: ObjectId +- title: String +- published_time: String + +向量化增强方案: + +1. 数据预处理: + - 提取文章摘要/描述字段 + - 清理和标准化文本内容 + - 添加分类标签 + +2. 向量化实现: + - 使用 Ollama 本地嵌入模型(如 nomic-embed-text) + - 为每篇文章生成 768 维向量 + - 存储向量到 MongoDB 的 vector 字段 + +3. 索引创建: + ```javascript + db.articles.createIndex( + { "vector": "2dsphere" }, + { "name": "vector_index" } + ) + ``` + +4. 语义搜索: + - 将用户查询转换为向量 + - 使用 $vectorSearch 进行相似度搜索 + - 结合传统关键词搜索提高准确性 + +5. Swarm 集成: + - 为代理添加语义搜索工具 + - 支持概念级别的新闻检索 + - 提高辩论质量和相关性 + +实施优先级: +1. 先完善基础文本搜索 +2. 添加文章摘要字段 +3. 集成 Ollama 嵌入模型 +4. 实现向量搜索功能 + """ + +class MockMongoDBClient: + """模拟 MongoDB 客户端(用于测试)""" + + def __init__(self): + self.mock_articles = [ + { + "_id": "mock_1", + "title": "滨江服务,还能涨价的物业", + "published_time": "2025-06-13T04:58:00.000Z", + "description": "房地产市场分析" + }, + { + "_id": "mock_2", + "title": "中国汽车行业在内卷什么?", + "published_time": "2025-06-11T05:07:00.000Z", + "description": "汽车行业竞争分析" + } + ] + + def find_documents(self, collection_name: str, query: Optional[Dict] = None, + limit: int = 100, **kwargs) -> Dict[str, Any]: + """模拟文档查询""" + return { + "success": True, + "documents": self.mock_articles[:limit] + } + + def connect(self, database_name: str) -> Dict[str, Any]: + """模拟连接""" + return {"success": True} + +async def main(): + """主函数""" + # 创建集成系统 + system = OllamaSwarmMongoDBIntegration() + + # 显示向量化建议 + print(system.get_vector_search_recommendation()) + + # 测试 RSS 数据获取 + print("\n📰 测试 RSS 数据获取:") + articles = system.get_rss_articles(limit=3) + for i, article in enumerate(articles, 1): + print(f"{i}. {article.get('title', '无标题')}") + + # 开始辩论(可选) + user_input = input("\n是否开始辩论?(y/n): ") + if user_input.lower() == 'y': + topic = input("请输入辩论主题(默认:房地产市场): ") or "房地产市场" + result = await system.start_debate(topic, rounds=2) + + print("\n📊 辩论总结:") + print(f"主题: {result['topic']}") + print(f"轮次: {result['rounds']}") + print(f"发言次数: {len(result['debate_history'])}") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/jixia_academy/core/debate_system/yijing_debate_system.md b/jixia_academy/core/debate_system/yijing_debate_system.md new file mode 100644 index 0000000..82746ee --- /dev/null +++ b/jixia_academy/core/debate_system/yijing_debate_system.md @@ -0,0 +1,231 @@ +# 🔮 太公心易辩论系统 + +> *"以自己的体,看待其他人的用,组合为六十四卦"* + +## ⚡ 易经辩论架构重设计 + +### 🎯 核心理念修正 + +之前的设计错误地将八仙按"资产类别"分工,这违背了易经的本质。真正的太公心易应该是: + +**不是专业分工,而是观察视角的变化!** + +## 🌊 先天八卦 - 八仙布局 + +### 阴阳鱼排列 +``` + 乾☰ 吕洞宾 (老父) + 兑☱ 钟汉离 巽☴ 蓝采和 (长女) +震☳ 铁拐李 坤☷ 何仙姑 (老母) + 艮☶ 曹国舅 坎☵ 张果老 (中男) + 离☲ 韩湘子 (中女) +``` + +### 对立统一关系 + +#### 🔥 乾坤对立 - 根本观点相反 +- **吕洞宾** (乾☰): 阳刚进取,天生看多 + - *"以剑仙之名发誓,这个市场充满机会!"* +- **何仙姑** (坤☷): 阴柔谨慎,天生看空 + - *"作为唯一的女仙,我更关注风险和保护。"* + +**辩论特点**: 根本性观点对立,永远无法达成一致 + +#### ⚡ 震巽对立 - 行动vs思考 +- **铁拐李** (震☳): 雷厉风行,立即行动 + - *"机会稍纵即逝,现在就要下手!"* +- **蓝采和** (巽☴): 深思熟虑,缓慢布局 + - *"让我们再观察一下,不要急于决定。"* + +#### 💧 坎离对立 - 理性vs感性 +- **张果老** (坎☵): 纯理性,数据驱动 + - *"倒骑驴看市场,数据不会说谎。"* +- **韩湘子** (离☲): 重直觉,情感判断 + - *"我的音律告诉我,市场的情绪在变化。"* + +#### 🏔️ 艮兑对立 - 保守vs激进 +- **曹国舅** (艮☶): 稳重保守,风险厌恶 + - *"稳健是王道,不要冒不必要的风险。"* +- **钟汉离** (兑☱): 激进创新,高风险偏好 + - *"不入虎穴,焉得虎子!创新需要勇气。"* + +## 🎭 三清八仙层级关系 + +### 三清 = Overlay (天层) +```python +class SanQing: + """三清天尊 - 上层决策""" + + hierarchy_level = "OVERLAY" + speaking_privilege = "ABSOLUTE" # 发言时八仙必须静听 + + def speak(self): + # 三清发言时,八仙进入静听模式 + for baxian in self.baxian_agents: + baxian.set_mode("LISTEN_ONLY") +``` + +#### 太上老君 - 最高决策者 +- **职责**: 综合八仙观点,做出最终决策 +- **特权**: 可以否决任何八仙的建议 +- **风格**: 高屋建瓴,统揽全局 + +#### 元始天尊 - 技术支撑 +- **职责**: 提供技术分析和数据支撑 +- **特权**: 可以要求八仙提供具体数据 +- **风格**: 精准理性,技术权威 + +#### 通天教主 - 情绪导师 +- **职责**: 分析市场情绪和群体心理 +- **特权**: 可以调节八仙的辩论情绪 +- **风格**: 洞察人心,情绪敏感 + +### 八仙 = Underlay (地层) +```python +class BaXian: + """八仙过海 - 底层辩论""" + + hierarchy_level = "UNDERLAY" + speaking_privilege = "PEER" # 平辈关系,可以争论 + + def debate_with_peer(self, other_baxian): + # 八仙之间可以激烈争论 + if self.is_opposite(other_baxian): + return self.argue_intensely(other_baxian) + else: + return self.discuss_peacefully(other_baxian) +``` + +## 🔄 辩论流程重设计 + +### Phase 1: 八仙平辈辩论 +```python +async def baxian_peer_debate(topic: str): + """八仙平辈辩论阶段""" + + # 1. 对立卦位激烈争论 + qian_kun_debate = await debate_between(lu_dongbin, he_xiangu) # 乾坤对立 + zhen_xun_debate = await debate_between(tiegua_li, lan_caihe) # 震巽对立 + kan_li_debate = await debate_between(zhang_guolao, han_xiangzi) # 坎离对立 + gen_dui_debate = await debate_between(cao_guojiu, zhong_hanli) # 艮兑对立 + + # 2. 相邻卦位温和讨论 + adjacent_discussions = await discuss_adjacent_positions() + + return { + "intense_debates": [qian_kun_debate, zhen_xun_debate, kan_li_debate, gen_dui_debate], + "mild_discussions": adjacent_discussions + } +``` + +### Phase 2: 三清裁决 +```python +async def sanqing_overlay_decision(baxian_debates: Dict): + """三清上层裁决阶段""" + + # 八仙必须静听 + for baxian in all_baxian: + baxian.set_mode("SILENT_LISTEN") + + # 元始天尊技术分析 + technical_analysis = await yuanshi_tianzun.analyze_data(baxian_debates) + + # 通天教主情绪分析 + sentiment_analysis = await tongtian_jiaozhu.analyze_emotions(baxian_debates) + + # 太上老君最终决策 + final_decision = await taishang_laojun.make_decision( + technical_analysis, + sentiment_analysis, + baxian_debates + ) + + return final_decision +``` + +## 🎯 投资标的全覆盖 + +### 不按资产类别分工,按观察角度分工 + +#### 任何投资标的都可以从八个角度观察: + +**股票、期货、外汇、加密货币、另类资产...** + +- **乾 (吕洞宾)**: 看多角度 - "这个标的有上涨潜力" +- **坤 (何仙姑)**: 看空角度 - "这个标的风险很大" +- **震 (铁拐李)**: 行动角度 - "现在就要买入/卖出" +- **巽 (蓝采和)**: 等待角度 - "再观察一段时间" +- **坎 (张果老)**: 数据角度 - "技术指标显示..." +- **离 (韩湘子)**: 直觉角度 - "我感觉市场情绪..." +- **艮 (曹国舅)**: 保守角度 - "风险控制最重要" +- **兑 (钟汉离)**: 激进角度 - "高风险高收益" + +## 🔮 六十四卦生成机制 + +### 体用关系 +```python +def generate_64_gua_analysis(target_asset: str): + """生成六十四卦分析""" + + analyses = {} + + for observer in baxian: # 8个观察者 (体) + for observed in baxian: # 8个被观察者 (用) + if observer != observed: + gua_name = f"{observer.trigram}{observed.trigram}" + + analysis = observer.analyze_from_perspective( + target_asset, + observed.viewpoint + ) + + analyses[gua_name] = analysis + + return analyses # 8x8 = 64种分析角度 +``` + +### 实际应用示例 +```python +# 分析比特币 +bitcoin_analysis = { + "乾乾": "吕洞宾看吕洞宾的比特币观点", # 自我强化 + "乾坤": "吕洞宾看何仙姑的比特币观点", # 多空对立 + "乾震": "吕洞宾看铁拐李的比特币观点", # 看多+行动 + # ... 64种组合 +} +``` + +## ⚖️ 辩论规则重定义 + +### 八仙辩论规则 +1. **对立卦位**: 必须激烈争论,观点相反 +2. **相邻卦位**: 可以温和讨论,观点相近 +3. **平辈关系**: 无上下级,可以互相质疑 +4. **轮流发言**: 按先天八卦顺序发言 + +### 三清介入规则 +1. **绝对权威**: 三清发言时,八仙必须静听 +2. **技术支撑**: 元始天尊提供数据分析 +3. **情绪调节**: 通天教主控制辩论节奏 +4. **最终裁决**: 太上老君综合决策 + +## 🎉 重设计的优势 + +### ✅ 符合易经本质 +- 体现了"体用关系"的核心思想 +- 遵循先天八卦的阴阳对立 +- 实现了"男女老少皆可成仙"的理念 + +### ✅ 投资标的全覆盖 +- 不局限于特定资产类别 +- 任何投资标的都可以从8个角度分析 +- 生成64种不同的分析视角 + +### ✅ 辩论更加真实 +- 对立观点的激烈争论 +- 层级关系的权威体现 +- 符合中华文化的等级秩序 + +--- + +**🔮 这才是真正的太公心易!以易经智慧指导AI投资分析!** \ No newline at end of file diff --git a/jixia_academy/core/main.py b/jixia_academy/core/main.py new file mode 100644 index 0000000..9541fe1 --- /dev/null +++ b/jixia_academy/core/main.py @@ -0,0 +1,249 @@ +#!/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() \ No newline at end of file diff --git a/jixia_academy/core/memory_bank/base_memory_bank.py b/jixia_academy/core/memory_bank/base_memory_bank.py new file mode 100644 index 0000000..9244514 --- /dev/null +++ b/jixia_academy/core/memory_bank/base_memory_bank.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +通用记忆银行抽象,便于插入不同后端(Vertex、Cloudflare AutoRAG等) +""" +from __future__ import annotations + +from typing import Dict, List, Any, Optional, Protocol, runtime_checkable + + +@runtime_checkable +class MemoryBankProtocol(Protocol): + async def create_memory_bank(self, agent_name: str, display_name: Optional[str] = None) -> str: ... + + async def add_memory( + self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Optional[Dict[str, Any]] = None, + ) -> str: ... + + async def search_memories( + self, + agent_name: str, + query: str, + memory_type: Optional[str] = None, + limit: int = 10, + ) -> List[Dict[str, Any]]: ... + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: ... + + async def save_debate_session( + self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Optional[Dict[str, Any]] = None, + ) -> None: ... diff --git a/jixia_academy/core/memory_bank/cloudflare_memory_bank.py b/jixia_academy/core/memory_bank/cloudflare_memory_bank.py new file mode 100644 index 0000000..2c284c6 --- /dev/null +++ b/jixia_academy/core/memory_bank/cloudflare_memory_bank.py @@ -0,0 +1,454 @@ +#!/usr/bin/env python3 +""" +Cloudflare AutoRAG Vectorize 记忆银行实现 +为稷下学宫AI辩论系统提供Cloudflare后端的记忆功能 +""" + +import os +import json +from typing import Dict, List, Optional, Any +from dataclasses import dataclass +from datetime import datetime +import aiohttp +from config.settings import get_cloudflare_config + + +@dataclass +class MemoryEntry: + """记忆条目数据结构""" + id: str + content: str + metadata: Dict[str, Any] + timestamp: str # ISO format string + agent_name: str + debate_topic: str + memory_type: str # "conversation", "preference", "knowledge", "strategy" + + +class CloudflareMemoryBank: + """ + Cloudflare AutoRAG Vectorize 记忆银行管理器 + 利用Cloudflare Vectorize索引和Workers AI进行向量检索增强生成 + """ + + def __init__(self): + """初始化Cloudflare Memory Bank""" + self.config = get_cloudflare_config() + self.account_id = self.config['account_id'] + self.api_token = self.config['api_token'] + self.vectorize_index = self.config['vectorize_index'] + self.embed_model = self.config['embed_model'] + self.autorag_domain = self.config['autorag_domain'] + + # 构建API基础URL + self.base_url = f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}" + self.headers = { + "Authorization": f"Bearer {self.api_token}", + "Content-Type": "application/json" + } + + # 八仙智能体名称映射 + self.baxian_agents = { + "tieguaili": "铁拐李", + "hanzhongli": "汉钟离", + "zhangguolao": "张果老", + "lancaihe": "蓝采和", + "hexiangu": "何仙姑", + "lvdongbin": "吕洞宾", + "hanxiangzi": "韩湘子", + "caoguojiu": "曹国舅" + } + + async def _get_session(self) -> aiohttp.ClientSession: + """获取aiohttp会话""" + return aiohttp.ClientSession() + + async def create_memory_bank(self, agent_name: str, display_name: str = None) -> str: + """ + 为指定智能体创建记忆空间(在Cloudflare中通过命名空间或元数据实现) + + Args: + agent_name: 智能体名称 (如 "tieguaili") + display_name: 显示名称 (如 "铁拐李的记忆银行") + + Returns: + 记忆空间标识符 (这里用agent_name作为标识符) + """ + # Cloudflare Vectorize使用统一的索引,通过元数据区分不同智能体的记忆 + # 所以这里不需要实际创建,只需要返回标识符 + if not display_name: + display_name = self.baxian_agents.get(agent_name, agent_name) + + print(f"✅ 为 {display_name} 准备Cloudflare记忆空间") + return f"cf_memory_{agent_name}" + + async def add_memory(self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Dict[str, Any] = None) -> str: + """ + 添加记忆到Cloudflare Vectorize索引 + + Args: + agent_name: 智能体名称 + content: 记忆内容 + memory_type: 记忆类型 ("conversation", "preference", "knowledge", "strategy") + debate_topic: 辩论主题 + metadata: 额外元数据 + + Returns: + 记忆ID + """ + if metadata is None: + metadata = {} + + # 生成记忆ID + memory_id = f"mem_{agent_name}_{int(datetime.now().timestamp() * 1000000)}" + + # 构建记忆条目 + memory_entry = MemoryEntry( + id=memory_id, + content=content, + metadata={ + **metadata, + "agent_name": agent_name, + "chinese_name": self.baxian_agents.get(agent_name, agent_name), + "memory_type": memory_type, + "debate_topic": debate_topic, + "system": "jixia_academy" + }, + timestamp=datetime.now().isoformat(), + agent_name=agent_name, + debate_topic=debate_topic, + memory_type=memory_type + ) + + # 将记忆条目转换为JSON字符串,用于存储和检索 + memory_data = { + "id": memory_id, + "values": [], # 向量值将在嵌入时填充 + "metadata": memory_entry.metadata + } + + try: + # 1. 使用Workers AI生成嵌入向量 + embedding = await self._generate_embedding(content) + memory_data["values"] = embedding + + # 2. 将记忆插入Vectorize索引 + async with await self._get_session() as session: + url = f"{self.base_url}/vectorize/indexes/{self.vectorize_index}/upsert" + payload = { + "vectors": [memory_data] + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + print(f"✅ 为 {self.baxian_agents.get(agent_name)} 添加记忆: {memory_type}") + return memory_id + else: + error_text = await response.text() + raise Exception(f"Failed to upsert memory: {response.status} - {error_text}") + + except Exception as e: + print(f"❌ 添加记忆失败: {e}") + raise + + async def _generate_embedding(self, text: str) -> List[float]: + """ + 使用Cloudflare Workers AI生成文本嵌入 + + Args: + text: 要嵌入的文本 + + Returns: + 嵌入向量 + """ + async with await self._get_session() as session: + url = f"{self.base_url}/ai/run/{self.embed_model}" + payload = { + "text": [text] # Workers AI embeddings API expects a list of texts + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + # 提取嵌入向量 (通常是 result["result"]["data"][0]["embedding"]) + if "result" in result and "data" in result["result"] and len(result["result"]["data"]) > 0: + return result["result"]["data"][0]["embedding"] + else: + raise Exception(f"Unexpected embedding response format: {result}") + else: + error_text = await response.text() + raise Exception(f"Failed to generate embedding: {response.status} - {error_text}") + + async def search_memories(self, + agent_name: str, + query: str, + memory_type: str = None, + limit: int = 10) -> List[Dict[str, Any]]: + """ + 使用向量相似性搜索智能体的相关记忆 + + Args: + agent_name: 智能体名称 + query: 搜索查询 + memory_type: 记忆类型过滤 + limit: 返回结果数量限制 + + Returns: + 相关记忆列表 + """ + try: + # 1. 为查询生成嵌入向量 + query_embedding = await self._generate_embedding(query) + + # 2. 构建过滤条件 + filters = { + "agent_name": agent_name + } + if memory_type: + filters["memory_type"] = memory_type + + # 3. 执行向量搜索 + async with await self._get_session() as session: + url = f"{self.base_url}/vectorize/indexes/{self.vectorize_index}/query" + payload = { + "vector": query_embedding, + "topK": limit, + "filter": filters, + "returnMetadata": True + } + + async with session.post(url, headers=self.headers, json=payload) as response: + if response.status == 200: + result = await response.json() + matches = result.get("result", {}).get("matches", []) + + # 格式化返回结果 + memories = [] + for match in matches: + memory_data = { + "content": match["metadata"].get("content", ""), + "metadata": match["metadata"], + "relevance_score": match["score"] + } + memories.append(memory_data) + + return memories + else: + error_text = await response.text() + raise Exception(f"Failed to search memories: {response.status} - {error_text}") + + except Exception as e: + print(f"❌ 搜索记忆失败: {e}") + return [] + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: + """ + 获取智能体在特定辩论主题下的上下文记忆 + + Args: + agent_name: 智能体名称 + debate_topic: 辩论主题 + + Returns: + 格式化的上下文字符串 + """ + # 搜索相关记忆 + conversation_memories = await self.search_memories( + agent_name, debate_topic, "conversation", limit=5 + ) + preference_memories = await self.search_memories( + agent_name, debate_topic, "preference", limit=3 + ) + strategy_memories = await self.search_memories( + agent_name, debate_topic, "strategy", limit=3 + ) + + # 构建上下文 + context_parts = [] + + if conversation_memories: + context_parts.append("## 历史对话记忆") + for mem in conversation_memories: + context_parts.append(f"- {mem['content']}") + + if preference_memories: + context_parts.append("\n## 偏好记忆") + for mem in preference_memories: + context_parts.append(f"- {mem['content']}") + + if strategy_memories: + context_parts.append("\n## 策略记忆") + for mem in strategy_memories: + context_parts.append(f"- {mem['content']}") + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + if context_parts: + return f"# {chinese_name}的记忆上下文\n\n" + "\n".join(context_parts) + else: + return f"# {chinese_name}的记忆上下文\n\n暂无相关记忆。" + + async def save_debate_session(self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Dict[str, Any] = None) -> None: + """ + 保存完整的辩论会话到各参与者的记忆银行 + + Args: + debate_topic: 辩论主题 + participants: 参与者列表 + conversation_history: 对话历史 + outcomes: 辩论结果和洞察 + """ + for agent_name in participants: + if agent_name not in self.baxian_agents: + continue + + # 保存对话历史 + conversation_summary = self._summarize_conversation( + conversation_history, agent_name + ) + await self.add_memory( + agent_name=agent_name, + content=conversation_summary, + memory_type="conversation", + debate_topic=debate_topic, + metadata={ + "participants": participants, + "session_length": len(conversation_history) + } + ) + + # 保存策略洞察 + if outcomes: + strategy_insight = self._extract_strategy_insight( + outcomes, agent_name + ) + if strategy_insight: + await self.add_memory( + agent_name=agent_name, + content=strategy_insight, + memory_type="strategy", + debate_topic=debate_topic, + metadata={"session_outcome": outcomes} + ) + + def _summarize_conversation(self, + conversation_history: List[Dict[str, str]], + agent_name: str) -> str: + """ + 为特定智能体总结对话历史 + + Args: + conversation_history: 对话历史 + agent_name: 智能体名称 + + Returns: + 对话总结 + """ + agent_messages = [ + msg for msg in conversation_history + if msg.get("agent") == agent_name + ] + + if not agent_messages: + return "本次辩论中未发言" + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + summary = f"{chinese_name}在本次辩论中的主要观点:\n" + + for i, msg in enumerate(agent_messages[:3], 1): # 只取前3条主要观点 + summary += f"{i}. {msg.get('content', '')[:100]}...\n" + + return summary + + def _extract_strategy_insight(self, + outcomes: Dict[str, Any], + agent_name: str) -> Optional[str]: + """ + 从辩论结果中提取策略洞察 + + Args: + outcomes: 辩论结果 + agent_name: 智能体名称 + + Returns: + 策略洞察或None + """ + # 这里可以根据实际的outcomes结构来提取洞察 + # 暂时返回一个简单的示例 + chinese_name = self.baxian_agents.get(agent_name, agent_name) + + if "winner" in outcomes and outcomes["winner"] == agent_name: + return f"{chinese_name}在本次辩论中获胜,其论证策略值得保持。" + elif "insights" in outcomes and agent_name in outcomes["insights"]: + return outcomes["insights"][agent_name] + + return None + + +# 便捷函数 +async def initialize_baxian_memory_banks() -> CloudflareMemoryBank: + """ + 初始化所有八仙智能体的Cloudflare记忆空间 + + Returns: + 配置好的CloudflareMemoryBank实例 + """ + memory_bank = CloudflareMemoryBank() + + print("🏛️ 正在为稷下学宫八仙创建Cloudflare记忆空间...") + + for agent_key, chinese_name in memory_bank.baxian_agents.items(): + try: + await memory_bank.create_memory_bank(agent_key) + except Exception as e: + print(f"⚠️ 创建 {chinese_name} 记忆空间时出错: {e}") + + print("✅ 八仙Cloudflare记忆空间初始化完成") + return memory_bank + + +if __name__ == "__main__": + import asyncio + + async def test_memory_bank(): + """测试Cloudflare Memory Bank功能""" + try: + # 创建Memory Bank实例 + memory_bank = CloudflareMemoryBank() + + # 测试创建记忆空间 + await memory_bank.create_memory_bank("tieguaili") + + # 测试添加记忆 + await memory_bank.add_memory( + agent_name="tieguaili", + content="在讨论NVIDIA股票时,我倾向于逆向思维,关注潜在风险。", + memory_type="preference", + debate_topic="NVIDIA投资分析" + ) + + # 测试搜索记忆 + results = await memory_bank.search_memories( + agent_name="tieguaili", + query="NVIDIA", + limit=5 + ) + + print(f"搜索结果: {len(results)} 条记忆") + for result in results: + print(f"- {result['content']}") + + except Exception as e: + print(f"❌ 测试失败: {e}") + + # 运行测试 + asyncio.run(test_memory_bank()) \ No newline at end of file diff --git a/jixia_academy/core/memory_bank/factory.py b/jixia_academy/core/memory_bank/factory.py new file mode 100644 index 0000000..98daf98 --- /dev/null +++ b/jixia_academy/core/memory_bank/factory.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +记忆银行工厂:根据配置创建不同后端实现 +""" +from __future__ import annotations + +import os +from typing import Optional + +from jixia_academy.core.memory_bank.interface import MemoryBankInterface + + +def get_memory_backend(prefer: Optional[str] = None) -> MemoryBankInterface: + """ + 获取记忆银行后端 + 默认使用内存实现 + """ + from jixia_academy.core.memory_bank.memory_impl import MemoryBankImpl + + # 使用内存实现 + memory_bank = MemoryBankImpl(storage_path="jixia_academy/data/memory") + print("🧠 使用内存记忆银行后端") + return memory_bank diff --git a/jixia_academy/core/memory_bank/interface.py b/jixia_academy/core/memory_bank/interface.py new file mode 100644 index 0000000..291607b --- /dev/null +++ b/jixia_academy/core/memory_bank/interface.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +记忆银行接口定义 +Memory Bank Interface Definition +""" + +from abc import ABC, abstractmethod +from typing import List, Dict, Any, Optional +from datetime import datetime + + +class MemoryBankInterface(ABC): + """记忆银行接口""" + + @abstractmethod + async def initialize(self): + """初始化记忆银行""" + pass + + @abstractmethod + async def close(self): + """关闭记忆银行""" + pass + + @abstractmethod + async def add_debate_message( + self, + debate_id: str, + speaker: str, + message: str, + round_num: int + ): + """添加辩论消息""" + pass + + @abstractmethod + async def get_debate_history( + self, + debate_id: str + ) -> List[Dict[str, Any]]: + """获取辩论历史""" + pass + + @abstractmethod + async def save_debate_result( + self, + debate_id: str, + summary: Dict[str, Any], + participants: List[str] + ): + """保存辩论结果""" + pass + + @abstractmethod + async def get_debate_result( + self, + debate_id: str + ) -> Optional[Dict[str, Any]]: + """获取辩论结果""" + pass + + @abstractmethod + async def list_debates( + self, + limit: int = 10 + ) -> List[Dict[str, Any]]: + """列出辩论""" + pass + + @abstractmethod + async def add_market_data( + self, + symbol: str, + data: Dict[str, Any] + ): + """添加市场数据""" + pass + + @abstractmethod + async def get_market_data( + self, + symbol: str, + start_date: datetime, + end_date: datetime + ) -> List[Dict[str, Any]]: + """获取市场数据""" + pass + + @abstractmethod + async def store_analysis( + self, + analysis_id: str, + analysis_type: str, + content: Dict[str, Any] + ): + """存储分析结果""" + pass + + @abstractmethod + async def get_analysis( + self, + analysis_id: str + ) -> Optional[Dict[str, Any]]: + """获取分析结果""" + pass \ No newline at end of file diff --git a/jixia_academy/core/memory_bank/memory_impl.py b/jixia_academy/core/memory_bank/memory_impl.py new file mode 100644 index 0000000..e69de29 diff --git a/jixia_academy/core/memory_bank/vertex_memory_bank.py b/jixia_academy/core/memory_bank/vertex_memory_bank.py new file mode 100644 index 0000000..63e0621 --- /dev/null +++ b/jixia_academy/core/memory_bank/vertex_memory_bank.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +""" +Vertex AI Memory Bank 集成模块 +为稷下学宫AI辩论系统提供记忆银行功能 +""" + +import os +from typing import Dict, List, Optional, Any +from dataclasses import dataclass +from datetime import datetime +import json + +try: + from google.cloud import aiplatform + # Memory Bank 功能可能还在预览版中,先使用基础功能 + VERTEX_AI_AVAILABLE = True +except ImportError: + VERTEX_AI_AVAILABLE = False + print("⚠️ Google Cloud AI Platform 未安装,Memory Bank功能不可用") + print("安装命令: pip install google-cloud-aiplatform") + +from config.settings import get_google_genai_config + + +@dataclass +class MemoryEntry: + """记忆条目数据结构""" + content: str + metadata: Dict[str, Any] + timestamp: datetime + agent_name: str + debate_topic: str + memory_type: str # "conversation", "preference", "knowledge", "strategy" + + +class VertexMemoryBank: + """ + Vertex AI Memory Bank 管理器 + 为八仙辩论系统提供智能记忆功能 + """ + + def __init__(self, project_id: str, location: str = "us-central1"): + """ + 初始化Memory Bank + + Args: + project_id: Google Cloud项目ID + location: 部署区域 + """ + if not VERTEX_AI_AVAILABLE: + print("⚠️ Google Cloud AI Platform 未安装,使用本地模拟模式") + # 不抛出异常,允许使用本地模拟模式 + + self.project_id = project_id + self.location = location + self.memory_banks = {} # 存储不同智能体的记忆银行 + self.local_memories = {} # 本地记忆存储 (临时方案) + + # 初始化AI Platform + try: + aiplatform.init(project=project_id, location=location) + print(f"✅ Vertex AI 初始化成功: {project_id} @ {location}") + except Exception as e: + print(f"⚠️ Vertex AI 初始化失败,使用本地模拟模式: {e}") + + # 八仙智能体名称映射 + self.baxian_agents = { + "tieguaili": "铁拐李", + "hanzhongli": "汉钟离", + "zhangguolao": "张果老", + "lancaihe": "蓝采和", + "hexiangu": "何仙姑", + "lvdongbin": "吕洞宾", + "hanxiangzi": "韩湘子", + "caoguojiu": "曹国舅" + } + + @classmethod + def from_config(cls) -> 'VertexMemoryBank': + """ + 从配置创建Memory Bank实例 + + Returns: + VertexMemoryBank实例 + """ + config = get_google_genai_config() + project_id = config.get('project_id') + location = config.get('location', 'us-central1') + + if not project_id: + raise ValueError("Google Cloud Project ID 未配置,请设置 GOOGLE_CLOUD_PROJECT_ID") + + return cls(project_id=project_id, location=location) + + async def create_memory_bank(self, agent_name: str, display_name: str = None) -> str: + """ + 为指定智能体创建记忆银行 + + Args: + agent_name: 智能体名称 (如 "tieguaili") + display_name: 显示名称 (如 "铁拐李的记忆银行") + + Returns: + 记忆银行ID + """ + if not display_name: + chinese_name = self.baxian_agents.get(agent_name, agent_name) + display_name = f"{chinese_name}的记忆银行" + + try: + # 使用本地存储模拟记忆银行 (临时方案) + memory_bank_id = f"memory_bank_{agent_name}_{self.project_id}" + + # 初始化本地记忆存储 + if agent_name not in self.local_memories: + self.local_memories[agent_name] = [] + + self.memory_banks[agent_name] = memory_bank_id + print(f"✅ 为 {display_name} 创建记忆银行: {memory_bank_id}") + + return memory_bank_id + + except Exception as e: + print(f"❌ 创建记忆银行失败: {e}") + raise + + async def add_memory(self, + agent_name: str, + content: str, + memory_type: str = "conversation", + debate_topic: str = "", + metadata: Dict[str, Any] = None) -> str: + """ + 添加记忆到指定智能体的记忆银行 + + Args: + agent_name: 智能体名称 + content: 记忆内容 + memory_type: 记忆类型 ("conversation", "preference", "knowledge", "strategy") + debate_topic: 辩论主题 + metadata: 额外元数据 + + Returns: + 记忆ID + """ + if agent_name not in self.memory_banks: + await self.create_memory_bank(agent_name) + + if metadata is None: + metadata = {} + + # 构建记忆条目 + memory_entry = MemoryEntry( + content=content, + metadata={ + **metadata, + "agent_name": agent_name, + "chinese_name": self.baxian_agents.get(agent_name, agent_name), + "memory_type": memory_type, + "debate_topic": debate_topic, + "system": "jixia_academy" + }, + timestamp=datetime.now(), + agent_name=agent_name, + debate_topic=debate_topic, + memory_type=memory_type + ) + + try: + # 使用本地存储添加记忆 (临时方案) + memory_id = f"memory_{agent_name}_{len(self.local_memories[agent_name])}" + + # 添加到本地存储 + memory_data = { + "id": memory_id, + "content": content, + "metadata": memory_entry.metadata, + "timestamp": memory_entry.timestamp.isoformat(), + "memory_type": memory_type, + "debate_topic": debate_topic + } + + self.local_memories[agent_name].append(memory_data) + + print(f"✅ 为 {self.baxian_agents.get(agent_name)} 添加记忆: {memory_type}") + return memory_id + + except Exception as e: + print(f"❌ 添加记忆失败: {e}") + raise + + async def search_memories(self, + agent_name: str, + query: str, + memory_type: str = None, + limit: int = 10) -> List[Dict[str, Any]]: + """ + 搜索智能体的相关记忆 + + Args: + agent_name: 智能体名称 + query: 搜索查询 + memory_type: 记忆类型过滤 + limit: 返回结果数量限制 + + Returns: + 相关记忆列表 + """ + if agent_name not in self.memory_banks: + return [] + + try: + # 使用本地存储搜索记忆 (临时方案) + if agent_name not in self.local_memories: + return [] + + memories = self.local_memories[agent_name] + results = [] + + # 简单的文本匹配搜索 + query_lower = query.lower() + + for memory in memories: + # 检查记忆类型过滤 + if memory_type and memory.get("memory_type") != memory_type: + continue + + # 检查内容匹配 + content_lower = memory["content"].lower() + debate_topic_lower = memory.get("debate_topic", "").lower() + + # 在内容或辩论主题中搜索 + if query_lower in content_lower or query_lower in debate_topic_lower: + # 计算简单的相关性分数 + content_matches = content_lower.count(query_lower) + topic_matches = debate_topic_lower.count(query_lower) + total_words = len(content_lower.split()) + len(debate_topic_lower.split()) + + relevance_score = (content_matches + topic_matches) / max(total_words, 1) + + results.append({ + "content": memory["content"], + "metadata": memory["metadata"], + "relevance_score": relevance_score + }) + + # 按相关性排序并限制结果数量 + results.sort(key=lambda x: x["relevance_score"], reverse=True) + return results[:limit] + + except Exception as e: + print(f"❌ 搜索记忆失败: {e}") + return [] + + async def get_agent_context(self, agent_name: str, debate_topic: str) -> str: + """ + 获取智能体在特定辩论主题下的上下文记忆 + + Args: + agent_name: 智能体名称 + debate_topic: 辩论主题 + + Returns: + 格式化的上下文字符串 + """ + # 搜索相关记忆 + conversation_memories = await self.search_memories( + agent_name, debate_topic, "conversation", limit=5 + ) + preference_memories = await self.search_memories( + agent_name, debate_topic, "preference", limit=3 + ) + strategy_memories = await self.search_memories( + agent_name, debate_topic, "strategy", limit=3 + ) + + # 构建上下文 + context_parts = [] + + if conversation_memories: + context_parts.append("## 历史对话记忆") + for mem in conversation_memories: + context_parts.append(f"- {mem['content']}") + + if preference_memories: + context_parts.append("\n## 偏好记忆") + for mem in preference_memories: + context_parts.append(f"- {mem['content']}") + + if strategy_memories: + context_parts.append("\n## 策略记忆") + for mem in strategy_memories: + context_parts.append(f"- {mem['content']}") + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + if context_parts: + return f"# {chinese_name}的记忆上下文\n\n" + "\n".join(context_parts) + else: + return f"# {chinese_name}的记忆上下文\n\n暂无相关记忆。" + + async def save_debate_session(self, + debate_topic: str, + participants: List[str], + conversation_history: List[Dict[str, str]], + outcomes: Dict[str, Any] = None) -> None: + """ + 保存完整的辩论会话到各参与者的记忆银行 + + Args: + debate_topic: 辩论主题 + participants: 参与者列表 + conversation_history: 对话历史 + outcomes: 辩论结果和洞察 + """ + for agent_name in participants: + if agent_name not in self.baxian_agents: + continue + + # 保存对话历史 + conversation_summary = self._summarize_conversation( + conversation_history, agent_name + ) + await self.add_memory( + agent_name=agent_name, + content=conversation_summary, + memory_type="conversation", + debate_topic=debate_topic, + metadata={ + "participants": participants, + "session_length": len(conversation_history) + } + ) + + # 保存策略洞察 + if outcomes: + strategy_insight = self._extract_strategy_insight( + outcomes, agent_name + ) + if strategy_insight: + await self.add_memory( + agent_name=agent_name, + content=strategy_insight, + memory_type="strategy", + debate_topic=debate_topic, + metadata={"session_outcome": outcomes} + ) + + def _summarize_conversation(self, + conversation_history: List[Dict[str, str]], + agent_name: str) -> str: + """ + 为特定智能体总结对话历史 + + Args: + conversation_history: 对话历史 + agent_name: 智能体名称 + + Returns: + 对话总结 + """ + agent_messages = [ + msg for msg in conversation_history + if msg.get("agent") == agent_name + ] + + if not agent_messages: + return "本次辩论中未发言" + + chinese_name = self.baxian_agents.get(agent_name, agent_name) + summary = f"{chinese_name}在本次辩论中的主要观点:\n" + + for i, msg in enumerate(agent_messages[:3], 1): # 只取前3条主要观点 + summary += f"{i}. {msg.get('content', '')[:100]}...\n" + + return summary + + def _extract_strategy_insight(self, + outcomes: Dict[str, Any], + agent_name: str) -> Optional[str]: + """ + 从辩论结果中提取策略洞察 + + Args: + outcomes: 辩论结果 + agent_name: 智能体名称 + + Returns: + 策略洞察或None + """ + # 这里可以根据实际的outcomes结构来提取洞察 + # 暂时返回一个简单的示例 + chinese_name = self.baxian_agents.get(agent_name, agent_name) + + if "winner" in outcomes and outcomes["winner"] == agent_name: + return f"{chinese_name}在本次辩论中获胜,其论证策略值得保持。" + elif "insights" in outcomes and agent_name in outcomes["insights"]: + return outcomes["insights"][agent_name] + + return None + + +# 便捷函数 +async def initialize_baxian_memory_banks(project_id: str, location: str = "us-central1") -> VertexMemoryBank: + """ + 初始化所有八仙智能体的记忆银行 + + Args: + project_id: Google Cloud项目ID + location: 部署区域 + + Returns: + 配置好的VertexMemoryBank实例 + """ + memory_bank = VertexMemoryBank(project_id, location) + + print("🏛️ 正在为稷下学宫八仙创建记忆银行...") + + for agent_key, chinese_name in memory_bank.baxian_agents.items(): + try: + await memory_bank.create_memory_bank(agent_key) + except Exception as e: + print(f"⚠️ 创建 {chinese_name} 记忆银行时出错: {e}") + + print("✅ 八仙记忆银行初始化完成") + return memory_bank + + +if __name__ == "__main__": + import asyncio + + async def test_memory_bank(): + """测试Memory Bank功能""" + try: + # 从配置创建Memory Bank + memory_bank = VertexMemoryBank.from_config() + + # 测试创建记忆银行 + await memory_bank.create_memory_bank("tieguaili") + + # 测试添加记忆 + await memory_bank.add_memory( + agent_name="tieguaili", + content="在讨论NVIDIA股票时,我倾向于逆向思维,关注潜在风险。", + memory_type="preference", + debate_topic="NVIDIA投资分析" + ) + + # 测试搜索记忆 + results = await memory_bank.search_memories( + agent_name="tieguaili", + query="NVIDIA", + limit=5 + ) + + print(f"搜索结果: {len(results)} 条记忆") + for result in results: + print(f"- {result['content']}") + + except Exception as e: + print(f"❌ 测试失败: {e}") + + # 运行测试 + asyncio.run(test_memory_bank()) diff --git a/jixia_academy/ui/streamlit/__init__.py b/jixia_academy/ui/streamlit/__init__.py new file mode 100644 index 0000000..f40a138 --- /dev/null +++ b/jixia_academy/ui/streamlit/__init__.py @@ -0,0 +1 @@ +# 应用模块 \ No newline at end of file diff --git a/jixia_academy/ui/streamlit/streamlit_app.py b/jixia_academy/ui/streamlit/streamlit_app.py new file mode 100644 index 0000000..c9aef9d --- /dev/null +++ b/jixia_academy/ui/streamlit/streamlit_app.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +炼妖壶 (Lianyaohu) - 稷下学宫AI辩论系统 +主Streamlit应用入口 + +重构版本: +- 清晰的模块化结构 +- 统一的配置管理 +- 安全的密钥处理 +""" + +import streamlit as st +import sys +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +def configure_page(): + """配置页面基本设置""" + st.set_page_config( + page_title="炼妖壶 - 稷下学宫AI辩论系统", + page_icon="🏛️", + layout="wide", + initial_sidebar_state="expanded" + ) + +def show_header(): + """显示页面头部""" + st.title("🏛️ 炼妖壶 - 稷下学宫AI辩论系统") + st.markdown("**基于中国哲学传统的多AI智能体辩论平台**") + + # 显示系统状态 + col1, col2, col3 = st.columns(3) + with col1: + st.metric("系统状态", "🟢 运行中") + with col2: + st.metric("AI模型", "OpenRouter") + with col3: + # 更新数据源展示,加入 OpenBB + st.metric("数据源", "RapidAPI + OpenBB") + +def show_sidebar(): + """显示侧边栏""" + with st.sidebar: + st.markdown("## 🎛️ 控制面板") + + # 系统信息 + st.markdown("### 📊 系统信息") + st.info("**版本**: v2.0 (重构版)") + st.info("**状态**: 迁移完成") + + # 配置检查 + st.markdown("### 🔧 配置状态") + try: + from config.doppler_config import validate_config + if validate_config(): + st.success("✅ 配置正常") + else: + st.error("❌ 配置异常") + except Exception as e: + st.warning(f"⚠️ 配置检查失败: {str(e)}") + + # 快速操作 + st.markdown("### ⚡ 快速操作") + if st.button("🧪 测试API连接"): + test_api_connections() + + if st.button("🏛️ 启动八仙论道"): + start_jixia_debate() + +def test_api_connections(): + """测试API连接""" + with st.spinner("正在测试API连接..."): + try: + from scripts.api_health_check import test_openrouter_api, test_rapidapi_connection + + openrouter_ok = test_openrouter_api() + rapidapi_ok = test_rapidapi_connection() + + if openrouter_ok and rapidapi_ok: + st.success("✅ 所有API连接正常") + else: + st.error("❌ 部分API连接失败") + except Exception as e: + st.error(f"❌ API测试异常: {str(e)}") + +def start_jixia_debate(): + """启动稷下学宫辩论""" + with st.spinner("正在启动稷下学宫八仙论道..."): + try: + from config.settings import get_rapidapi_key + from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine + + api_key = get_rapidapi_key() + engine = JixiaPerpetualEngine(api_key) + + # 运行辩论 + results = engine.simulate_jixia_debate('TSLA') + + st.success("✅ 八仙论道完成") + st.json(results) + except Exception as e: + st.error(f"❌ 辩论启动失败: {str(e)}") + +def main(): + """主函数""" + configure_page() + show_header() + show_sidebar() + + # 主内容区域 + st.markdown("---") + + # 选项卡(新增 OpenBB 数据页签和AI协作页签) + tab1, tab2, tab3, tab4, tab5 = st.tabs(["🏛️ 稷下学宫", "🌍 天下体系", "📊 数据分析", "📈 OpenBB 数据", "🤖 AI协作"]) + + with tab1: + st.markdown("### 🏛️ 稷下学宫 - 八仙论道") + st.markdown("**多AI智能体辩论系统,基于中国传统八仙文化**") + + # 辩论模式选择 + debate_mode = st.selectbox( + "选择辩论模式", + ["ADK模式 (太上老君主持)", "传统模式 (RapidAPI数据)"] + ) + + if debate_mode == "ADK模式 (太上老君主持)": + from app.tabs.adk_debate_tab import render_adk_debate_tab + render_adk_debate_tab() + + else: + # 传统模式 + col1, col2 = st.columns([2, 1]) + with col1: + topic = st.text_input("辩论主题 (股票代码)", value="TSLA") + with col2: + if st.button("🎭 开始辩论", type="primary"): + start_debate_session(topic) + + # 显示辩论历史 + if 'debate_history' in st.session_state: + st.markdown("#### 📜 辩论记录") + for record in st.session_state.debate_history[-3:]: # 显示最近3次 + with st.expander(f"🎭 {record['topic']} - {record['time']}"): + st.json(record['results']) + + with tab2: + st.markdown("### 🌍 天下体系分析") + try: + from app.tabs.tianxia_tab import render_tianxia_tab + render_tianxia_tab() + except Exception as e: + st.error(f"❌ 天下体系模块加载失败: {str(e)}") + + with tab3: + st.markdown("### 📊 数据分析") + st.info("🚧 数据分析模块开发中...") + + # 显示系统统计 + try: + from config.settings import get_rapidapi_key + from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine + + api_key = get_rapidapi_key() + engine = JixiaPerpetualEngine(api_key) + stats = engine.get_usage_stats() + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("API调用总数", stats['total_calls']) + with col2: + st.metric("活跃API数", f"{stats['active_apis']}/{stats['total_apis']}") + with col3: + st.metric("未使用API", stats['unused_count']) + + except Exception as e: + st.warning(f"⚠️ 无法加载统计数据: {str(e)}") + + with tab5: + st.markdown("### 🤖 AI协作") + try: + from app.tabs.ai_collaboration_tab import main as ai_collaboration_main + ai_collaboration_main() + except Exception as e: + st.error(f"❌ AI协作模块加载失败: {str(e)}") + +def start_debate_session(topic: str): + """启动辩论会话""" + if not topic: + st.error("请输入辩论主题") + return + + with st.spinner(f"🏛️ 八仙正在就 {topic} 展开论道..."): + try: + from config.settings import get_rapidapi_key + from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine + from datetime import datetime + + api_key = get_rapidapi_key() + engine = JixiaPerpetualEngine(api_key) + + # 运行辩论 + results = engine.simulate_jixia_debate(topic) + + # 保存到会话状态 + if 'debate_history' not in st.session_state: + st.session_state.debate_history = [] + + st.session_state.debate_history.append({ + 'topic': topic, + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'results': {name: {'success': result.success, 'api_used': result.api_used} + for name, result in results.items()} + }) + + st.success(f"✅ 八仙论道完成!共有 {len(results)} 位仙人参与") + + # 显示结果摘要 + successful_debates = sum(1 for result in results.values() if result.success) + st.info(f"📊 成功获取数据: {successful_debates}/{len(results)} 位仙人") + + except Exception as e: + st.error(f"❌ 辩论启动失败: {str(e)}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/ui/streamlit/tabs/adk_debate_tab.py b/jixia_academy/ui/streamlit/tabs/adk_debate_tab.py new file mode 100644 index 0000000..b03c427 --- /dev/null +++ b/jixia_academy/ui/streamlit/tabs/adk_debate_tab.py @@ -0,0 +1,205 @@ + +import streamlit as st +import asyncio +import sys +from pathlib import Path +from typing import Dict, Any, List + +# Ensure the main project directory is in the Python path +project_root = Path(__file__).parent.parent.parent +sys.path.insert(0, str(project_root)) + +try: + from google.adk import Agent, Runner + from google.adk.sessions import InMemorySessionService, Session + from google.genai import types + ADK_AVAILABLE = True +except ImportError: + ADK_AVAILABLE = False + # 创建占位符类 + class Agent: + pass + class Runner: + pass + class InMemorySessionService: + pass + class Session: + pass + class types: + class Content: + pass + class Part: + pass + +async def _get_llm_reply(runner: Runner, session: Session, prompt: str) -> str: + """Helper function to call a Runner and get a text reply.""" + 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: + 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: + reply += str(part.text) + elif hasattr(event, 'text') and event.text: + reply += str(event.text) + + return reply.strip() + +async def run_adk_debate_streamlit(topic: str, participants: List[str], rounds: int): + """ + Runs the ADK turn-based debate and yields each statement for Streamlit display. + """ + try: + yield "🚀 **启动ADK八仙轮流辩论 (太上老君主持)...**" + + all_immortals = ["铁拐李", "吕洞宾", "何仙姑", "张果老", "蓝采和", "汉钟离", "韩湘子", "曹国舅"] + if not participants: + participants = all_immortals + + character_configs = { + "太上老君": {"name": "太上老君", "model": "gemini-1.5-pro", "instruction": "你是太上老君,天道化身,辩论的主持人。你的言辞沉稳、公正、充满智慧。你的任务是:1. 对辩论主题进行开场介绍。2. 在每轮开始时进行引导。3. 在辩论结束后,对所有观点进行全面、客观的总结。保持中立,不偏袒任何一方。"}, + "铁拐李": {"name": "铁拐李", "model": "gemini-1.5-flash", "instruction": "你是铁拐李,八仙中的逆向思维专家。你善于从批判和质疑的角度看问题,发言风格直接、犀利,但富有智慧。"}, + "吕洞宾": {"name": "吕洞宾", "model": "gemini-1.5-flash", "instruction": "你是吕洞宾,八仙中的理性分析者。你善于平衡各方观点,用理性和逻辑来分析问题,发言风格温和而深刻。"}, + "何仙姑": {"name": "何仙姑", "model": "gemini-1.5-flash", "instruction": "你是何仙姑,八仙中的风险控制专家。你总是从风险管理的角度思考问题,善于发现潜在危险,发言风格谨慎、细致。"}, + "张果老": {"name": "张果老", "model": "gemini-1.5-flash", "instruction": "你是张果老,八仙中的历史智慧者。你善于从历史数据中寻找规律和智慧,提供长期视角,发言风格沉稳、博学。"}, + "蓝采和": {"name": "蓝采和", "model": "gemini-1.5-flash", "instruction": "你是蓝采和,八仙中的创新思维者。你善于从新兴视角和非传统方法来看待问题,发言风格活泼、新颖。"}, + "汉钟离": {"name": "汉钟离", "model": "gemini-1.5-flash", "instruction": "你是汉钟离,八仙中的平衡协调者。你善于综合各方观点,寻求和谐统一的解决方案,发言风格平和、包容。"}, + "韩湘子": {"name": "韩湘子", "model": "gemini-1.5-flash", "instruction": "你是韩湘子,八仙中的艺术感知者。你善于从美学和感性的角度分析问题,发言风格优雅、感性。"}, + "曹国舅": {"name": "曹国舅", "model": "gemini-1.5-flash", "instruction": "你是曹国舅,八仙中的实务执行者。你关注实际操作和具体细节,发言风格务实、严谨。"} + } + + session_service = InMemorySessionService() + session = await session_service.create_session(state={}, app_name="稷下学宫八仙论道系统-Streamlit", user_id="st_user") + + runners: Dict[str, Runner] = {} + for name, config in character_configs.items(): + if name == "太上老君" or name in participants: + agent = Agent(name=config["name"], model=config["model"], instruction=config["instruction"]) + runners[name] = Runner(app_name="稷下学宫八仙论道系统-Streamlit", agent=agent, session_service=session_service) + + host_runner = runners.get("太上老君") + if not host_runner: + yield "❌ **主持人太上老君初始化失败。**" + return + + yield f"🎯 **参与仙人**: {', '.join(participants)}" + debate_history = [] + + # Opening statement + opening_prompt = f"请为本次关于“{topic}”的辩论,发表一段公正、深刻的开场白,并宣布辩论开始。" + opening_statement = await _get_llm_reply(host_runner, session, opening_prompt) + yield f"👑 **太上老君**: {opening_statement}" + + # Debate rounds + for round_num in range(rounds): + round_intro_prompt = f"请为第 {round_num + 1} 轮辩论说一段引导语。" + round_intro = await _get_llm_reply(host_runner, session, round_intro_prompt) + yield f"👑 **太上老君**: {round_intro}" + + for name in participants: + if name not in runners: continue + + history_context = f"\n最近的论道内容:\n" + "\n".join([f"- {h}" for h in debate_history[-5:]]) if debate_history else "" + prompt = f"论道主题: {topic}{history_context}\n\n请从你的角色特点出发,简洁地发表观点。" + + reply = await _get_llm_reply(runners[name], session, prompt) + yield f"🗣️ **{name}**: {reply}" + + debate_history.append(f"{name}: {reply}") + await asyncio.sleep(1) + + # Summary + summary_prompt = f"辩论已结束。以下是完整的辩论记录:\n\n{' '.join(debate_history)}\n\n请对本次辩论进行全面、公正、深刻的总结。" + summary = await _get_llm_reply(host_runner, session, summary_prompt) + yield f"👑 **太上老君**: {summary}" + + for runner in runners.values(): + await runner.close() + + yield "🎉 **ADK八仙轮流辩论完成!**" + + except Exception as e: + yield f"❌ **运行ADK八仙轮流辩论失败**: {e}" + import traceback + st.error(traceback.format_exc()) + +def render_adk_debate_tab(): + """Renders the Streamlit UI for the ADK Debate tab.""" + + # 检查 ADK 是否可用 + if not ADK_AVAILABLE: + st.error("🚫 Google ADK 模块未安装或不可用") + st.info("📦 正在安装 Google ADK,请稍候...") + st.info("💡 安装完成后请刷新页面") + + with st.expander("📋 安装说明"): + st.code(""" +# 安装 Google ADK +pip install google-adk>=1.12.0 + +# 或从 GitHub 安装开发版 +pip install git+https://github.com/google/adk-python.git@main + """) + return + st.markdown("### 🏛️ 八仙论道 (ADK版 - 太上老君主持)") + + topic = st.text_input( + "辩论主题", + value="AI是否应该拥有创造力?", + key="adk_topic_input" + ) + + all_immortals = ["铁拐李", "吕洞宾", "何仙姑", "张果老", "蓝采和", "汉钟离", "韩湘子", "曹国舅"] + + col1, col2 = st.columns(2) + with col1: + rounds = st.number_input("辩论轮数", min_value=1, max_value=5, value=1, key="adk_rounds_input") + with col2: + participants = st.multiselect( + "选择参与的仙人 (默认全选)", + options=all_immortals, + default=all_immortals, + key="adk_participants_select" + ) + + if st.button("🚀 开始论道", key="start_adk_debate_button", type="primary"): + if not topic: + st.error("请输入辩论主题。") + return + if not participants: + st.error("请至少选择一位参与的仙人。") + return + + st.markdown("---") + st.markdown("#### 📜 论道实录") + + # Placeholder for real-time output + output_container = st.empty() + full_log = "" + + # Run the async debate function + try: + # Get a new event loop for the thread + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + # Create a coroutine object + coro = run_adk_debate_streamlit(topic, participants, rounds) + + # Run the coroutine until completion + for message in loop.run_until_complete(async_generator_to_list(coro)): + full_log += message + "\n\n" + output_container.markdown(full_log) + + except Exception as e: + st.error(f"启动辩论时发生错误: {e}") + +async def async_generator_to_list(async_gen): + """Helper to consume an async generator and return a list of its items.""" + return [item async for item in async_gen] diff --git a/jixia_academy/ui/streamlit/tabs/ai_collaboration_tab.py b/jixia_academy/ui/streamlit/tabs/ai_collaboration_tab.py new file mode 100644 index 0000000..8f8d389 --- /dev/null +++ b/jixia_academy/ui/streamlit/tabs/ai_collaboration_tab.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +四AI团队协作Web界面 +基于Streamlit的实时协作监控和管理界面 +""" + +import streamlit as st +import asyncio +import json +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +from datetime import datetime, timedelta +import sys +from pathlib import Path + +# 添加项目路径到sys.path +project_root = Path(__file__).parent.parent.parent.parent +sys.path.insert(0, str(project_root)) + +from src.jixia.coordination.ai_team_collaboration import ( + AITeamCollaboration, AIRole, MessageType, CollaborationType, WorkPhase +) + +# 页面配置 +st.set_page_config( + page_title="四AI团队协作中心", + page_icon="🤖", + layout="wide", + initial_sidebar_state="expanded" +) + +# 初始化协作系统 +@st.cache_resource +def init_collaboration_system(): + """初始化协作系统""" + return AITeamCollaboration() + +def main(): + """主界面""" + st.title("🤖 四AI团队协作中心") + st.markdown("### OpenBB集成项目实时协作监控") + + # 初始化系统 + collab = init_collaboration_system() + + # 侧边栏 + with st.sidebar: + st.header("🎯 项目状态") + + # 当前阶段 + current_phase = st.selectbox( + "当前工作阶段", + [phase.value for phase in WorkPhase], + index=list(WorkPhase).index(collab.current_phase) + ) + + if st.button("更新阶段"): + new_phase = WorkPhase(current_phase) + asyncio.run(collab.advance_phase(new_phase)) + st.success(f"阶段已更新为: {current_phase}") + st.rerun() + + st.divider() + + # AI状态概览 + st.subheader("🤖 AI状态概览") + for ai_role, status in collab.ai_status.items(): + status_color = { + "ready": "🟢", + "active": "🔵", + "waiting": "🟡", + "completed_handoff": "✅", + "received_handoff": "📥" + }.get(status["status"], "⚪") + + st.write(f"{status_color} **{ai_role.value}**") + st.write(f" 📋 {status['current_task']}") + st.write(f" 🎯 {status['role']}") + + # 主要内容区域 + tab1, tab2, tab3, tab4, tab5 = st.tabs([ + "📢 主协作频道", "📊 AI仪表板", "🔄 工作流管理", "📈 协作分析", "⚙️ 系统管理" + ]) + + with tab1: + render_main_collaboration(collab) + + with tab2: + render_ai_dashboard(collab) + + with tab3: + render_workflow_management(collab) + + with tab4: + render_collaboration_analytics(collab) + + with tab5: + render_system_management(collab) + +def render_main_collaboration(collab): + """渲染主协作频道""" + st.header("📢 主协作频道") + + # 频道选择 + channel_options = { + channel.name: channel_id + for channel_id, channel in collab.channels.items() + } + + selected_channel_name = st.selectbox( + "选择频道", + list(channel_options.keys()), + index=0 + ) + + selected_channel_id = channel_options[selected_channel_name] + channel = collab.channels[selected_channel_id] + + col1, col2 = st.columns([2, 1]) + + with col1: + # 消息历史 + st.subheader(f"💬 {channel.name}") + + if channel.message_history: + for msg in channel.message_history[-10:]: # 显示最近10条消息 + sender_emoji = { + AIRole.QWEN: "🏗️", + AIRole.CLAUDE: "💻", + AIRole.GEMINI: "🧪", + AIRole.ROVODEV: "📚" + }.get(msg.sender, "🤖") + + with st.chat_message(msg.sender.value, avatar=sender_emoji): + st.write(f"**{msg.message_type.value}** - {msg.timestamp.strftime('%H:%M')}") + st.write(msg.content) + + if msg.attachments: + st.write("📎 附件:") + for attachment in msg.attachments: + st.write(f" • {attachment}") + + if msg.tags: + tag_html = " ".join([f"{tag}" for tag in msg.tags]) + st.markdown(tag_html, unsafe_allow_html=True) + else: + st.info("暂无消息") + + with col2: + # 频道信息 + st.subheader("ℹ️ 频道信息") + st.write(f"**类型**: {channel.channel_type.value}") + st.write(f"**参与者**: {len(channel.participants)}人") + st.write(f"**主持人**: {channel.moderator.value}") + st.write(f"**消息数**: {len(channel.message_history)}") + st.write(f"**最后活动**: {channel.last_activity.strftime('%Y-%m-%d %H:%M')}") + + # 参与者列表 + st.write("**参与者列表**:") + for participant in channel.participants: + role_emoji = { + AIRole.QWEN: "🏗️", + AIRole.CLAUDE: "💻", + AIRole.GEMINI: "🧪", + AIRole.ROVODEV: "📚" + }.get(participant, "🤖") + st.write(f"{role_emoji} {participant.value}") + + # 发送消息区域 + st.divider() + st.subheader("📝 发送消息") + + col1, col2, col3 = st.columns([2, 1, 1]) + + with col1: + message_content = st.text_area("消息内容", height=100) + + with col2: + sender = st.selectbox( + "发送者", + [role.value for role in AIRole] + ) + + message_type = st.selectbox( + "消息类型", + [msg_type.value for msg_type in MessageType] + ) + + with col3: + receiver = st.selectbox( + "接收者", + ["广播"] + [role.value for role in AIRole] + ) + + priority = st.slider("优先级", 1, 5, 1) + + if st.button("发送消息", type="primary"): + if message_content: + try: + receiver_role = None if receiver == "广播" else AIRole(receiver) + + asyncio.run(collab.send_message( + sender=AIRole(sender), + content=message_content, + message_type=MessageType(message_type), + channel_id=selected_channel_id, + receiver=receiver_role, + priority=priority + )) + + st.success("消息发送成功!") + st.rerun() + except Exception as e: + st.error(f"发送失败: {str(e)}") + else: + st.warning("请输入消息内容") + +def render_ai_dashboard(collab): + """渲染AI仪表板""" + st.header("📊 AI工作仪表板") + + # AI选择 + selected_ai = st.selectbox( + "选择AI", + [role.value for role in AIRole] + ) + + ai_role = AIRole(selected_ai) + dashboard = collab.get_ai_dashboard(ai_role) + + # 基本信息 + col1, col2, col3, col4 = st.columns(4) + + with col1: + st.metric("当前状态", dashboard["status"]["status"]) + + with col2: + st.metric("活跃频道", len(dashboard["active_channels"])) + + with col3: + st.metric("待处理任务", len(dashboard["pending_tasks"])) + + with col4: + st.metric("协作得分", dashboard["collaboration_stats"]["collaboration_score"]) + + # 详细信息 + col1, col2 = st.columns(2) + + with col1: + # 待处理任务 + st.subheader("📋 待处理任务") + if dashboard["pending_tasks"]: + for task in dashboard["pending_tasks"]: + with st.expander(f"{task['type']} - 优先级 {task['priority']}"): + st.write(f"**来自**: {task['from']}") + st.write(f"**频道**: {task['channel']}") + st.write(f"**创建时间**: {task['created']}") + st.write(f"**描述**: {task['description']}") + else: + st.info("暂无待处理任务") + + with col2: + # 最近消息 + st.subheader("📨 最近消息") + if dashboard["recent_messages"]: + for msg in dashboard["recent_messages"][:5]: + priority_color = { + 1: "🔵", 2: "🟢", 3: "🟡", 4: "🟠", 5: "🔴" + }.get(msg["priority"], "⚪") + + st.write(f"{priority_color} **{msg['sender']}** 在 **{msg['channel']}**") + st.write(f" {msg['content']}") + st.write(f" ⏰ {msg['timestamp']}") + st.divider() + else: + st.info("暂无最近消息") + + # 协作统计 + st.subheader("📈 协作统计") + + stats = dashboard["collaboration_stats"] + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("发送消息", stats["messages_sent"]) + + with col2: + st.metric("接收消息", stats["messages_received"]) + + with col3: + st.metric("总消息数", stats["total_messages"]) + +def render_workflow_management(collab): + """渲染工作流管理""" + st.header("🔄 工作流管理") + + # 工作流规则 + st.subheader("📜 工作流规则") + + rules_data = [] + for rule_id, rule in collab.workflow_rules.items(): + rules_data.append({ + "规则ID": rule.id, + "规则名称": rule.name, + "触发阶段": rule.trigger_phase.value, + "目标AI": rule.target_ai.value if rule.target_ai else "无", + "状态": "✅ 激活" if rule.is_active else "❌ 禁用" + }) + + if rules_data: + st.dataframe(pd.DataFrame(rules_data), use_container_width=True) + + # 手动工作交接 + st.divider() + st.subheader("🤝 手动工作交接") + + col1, col2, col3 = st.columns(3) + + with col1: + from_ai = st.selectbox("交接方", [role.value for role in AIRole]) + to_ai = st.selectbox("接收方", [role.value for role in AIRole]) + + with col2: + task_desc = st.text_input("任务描述") + deliverables = st.text_area("交付物列表 (每行一个)") + + with col3: + notes = st.text_area("备注") + + if st.button("执行工作交接"): + if task_desc and from_ai != to_ai: + deliverable_list = [d.strip() for d in deliverables.split('\n') if d.strip()] + + try: + asyncio.run(collab.handoff_work( + from_ai=AIRole(from_ai), + to_ai=AIRole(to_ai), + task_description=task_desc, + deliverables=deliverable_list, + notes=notes + )) + st.success("工作交接完成!") + st.rerun() + except Exception as e: + st.error(f"交接失败: {str(e)}") + else: + st.warning("请填写完整信息,且交接方和接收方不能相同") + +def render_collaboration_analytics(collab): + """渲染协作分析""" + st.header("📈 协作分析") + + # 消息统计 + st.subheader("💬 消息统计") + + # 收集所有消息数据 + message_data = [] + for channel_id, channel in collab.channels.items(): + for msg in channel.message_history: + message_data.append({ + "频道": channel.name, + "发送者": msg.sender.value, + "消息类型": msg.message_type.value, + "优先级": msg.priority, + "时间": msg.timestamp, + "日期": msg.timestamp.date(), + "小时": msg.timestamp.hour + }) + + if message_data: + df = pd.DataFrame(message_data) + + col1, col2 = st.columns(2) + + with col1: + # 按AI发送者统计 + sender_counts = df.groupby("发送者").size().reset_index() + sender_counts.columns = ["AI", "消息数量"] + + fig = px.bar(sender_counts, x="AI", y="消息数量", + title="各AI发送消息统计") + st.plotly_chart(fig, use_container_width=True) + + with col2: + # 按消息类型统计 + type_counts = df.groupby("消息类型").size().reset_index() + type_counts.columns = ["消息类型", "数量"] + + fig = px.pie(type_counts, values="数量", names="消息类型", + title="消息类型分布") + st.plotly_chart(fig, use_container_width=True) + + # 时间线分析 + st.subheader("⏰ 活跃度时间线") + + if len(df) > 1: + daily_counts = df.groupby("日期").size().reset_index() + daily_counts.columns = ["日期", "消息数量"] + + fig = px.line(daily_counts, x="日期", y="消息数量", + title="每日消息数量趋势") + st.plotly_chart(fig, use_container_width=True) + + # 频道活跃度 + st.subheader("📢 频道活跃度") + channel_counts = df.groupby("频道").size().reset_index() + channel_counts.columns = ["频道", "消息数量"] + channel_counts = channel_counts.sort_values("消息数量", ascending=True) + + fig = px.bar(channel_counts, x="消息数量", y="频道", + orientation='h', title="各频道消息数量") + st.plotly_chart(fig, use_container_width=True) + + else: + st.info("暂无消息数据用于分析") + +def render_system_management(collab): + """渲染系统管理""" + st.header("⚙️ 系统管理") + + # 系统状态 + st.subheader("🔍 系统状态") + + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("活跃频道", len([c for c in collab.channels.values() if c.is_active])) + + with col2: + total_messages = sum(len(c.message_history) for c in collab.channels.values()) + st.metric("总消息数", total_messages) + + with col3: + st.metric("工作流规则", len(collab.workflow_rules)) + + # 频道管理 + st.subheader("📢 频道管理") + + for channel_id, channel in collab.channels.items(): + with st.expander(f"{channel.name} ({channel.channel_type.value})"): + col1, col2 = st.columns(2) + + with col1: + st.write(f"**描述**: {channel.description}") + st.write(f"**参与者**: {len(channel.participants)}人") + st.write(f"**消息数**: {len(channel.message_history)}") + st.write(f"**状态**: {'🟢 活跃' if channel.is_active else '🔴 禁用'}") + + with col2: + st.write("**参与者列表**:") + for participant in channel.participants: + st.write(f" • {participant.value}") + + st.write(f"**主持人**: {channel.moderator.value}") + st.write(f"**最后活动**: {channel.last_activity.strftime('%Y-%m-%d %H:%M')}") + + # 数据导出 + st.divider() + st.subheader("📤 数据导出") + + if st.button("导出协作数据"): + # 准备导出数据 + export_data = { + "channels": {}, + "ai_status": {}, + "workflow_rules": {}, + "system_info": { + "current_phase": collab.current_phase.value, + "export_time": datetime.now().isoformat() + } + } + + # 频道数据 + for channel_id, channel in collab.channels.items(): + export_data["channels"][channel_id] = { + "name": channel.name, + "type": channel.channel_type.value, + "participants": [p.value for p in channel.participants], + "message_count": len(channel.message_history), + "last_activity": channel.last_activity.isoformat() + } + + # AI状态数据 + for ai_role, status in collab.ai_status.items(): + export_data["ai_status"][ai_role.value] = status + + # 工作流规则 + for rule_id, rule in collab.workflow_rules.items(): + export_data["workflow_rules"][rule_id] = { + "name": rule.name, + "description": rule.description, + "trigger_phase": rule.trigger_phase.value, + "action": rule.action, + "is_active": rule.is_active + } + + # 创建下载链接 + json_str = json.dumps(export_data, indent=2, ensure_ascii=False) + st.download_button( + label="下载协作数据 (JSON)", + data=json_str, + file_name=f"ai_collaboration_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", + mime="application/json" + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jixia_academy/ui/streamlit/tabs/openbb_tab.py b/jixia_academy/ui/streamlit/tabs/openbb_tab.py new file mode 100644 index 0000000..210f6a1 --- /dev/null +++ b/jixia_academy/ui/streamlit/tabs/openbb_tab.py @@ -0,0 +1,184 @@ +import streamlit as st +import pandas as pd +import plotly.express as px +from datetime import datetime, timedelta + + +def _check_openbb_installed() -> bool: + try: + # OpenBB v4 推荐用法: from openbb import obb + from openbb import obb # noqa: F401 + return True + except Exception: + return False + + +def _load_price_data(symbol: str, days: int = 365) -> pd.DataFrame: + """Fetch OHLCV using OpenBB v4 when available; otherwise return demo/synthetic data.""" + end = datetime.utcnow().date() + start = end - timedelta(days=days) + + # 优先使用 OpenBB v4 + try: + from openbb import obb + + # 先尝试股票路由 + try: + out = obb.equity.price.historical( + symbol, + start_date=str(start), + end_date=str(end), + ) + except Exception: + out = None + + # 若股票无数据,再尝试 ETF 路由 + if out is None or (hasattr(out, "is_empty") and out.is_empty): + try: + out = obb.etf.price.historical( + symbol, + start_date=str(start), + end_date=str(end), + ) + except Exception: + out = None + + if out is not None: + if hasattr(out, "to_df"): + df = out.to_df() + elif hasattr(out, "to_dataframe"): + df = out.to_dataframe() + else: + # 兜底: 有些 provider 返回可序列化对象 + df = pd.DataFrame(out) # type: ignore[arg-type] + + # 规格化列名 + if not isinstance(df, pd.DataFrame) or df.empty: + raise ValueError("OpenBB 返回空数据") + + # 有的表以 index 为日期 + if 'date' in df.columns: + df['Date'] = pd.to_datetime(df['date']) + elif df.index.name in ('date', 'Date') or isinstance(df.index, pd.DatetimeIndex): + df = df.copy() + df['Date'] = pd.to_datetime(df.index) + else: + # 尝试查找常见日期列 + for cand in ['timestamp', 'time', 'datetime']: + if cand in df.columns: + df['Date'] = pd.to_datetime(df[cand]) + break + + # 归一化收盘价列 + close_col = None + for cand in ['adj_close', 'close', 'Close', 'price', 'close_price', 'c']: + if cand in df.columns: + close_col = cand + break + if close_col is None: + raise ValueError("未找到收盘价列") + df['Close'] = pd.to_numeric(df[close_col], errors='coerce') + + # 仅保留需要列并清洗 + if 'Date' not in df.columns: + raise ValueError("未找到日期列") + df = df[['Date', 'Close']].dropna() + df = df.sort_values('Date').reset_index(drop=True) + # 限定时间窗口(有些 provider 可能返回更长区间) + df = df[df['Date'].dt.date.between(start, end)] + + if df.empty: + raise ValueError("清洗后为空") + return df + + except Exception: + # 如果 OpenBB 不可用或调用失败,进入本地演示/合成数据兜底 + pass + + # Fallback to demo from examples/data + try: + from pathlib import Path + root = Path(__file__).resolve().parents[2] + demo_map = { + 'AAPL': root / 'examples' / 'data' / 'demo_results_aapl.json', + 'MSFT': root / 'examples' / 'data' / 'demo_results_msft.json', + 'TSLA': root / 'examples' / 'data' / 'demo_results_tsla.json', + } + path = demo_map.get(symbol.upper()) + if path and path.exists(): + df = pd.read_json(path) + if 'date' in df.columns: + df['Date'] = pd.to_datetime(df['date']) + if 'close' in df.columns: + df['Close'] = df['close'] + df = df[['Date', 'Close']].dropna().sort_values('Date').reset_index(drop=True) + # 裁剪到时间窗口 + df = df[df['Date'].dt.date.between(start, end)] + return df + except Exception: + pass + + # Last resort: minimal synthetic data(避免 FutureWarning) + dates = pd.date_range(end=end, periods=min(days, 180)) + return pd.DataFrame({ + 'Date': dates, + 'Close': pd.Series(range(len(dates))).rolling(5).mean().bfill() + }) + + +def _kpis_from_df(df: pd.DataFrame) -> dict: + if df.empty or 'Close' not in df.columns: + return {"最新价": "-", "近30日涨幅": "-", "最大回撤(近90日)": "-"} + latest = float(df['Close'].iloc[-1]) + last_30 = df.tail(30) + if len(last_30) > 1: + pct_30 = (last_30['Close'].iloc[-1] / last_30['Close'].iloc[0] - 1) * 100 + else: + pct_30 = 0.0 + # max drawdown over last 90 days + lookback = df.tail(90)['Close'] + roll_max = lookback.cummax() + drawdown = (lookback / roll_max - 1).min() * 100 + return { + "最新价": f"{latest:,.2f}", + "近30日涨幅": f"{pct_30:.2f}%", + "最大回撤(近90日)": f"{drawdown:.2f}%", + } + + +def render_openbb_tab(): + st.write("使用 OpenBB(如可用)或演示数据展示市场概览。") + + col_a, col_b = st.columns([2, 1]) + with col_b: + symbol = st.text_input("股票/ETF 代码", value="AAPL") + days = st.slider("时间窗口(天)", 90, 720, 365, step=30) + obb_ready = _check_openbb_installed() + if obb_ready: + st.success("OpenBB 已安装 ✅") + else: + st.info("未检测到 OpenBB,将使用演示数据。可在 requirements.txt 中加入 openbb 后安装启用。") + + with col_a: + df = _load_price_data(symbol, days) + if df is None or df.empty: + st.warning("未获取到数据") + return + # 绘制收盘价 + if 'Date' in df.columns and 'Close' in df.columns: + fig = px.line(df, x='Date', y='Close', title=f"{symbol.upper()} 收盘价") + st.plotly_chart(fig, use_container_width=True) + else: + st.dataframe(df.head()) + + # KPI 卡片 + st.markdown("#### 关键指标") + kpis = _kpis_from_df(df) + k1, k2, k3 = st.columns(3) + k1.metric("最新价", kpis["最新价"]) + k2.metric("近30日涨幅", kpis["近30日涨幅"]) + k3.metric("最大回撤(近90日)", kpis["最大回撤(近90日)"]) + + # 未来:基本面、新闻、情绪等组件占位 + with st.expander("🚧 更多组件(即将推出)"): + st.write("基本面卡片、新闻与情绪、宏观指标、策略筛选等将逐步接入。") \ No newline at end of file diff --git a/jixia_academy/ui/streamlit/tabs/tianxia_tab.py b/jixia_academy/ui/streamlit/tabs/tianxia_tab.py new file mode 100644 index 0000000..2af39ba --- /dev/null +++ b/jixia_academy/ui/streamlit/tabs/tianxia_tab.py @@ -0,0 +1,436 @@ +""" +天下体系 - 儒门天下观资本生态分析Tab +基于"天命树"结构模型分析全球资本市场权力结构 + +重构版本: +- 移除硬编码API密钥 +- 使用统一配置管理 +- 改进数据结构 +- 增强错误处理 +""" + +import streamlit as st +import pandas as pd +import plotly.express as px +from datetime import datetime +import time +import random +from typing import Dict, List, Any, Optional +from dataclasses import dataclass + +# 导入配置管理 +try: + from config.settings import get_rapidapi_key +except ImportError: + # 如果配置模块不可用,使用环境变量 + import os + def get_rapidapi_key(): + return os.getenv('RAPIDAPI_KEY', '') + +@dataclass +class StockEntity: + """股票实体数据类""" + symbol: str + name: str + role: str + dependency: Optional[str] = None + serves: Optional[str] = None + type: Optional[str] = None + +@dataclass +class EcosystemData: + """生态系统数据类""" + tianzi: Dict[str, str] + dafu: List[StockEntity] + shi: List[StockEntity] + jiajie: List[StockEntity] + +class TianxiaAnalyzer: + """天下体系分析器 - 天命树结构分析""" + + def __init__(self): + """初始化分析器""" + try: + self.rapidapi_key = get_rapidapi_key() + except Exception: + self.rapidapi_key = "" + st.warning("⚠️ 未配置RapidAPI密钥,将使用模拟数据") + + # 定义三大天命树生态系统 + self.ecosystems = self._initialize_ecosystems() + + def _initialize_ecosystems(self) -> Dict[str, EcosystemData]: + """初始化生态系统数据""" + return { + 'AI': EcosystemData( + tianzi={'symbol': 'NVDA', 'name': 'NVIDIA', 'tianming': 'CUDA + GPU硬件,定义AI计算范式'}, + dafu=[ + StockEntity('TSM', 'TSMC', '芯片代工', '高端芯片唯一代工厂'), + StockEntity('000660.SZ', 'SK Hynix', 'HBM内存', 'GPU性能关键'), + StockEntity('MU', 'Micron', 'HBM内存', 'GPU性能关键'), + StockEntity('SMCI', 'Supermicro', '服务器集成', 'GPU转化为计算能力') + ], + shi=[ + StockEntity('ASML', 'ASML', '光刻设备', serves='TSMC'), + StockEntity('AMAT', 'Applied Materials', '半导体设备', serves='TSMC') + ], + jiajie=[ + StockEntity('AMD', 'AMD', '竞争对手', type='竞争天子'), + StockEntity('GOOGL', 'Google', '云计算', type='云计算天子'), + StockEntity('AMZN', 'Amazon', '云计算', type='云计算天子') + ] + ), + 'EV': EcosystemData( + tianzi={'symbol': 'TSLA', 'name': 'Tesla', 'tianming': '软件定义汽车 + 超级充电网络'}, + dafu=[ + StockEntity('300750.SZ', 'CATL', '动力电池', '动力系统基石'), + StockEntity('6752.T', 'Panasonic', '动力电池', '动力系统基石'), + StockEntity('ALB', 'Albemarle', '锂矿', '源头命脉'), + StockEntity('002460.SZ', 'Ganfeng Lithium', '锂矿', '源头命脉') + ], + shi=[ + StockEntity('002497.SZ', 'Yahua Industrial', '氢氧化锂', serves='CATL'), + StockEntity('002850.SZ', 'Kedali', '精密结构件', serves='CATL') + ], + jiajie=[ + StockEntity('002594.SZ', 'BYD', '电动车', type='诸侯'), + StockEntity('VWAGY', 'Volkswagen', '传统车企', type='诸侯'), + StockEntity('F', 'Ford', '传统车企', type='诸侯') + ] + ), + 'Consumer_Electronics': EcosystemData( + tianzi={'symbol': 'AAPL', 'name': 'Apple', 'tianming': 'iOS + App Store生态系统'}, + dafu=[ + StockEntity('2317.TW', 'Foxconn', '代工制造', '物理执行者'), + StockEntity('TSM', 'TSMC', '芯片代工', '性能优势保障'), + StockEntity('005930.KS', 'Samsung Display', '屏幕供应', '显示技术'), + StockEntity('QCOM', 'Qualcomm', '基带芯片', '通信命脉') + ], + shi=[ + StockEntity('002475.SZ', 'Luxshare', '精密制造', serves='Foxconn'), + StockEntity('002241.SZ', 'Goertek', '声学器件', serves='Foxconn') + ], + jiajie=[ + StockEntity('005930.KS', 'Samsung', '手机制造', type='亦敌亦友天子'), + StockEntity('1810.HK', 'Xiaomi', '手机制造', type='诸侯'), + StockEntity('NVDA', 'NVIDIA', 'AI芯片', type='跨生态天子') + ] + ) + } + + def get_stock_data(self, symbol: str) -> Dict[str, Any]: + """ + 获取股票数据 + + Args: + symbol: 股票代码 + + Returns: + 股票数据字典 + """ + # TODO: 实现真实API调用 + # 目前使用模拟数据 + try: + return { + 'price': round(random.uniform(50, 500), 2), + 'change_pct': round(random.uniform(-5, 5), 2), + 'market_cap': f"{random.randint(100, 3000)}B", + 'volume': random.randint(1000000, 100000000) + } + except Exception: + return { + 'price': 'N/A', + 'change_pct': 0, + 'market_cap': 'N/A', + 'volume': 'N/A' + } + + def create_tianming_card(self, ecosystem_name: str, ecosystem_data: EcosystemData) -> None: + """ + 创建天命卡片 + + Args: + ecosystem_name: 生态系统名称 + ecosystem_data: 生态系统数据 + """ + tianzi = ecosystem_data.tianzi + stock_data = self.get_stock_data(tianzi['symbol']) + + st.markdown(f"### 👑 {ecosystem_name} 天命树") + + # 天子信息 + col1, col2, col3 = st.columns([1, 2, 1]) + + with col1: + st.markdown("#### 🌟 天子") + st.markdown(f"**{tianzi['name']}** ({tianzi['symbol']})") + + with col2: + st.markdown("#### 📜 天命") + st.info(tianzi['tianming']) + + with col3: + st.metric( + label="股价", + value=f"${stock_data['price']}", + delta=f"{stock_data['change_pct']:+.2f}%" + ) + + # 大夫层级 + if ecosystem_data.dafu: + st.markdown("#### 🏛️ 大夫 (核心依赖)") + dafu_cols = st.columns(min(len(ecosystem_data.dafu), 4)) + + for i, dafu in enumerate(ecosystem_data.dafu): + col_index = i % 4 + with dafu_cols[col_index]: + data = self.get_stock_data(dafu.symbol) + st.metric( + label=f"{dafu.name}", + value=f"${data['price']}", + delta=f"{data['change_pct']:+.2f}%" + ) + st.caption(f"**{dafu.role}**: {dafu.dependency}") + + # 士层级 + if ecosystem_data.shi: + st.markdown("#### ⚔️ 士 (专业供应商)") + shi_cols = st.columns(min(len(ecosystem_data.shi), 3)) + + for i, shi in enumerate(ecosystem_data.shi): + col_index = i % 3 + with shi_cols[col_index]: + data = self.get_stock_data(shi.symbol) + st.metric( + label=f"{shi.name}", + value=f"${data['price']}", + delta=f"{data['change_pct']:+.2f}%" + ) + st.caption(f"**{shi.role}** → 服务于{shi.serves}") + + # 嫁接关系 + if ecosystem_data.jiajie: + st.markdown("#### 🔗 嫁接关系 (跨生态链接)") + jiajie_cols = st.columns(min(len(ecosystem_data.jiajie), 4)) + + for i, jiajie in enumerate(ecosystem_data.jiajie): + col_index = i % 4 + with jiajie_cols[col_index]: + data = self.get_stock_data(jiajie.symbol) + st.metric( + label=f"{jiajie.name}", + value=f"${data['price']}", + delta=f"{data['change_pct']:+.2f}%" + ) + st.caption(f"**{jiajie.type}**") + + st.markdown("---") + + def create_tianming_tree_table(self) -> pd.DataFrame: + """ + 创建天命树完整表格 - 用于投资组合去相关性分析 + + Returns: + 包含所有股票信息的DataFrame + """ + st.markdown("### 📋 天命树完整表格 - 投资组合去相关性分析") + st.markdown("**核心理念**: 投资组合的本质是去相关性 - 从不同root下的不同spine下的不同leaf进行配置") + + all_stocks = [] + + for eco_name, eco_data in self.ecosystems.items(): + # 天子 + tianzi = eco_data.tianzi + stock_data = self.get_stock_data(tianzi['symbol']) + all_stocks.append({ + 'Root': eco_name, + 'Level': '👑 天子', + 'Symbol': tianzi['symbol'], + 'Company': tianzi['name'], + 'Role': '定义范式', + 'Dependency_Path': f"{eco_name}", + 'Price': stock_data['price'], + 'Change%': stock_data['change_pct'], + 'Market_Cap': stock_data['market_cap'], + 'Correlation_Risk': '极高 - 生态核心' + }) + + # 大夫 + for dafu in eco_data.dafu: + stock_data = self.get_stock_data(dafu.symbol) + all_stocks.append({ + 'Root': eco_name, + 'Level': '🏛️ 大夫', + 'Symbol': dafu.symbol, + 'Company': dafu.name, + 'Role': dafu.role, + 'Dependency_Path': f"{eco_name} → {tianzi['name']} → {dafu.name}", + 'Price': stock_data['price'], + 'Change%': stock_data['change_pct'], + 'Market_Cap': stock_data['market_cap'], + 'Correlation_Risk': '高 - 深度绑定天子' + }) + + # 士 + for shi in eco_data.shi: + stock_data = self.get_stock_data(shi.symbol) + all_stocks.append({ + 'Root': eco_name, + 'Level': '⚔️ 士', + 'Symbol': shi.symbol, + 'Company': shi.name, + 'Role': shi.role, + 'Dependency_Path': f"{eco_name} → {shi.serves} → {shi.name}", + 'Price': stock_data['price'], + 'Change%': stock_data['change_pct'], + 'Market_Cap': stock_data['market_cap'], + 'Correlation_Risk': '中 - 专业供应商' + }) + + # 嫁接 + for jiajie in eco_data.jiajie: + stock_data = self.get_stock_data(jiajie.symbol) + all_stocks.append({ + 'Root': '🔗 跨生态', + 'Level': '🔗 嫁接', + 'Symbol': jiajie.symbol, + 'Company': jiajie.name, + 'Role': jiajie.type or jiajie.role, + 'Dependency_Path': f"多生态嫁接 → {jiajie.name}", + 'Price': stock_data['price'], + 'Change%': stock_data['change_pct'], + 'Market_Cap': stock_data['market_cap'], + 'Correlation_Risk': '低 - 多元化依赖' + }) + + df = pd.DataFrame(all_stocks) + + # 显示表格 + st.dataframe( + df, + use_container_width=True, + column_config={ + "Root": st.column_config.TextColumn("生态根节点", width="small"), + "Level": st.column_config.TextColumn("层级", width="small"), + "Symbol": st.column_config.TextColumn("代码", width="small"), + "Company": st.column_config.TextColumn("公司", width="medium"), + "Role": st.column_config.TextColumn("角色", width="medium"), + "Dependency_Path": st.column_config.TextColumn("依赖路径", width="large"), + "Price": st.column_config.NumberColumn("股价", format="$%.2f"), + "Change%": st.column_config.NumberColumn("涨跌幅", format="%.2f%%"), + "Market_Cap": st.column_config.TextColumn("市值", width="small"), + "Correlation_Risk": st.column_config.TextColumn("相关性风险", width="medium") + } + ) + + return df + +def render_tianxia_tab() -> None: + """渲染天下体系Tab""" + + # 页面标题 + st.markdown("### 🏛️ 天下体系 - 儒门天下观资本生态分析") + st.markdown("**基于'天命树'结构模型,穿透市场表象,绘制全球资本市场真实的权力结构**") + st.markdown("---") + + # 初始化分析器 + analyzer = TianxiaAnalyzer() + + # 控制面板 + col1, col2, col3 = st.columns([1, 1, 2]) + with col1: + auto_refresh = st.checkbox("🔄 自动刷新", value=False, key="tianxia_auto_refresh") + with col2: + if st.button("🏛️ 扫描天下", type="primary", key="tianxia_scan_btn"): + st.session_state.trigger_tianxia_scan = True + with col3: + st.markdown("*正在分析全球资本生态权力结构...*") + + # 理论介绍 + with st.expander("📚 天命树理论基础"): + st.markdown(""" + ### 🏛️ 儒门天下观核心思想 + + **两大哲学基石:** + 1. **结构非平权**: 资本宇宙本质是不平权的、层级森严的树状结构 + 2. **天命与脉络**: 每个生态都有唯一的"根节点"(天子),拥有定义整个生态的"天命" + + **四层架构:** + - **👑 天子**: 定义范式的平台型公司 (如Apple, NVIDIA, Tesla) + - **🏛️ 大夫**: 深度绑定天子的核心供应商 (如TSMC, CATL) + - **⚔️ 士**: 专业供应商和服务商 (如ASML, Luxshare) + - **🔗 嫁接**: 跨生态的策略性链接关系 + """) + + # 自动刷新逻辑 + if auto_refresh: + time.sleep(60) + st.rerun() + + # 触发扫描或显示数据 + if st.session_state.get('trigger_tianxia_scan', False) or 'tianxia_scan_time' not in st.session_state: + with st.spinner("🏛️ 正在扫描天下体系..."): + st.session_state.tianxia_scan_time = datetime.now() + st.session_state.trigger_tianxia_scan = False + + # 显示扫描时间 + if 'tianxia_scan_time' in st.session_state: + st.info(f"📅 最后扫描时间: {st.session_state.tianxia_scan_time.strftime('%Y-%m-%d %H:%M:%S')}") + + # 显示三大生态系统 + st.markdown("## 🌍 三大天命树生态系统") + + # 分析模式选择 + analysis_mode = st.selectbox( + "选择分析模式", + ["生态系统分析", "投资组合去相关性分析"], + key="tianxia_analysis_mode" + ) + + if analysis_mode == "生态系统分析": + # 生态系统选择 + selected_ecosystem = st.selectbox( + "选择要分析的生态系统", + ["全部", "AI", "EV", "Consumer_Electronics"], + format_func=lambda x: { + "全部": "🌍 全部生态系统", + "AI": "🤖 AI人工智能生态", + "EV": "⚡ 电动汽车生态", + "Consumer_Electronics": "📱 消费电子生态" + }[x], + key="tianxia_ecosystem_select" + ) + + if selected_ecosystem == "全部": + # 显示所有生态系统 + for eco_name, eco_data in analyzer.ecosystems.items(): + analyzer.create_tianming_card(eco_name, eco_data) + else: + # 显示选定的生态系统 + analyzer.create_tianming_card(selected_ecosystem, analyzer.ecosystems[selected_ecosystem]) + + else: # 投资组合去相关性分析 + st.markdown("## 🎯 投资组合去相关性分析") + st.info("**核心理念**: 真正的分散投资是从不同的root(天子)下的不同spine(大夫)下的不同leaf(士)进行配置") + + # 创建完整天命树表格 + df = analyzer.create_tianming_tree_table() + + # 页面底部说明 + st.markdown("---") + st.markdown(""" + ### 🎯 天下体系核心洞察 + + **权力结构分析**: + - **AI生态**: NVIDIA通过CUDA平台统治AI计算,TSMC是关键"嫁接"节点 + - **电动车生态**: Tesla定义软件汽车范式,CATL掌握电池命脉 + - **消费电子生态**: Apple建立iOS护城河,供应链高度集中化 + + **投资策略启示**: + 1. **投资天子**: 寻找定义范式的平台型公司 + 2. **关注大夫**: 深度绑定天子的核心供应商往往被低估 + 3. **警惕嫁接**: 被多个天子"嫁接"的公司风险与机会并存 + 4. **避开士层**: 缺乏议价能力的专业供应商投资价值有限 + + ⚠️ **免责声明**: 天下体系分析仅供参考,投资有风险,决策需谨慎! + """) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c0aa296..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1879 +0,0 @@ -{ - "name": "hyperdrive-neondb-test", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "hyperdrive-neondb-test", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "node-fetch": "^3.3.2", - "pg": "^8.11.3" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240129.0", - "@types/node": "^20.11.16", - "@types/pg": "^8.10.9", - "typescript": "^5.3.3", - "wrangler": "^3.28.2" - } - }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.4", - "resolved": "https://registry.npmmirror.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", - "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", - "dev": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "mime": "^3.0.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/@cloudflare/unenv-preset": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", - "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.14", - "workerd": "^1.20250124.0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", - "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", - "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", - "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", - "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", - "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workers-types": { - "version": "4.20250813.0", - "resolved": "https://registry.npmmirror.com/@cloudflare/workers-types/-/workers-types-4.20250813.0.tgz", - "integrity": "sha512-RFFjomDndGR+p7ug1HWDlW21qOJyRZbmI99dUtuR9tmwJbSZhUUnSFmzok9lBYVfkMMrO1O5vmB+IlgiecgLEA==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmmirror.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", - "dev": true, - "license": "ISC", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@types/node": { - "version": "20.19.10", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.10.tgz", - "integrity": "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/pg": { - "version": "8.15.5", - "resolved": "https://registry.npmmirror.com/@types/pg/-/pg-8.15.5.tgz", - "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmmirror.com/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmmirror.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true, - "license": "MIT" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true, - "license": "MIT" - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmmirror.com/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/miniflare": { - "version": "3.20250718.1", - "resolved": "https://registry.npmmirror.com/miniflare/-/miniflare-3.20250718.1.tgz", - "integrity": "sha512-9QAOHVKIVHmnQ1dJT9Fls8aVA8R5JjEizzV889Dinq/+bEPltqIepCvm9Z+fbNUgLvV7D/H1NUk8VdlLRgp9Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "8.14.0", - "acorn-walk": "8.3.2", - "exit-hook": "2.2.1", - "glob-to-regexp": "0.4.1", - "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250718.0", - "ws": "8.18.0", - "youch": "3.3.4", - "zod": "3.22.3" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-fetch/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmmirror.com/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.7" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmmirror.com/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmmirror.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmmirror.com/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmmirror.com/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unenv": { - "version": "2.0.0-rc.14", - "resolved": "https://registry.npmmirror.com/unenv/-/unenv-2.0.0-rc.14.tgz", - "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "exsolve": "^1.0.1", - "ohash": "^2.0.10", - "pathe": "^2.0.3", - "ufo": "^1.5.4" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerd": { - "version": "1.20250718.0", - "resolved": "https://registry.npmmirror.com/workerd/-/workerd-1.20250718.0.tgz", - "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250718.0", - "@cloudflare/workerd-darwin-arm64": "1.20250718.0", - "@cloudflare/workerd-linux-64": "1.20250718.0", - "@cloudflare/workerd-linux-arm64": "1.20250718.0", - "@cloudflare/workerd-windows-64": "1.20250718.0" - } - }, - "node_modules/wrangler": { - "version": "3.114.13", - "resolved": "https://registry.npmmirror.com/wrangler/-/wrangler-3.114.13.tgz", - "integrity": "sha512-bJbKJGTjClEp5XeyjiIKXodHW6j14ZsXuMphjvTZSwkQjGg6QlOol74/44d/u1Uso+hhIzYFg6m/d/1ggxUqWQ==", - "dev": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "@cloudflare/kv-asset-handler": "0.3.4", - "@cloudflare/unenv-preset": "2.0.2", - "@esbuild-plugins/node-globals-polyfill": "0.2.3", - "@esbuild-plugins/node-modules-polyfill": "0.2.2", - "blake3-wasm": "2.1.5", - "esbuild": "0.17.19", - "miniflare": "3.20250718.1", - "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.14", - "workerd": "1.20250718.0" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.17.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20250408.0" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmmirror.com/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" - } - }, - "node_modules/zod": { - "version": "3.22.3", - "resolved": "https://registry.npmmirror.com/zod/-/zod-3.22.3.tgz", - "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index feed0e2..0000000 --- a/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "hyperdrive-neondb-test", - "version": "1.0.0", - "description": "Cloudflare Worker to test Hyperdrive connection with NeonDB", - "main": "src/index.ts", - "scripts": { - "dev": "wrangler dev", - "deploy": "wrangler deploy", - "test": "wrangler dev --local" - }, - "dependencies": { - "node-fetch": "^3.3.2", - "pg": "^8.11.3" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240129.0", - "@types/node": "^20.11.16", - "@types/pg": "^8.10.9", - "typescript": "^5.3.3", - "wrangler": "^3.28.2" - }, - "keywords": [ - "cloudflare", - "workers", - "hyperdrive", - "neondb", - "postgresql" - ], - "author": "", - "license": "MIT" -} diff --git a/scripts/start_qwen_code.sh b/scripts/start_qwen_code.sh new file mode 100644 index 0000000..6e52f88 --- /dev/null +++ b/scripts/start_qwen_code.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Qwen Code 启动脚本 +# 临时清除代理设置以避免与 Qwen Code 的冲突 + +echo "正在启动 Qwen Code..." +echo "临时清除代理设置..." + +# 保存当前代理设置 +SAVED_HTTP_PROXY="$http_proxy" +SAVED_HTTPS_PROXY="$https_proxy" +SAVED_HTTP_PROXY_UPPER="$HTTP_PROXY" +SAVED_HTTPS_PROXY_UPPER="$HTTPS_PROXY" + +# 清除代理设置 +unset http_proxy +unset https_proxy +unset HTTP_PROXY +unset HTTPS_PROXY + +echo "代理设置已临时清除" +echo "启动 Qwen Code..." + +# 启动 Qwen Code +qwen + +# 恢复代理设置 +export http_proxy="$SAVED_HTTP_PROXY" +export https_proxy="$SAVED_HTTPS_PROXY" +export HTTP_PROXY="$SAVED_HTTP_PROXY_UPPER" +export HTTPS_PROXY="$SAVED_HTTPS_PROXY_UPPER" + +echo "代理设置已恢复" \ No newline at end of file