2026-03-19 12:21:04 +08:00
|
|
|
|
"""
|
2026-03-31 14:39:17 +08:00
|
|
|
|
交互式 CLI —— 四层架构自适应分析(增强版)
|
2026-03-20 13:20:31 +08:00
|
|
|
|
用法: python cli.py [数据库路径]
|
2026-03-19 12:21:04 +08:00
|
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
|
|
|
2026-03-31 14:39:17 +08:00
|
|
|
|
from core.config import DB_PATH, LLM_CONFIG, MAX_EXPLORATION_ROUNDS, PLAYBOOK_DIR, PROJECT_ROOT
|
2026-03-19 12:21:04 +08:00
|
|
|
|
from agent import DataAnalysisAgent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_help():
|
|
|
|
|
|
print("""
|
|
|
|
|
|
可用命令:
|
2026-03-20 13:20:31 +08:00
|
|
|
|
<问题> 分析一个问题
|
2026-03-19 12:21:04 +08:00
|
|
|
|
rounds=<N> <问题> 设置探索轮数
|
2026-03-20 13:20:31 +08:00
|
|
|
|
report [主题] 整合所有分析,生成综合报告
|
2026-03-31 14:39:17 +08:00
|
|
|
|
export 导出最近一次分析结果为 CSV
|
|
|
|
|
|
reports 列出已保存的报告文件
|
2026-03-20 13:20:31 +08:00
|
|
|
|
schema 查看数据库 Schema
|
|
|
|
|
|
playbooks 查看已加载的预设剧本
|
|
|
|
|
|
regen 重新生成预设剧本
|
|
|
|
|
|
history 查看分析历史
|
|
|
|
|
|
audit 查看 SQL 审计日志
|
|
|
|
|
|
clear 清空分析历史
|
|
|
|
|
|
help 显示帮助
|
|
|
|
|
|
quit / q 退出
|
2026-03-19 12:21:04 +08:00
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-31 14:39:17 +08:00
|
|
|
|
def cmd_reports(agent):
|
|
|
|
|
|
"""列出已保存的报告"""
|
|
|
|
|
|
reports_dir = agent.reports_dir
|
|
|
|
|
|
if not os.path.isdir(reports_dir):
|
|
|
|
|
|
print("(reports 目录不存在)")
|
|
|
|
|
|
return
|
|
|
|
|
|
files = sorted([f for f in os.listdir(reports_dir) if f.endswith(".md")])
|
|
|
|
|
|
if not files:
|
|
|
|
|
|
print("(尚无保存的报告)")
|
|
|
|
|
|
return
|
|
|
|
|
|
print(f"\n📁 已保存 {len(files)} 份报告:")
|
|
|
|
|
|
for f in files:
|
|
|
|
|
|
fpath = os.path.join(reports_dir, f)
|
|
|
|
|
|
size = os.path.getsize(fpath)
|
|
|
|
|
|
print(f" 📄 {f} ({size/1024:.1f} KB)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup_readline():
|
|
|
|
|
|
"""启用命令历史(Linux/macOS)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
import readline
|
|
|
|
|
|
histfile = os.path.join(PROJECT_ROOT, ".cli_history")
|
|
|
|
|
|
try:
|
|
|
|
|
|
readline.read_history_file(histfile)
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
import atexit
|
|
|
|
|
|
atexit.register(readline.write_history_file, histfile)
|
|
|
|
|
|
readline.set_history_length(100)
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 12:21:04 +08:00
|
|
|
|
def main():
|
|
|
|
|
|
db_path = sys.argv[1] if len(sys.argv) > 1 else DB_PATH
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(db_path):
|
|
|
|
|
|
print(f"❌ 数据库不存在: {db_path}")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
if not LLM_CONFIG["api_key"]:
|
2026-03-31 14:39:17 +08:00
|
|
|
|
print("❌ LLM_API_KEY 未配置!")
|
|
|
|
|
|
print(" 请设置环境变量或创建 .env 文件:")
|
|
|
|
|
|
print(" LLM_API_KEY=your-key")
|
|
|
|
|
|
print(" LLM_BASE_URL=https://api.openai.com/v1")
|
|
|
|
|
|
print(" LLM_MODEL=gpt-4o-mini")
|
2026-03-19 12:21:04 +08:00
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
2026-03-31 14:39:17 +08:00
|
|
|
|
setup_readline()
|
|
|
|
|
|
|
2026-03-19 12:21:04 +08:00
|
|
|
|
agent = DataAnalysisAgent(db_path)
|
|
|
|
|
|
|
|
|
|
|
|
print("=" * 60)
|
|
|
|
|
|
print(" 🤖 数据分析 Agent —— 四层架构")
|
|
|
|
|
|
print("=" * 60)
|
|
|
|
|
|
print(f"\n🔗 LLM: {LLM_CONFIG['model']} @ {LLM_CONFIG['base_url']}")
|
|
|
|
|
|
print(f"🔄 最大探索轮数: {MAX_EXPLORATION_ROUNDS}")
|
|
|
|
|
|
print(f"💾 数据库: {db_path}")
|
2026-03-20 13:20:31 +08:00
|
|
|
|
print(f"📋 预设剧本: {len(agent.playbook_mgr.playbooks)} 个")
|
2026-03-19 12:21:04 +08:00
|
|
|
|
print(f"\n💬 输入分析问题(help 查看命令)\n")
|
|
|
|
|
|
|
2026-03-31 14:39:17 +08:00
|
|
|
|
last_steps = None # 记录最近一次分析的 steps,用于 export
|
|
|
|
|
|
|
2026-03-19 12:21:04 +08:00
|
|
|
|
while True:
|
|
|
|
|
|
try:
|
|
|
|
|
|
user_input = input("📊 > ").strip()
|
|
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
|
|
|
|
print("\n👋 再见!")
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if not user_input:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
cmd = user_input.lower()
|
|
|
|
|
|
|
|
|
|
|
|
if cmd in ("quit", "exit", "q"):
|
|
|
|
|
|
print("👋 再见!")
|
|
|
|
|
|
break
|
|
|
|
|
|
elif cmd == "help":
|
|
|
|
|
|
print_help()
|
|
|
|
|
|
elif cmd == "schema":
|
|
|
|
|
|
print(agent.get_schema())
|
|
|
|
|
|
elif cmd == "history":
|
|
|
|
|
|
print(agent.get_history())
|
|
|
|
|
|
elif cmd == "audit":
|
|
|
|
|
|
print(agent.get_audit())
|
|
|
|
|
|
elif cmd == "clear":
|
|
|
|
|
|
agent.clear_history()
|
2026-03-31 14:39:17 +08:00
|
|
|
|
last_steps = None
|
2026-03-19 12:21:04 +08:00
|
|
|
|
print("✅ 历史已清空")
|
2026-03-31 14:39:17 +08:00
|
|
|
|
elif cmd == "reports":
|
|
|
|
|
|
cmd_reports(agent)
|
|
|
|
|
|
elif cmd == "export":
|
|
|
|
|
|
if last_steps:
|
|
|
|
|
|
fpath = agent.export_data(last_steps)
|
|
|
|
|
|
if fpath:
|
|
|
|
|
|
print(f"✅ 导出成功: {fpath}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("⚠️ 无数据可导出")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("⚠️ 请先执行一次分析")
|
2026-03-20 13:20:31 +08:00
|
|
|
|
elif cmd.startswith("report"):
|
|
|
|
|
|
topic = user_input[6:].strip()
|
|
|
|
|
|
try:
|
|
|
|
|
|
report = agent.full_report(question=topic)
|
|
|
|
|
|
print("\n" + report)
|
|
|
|
|
|
print("\n" + "~" * 60)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"\n❌ 报告整合出错: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
elif cmd == "playbooks":
|
|
|
|
|
|
if not agent.playbook_mgr.playbooks:
|
|
|
|
|
|
print("(无预设剧本,输入 regen 让 AI 自动生成)")
|
|
|
|
|
|
else:
|
|
|
|
|
|
for i, pb in enumerate(agent.playbook_mgr.playbooks, 1):
|
|
|
|
|
|
print(f" {i}. 📋 {pb.name} — {pb.description} ({len(pb.preset_queries)} 条预设)")
|
|
|
|
|
|
elif cmd == "regen":
|
|
|
|
|
|
if os.path.isdir(PLAYBOOK_DIR):
|
|
|
|
|
|
for f in os.listdir(PLAYBOOK_DIR):
|
|
|
|
|
|
if f.startswith("auto_") and f.endswith(".json"):
|
|
|
|
|
|
os.remove(os.path.join(PLAYBOOK_DIR, f))
|
|
|
|
|
|
agent.playbook_mgr.playbooks.clear()
|
|
|
|
|
|
print("🤖 AI 正在重新生成预设剧本...")
|
|
|
|
|
|
generated = agent.playbook_mgr.auto_generate(agent.schema_text, save_dir=PLAYBOOK_DIR)
|
|
|
|
|
|
if generated:
|
|
|
|
|
|
print(f"✅ 生成 {len(generated)} 个剧本:")
|
|
|
|
|
|
for pb in generated:
|
|
|
|
|
|
print(f" 📋 {pb.name} — {pb.description}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("⚠️ 生成失败")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 解析 rounds=N
|
|
|
|
|
|
max_rounds = MAX_EXPLORATION_ROUNDS
|
|
|
|
|
|
question = user_input
|
|
|
|
|
|
if "rounds=" in question.lower():
|
|
|
|
|
|
parts = question.split("rounds=")
|
|
|
|
|
|
question = parts[0].strip()
|
|
|
|
|
|
try:
|
|
|
|
|
|
max_rounds = int(parts[1].strip().split()[0])
|
|
|
|
|
|
except (ValueError, IndexError):
|
|
|
|
|
|
pass
|
2026-03-19 12:21:04 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-20 13:20:31 +08:00
|
|
|
|
report = agent.analyze(question, max_rounds=max_rounds)
|
2026-03-31 14:39:17 +08:00
|
|
|
|
# 保存 steps 用于 export
|
|
|
|
|
|
if agent.context.sessions:
|
|
|
|
|
|
last_steps = agent.context.sessions[-1].steps
|
2026-03-20 13:20:31 +08:00
|
|
|
|
print("\n" + report)
|
|
|
|
|
|
print("\n" + "~" * 60)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"\n❌ 分析出错: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2026-03-19 12:21:04 +08:00
|
|
|
|
|
|
|
|
|
|
print("\n📋 本次会话审计:")
|
|
|
|
|
|
print(agent.get_audit())
|
2026-03-20 13:20:31 +08:00
|
|
|
|
agent.close()
|
2026-03-19 12:21:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|