erlangshen 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.claude/agents/equity-agent.md +26 -0
  2. package/.claude/agents/macro-agent.md +25 -0
  3. package/.claude/commands/analyze.md +40 -0
  4. package/.claude/commands/macro.md +29 -0
  5. package/.claude/settings.json +12 -0
  6. package/CODEX_GOAL.md +46 -0
  7. package/README.md +206 -0
  8. package/bin/cli.js +67 -0
  9. package/bin/erlangshen +2 -0
  10. package/bin/xiaoergod +2 -0
  11. package/frontend/index.html +700 -0
  12. package/knowledge/crypto_guide.md +147 -0
  13. package/knowledge/economic_indicators.md +125 -0
  14. package/knowledge/financial_glossary.md +148 -0
  15. package/knowledge/first_principles.md +50 -0
  16. package/knowledge/first_principles_deep.md +115 -0
  17. package/knowledge/global_markets.md +173 -0
  18. package/knowledge/insights.md +141 -0
  19. package/knowledge/market_basics.md +116 -0
  20. package/knowledge/memos/session_20260513_003616.json +6 -0
  21. package/knowledge/memos/session_20260513_003822.json +6 -0
  22. package/knowledge/risk_management.md +151 -0
  23. package/knowledge/team_context.md +42 -0
  24. package/knowledge/trading_strategies.md +114 -0
  25. package/package.json +42 -0
  26. package/requirements.txt +14 -0
  27. package/scripts/postinstall.js +188 -0
  28. package/scripts/preuninstall.js +22 -0
  29. package/src/__init__.py +4 -0
  30. package/src/__pycache__/__init__.cpython-313.pyc +0 -0
  31. package/src/agents/__init__.py +3 -0
  32. package/src/agents/base.py +103 -0
  33. package/src/agents/base_agent.py +86 -0
  34. package/src/agents/equity.py +136 -0
  35. package/src/agents/equity_agent.py +91 -0
  36. package/src/agents/erlang.py +165 -0
  37. package/src/agents/macro.py +137 -0
  38. package/src/agents/macro_agent.py +81 -0
  39. package/src/agents/multi_asset.py +147 -0
  40. package/src/agents/multi_asset_agent.py +87 -0
  41. package/src/api/__init__.py +1 -0
  42. package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
  43. package/src/api/__pycache__/server.cpython-313.pyc +0 -0
  44. package/src/api/cli.py +435 -0
  45. package/src/api/cli_enhanced.py +537 -0
  46. package/src/api/server.py +266 -0
  47. package/src/brain.py +200 -0
  48. package/src/cli.py +153 -0
  49. package/src/commands/__init__.py +3 -0
  50. package/src/commands/analyze.py +131 -0
  51. package/src/commands/macro.py +100 -0
  52. package/src/commands/memo.py +216 -0
  53. package/src/commands/portfolio.py +154 -0
  54. package/src/commands/report.py +228 -0
  55. package/src/commands/risk.py +183 -0
  56. package/src/commands/search.py +183 -0
  57. package/src/commands/stock.py +124 -0
  58. package/src/config.py +327 -0
  59. package/src/core/__init__.py +1 -0
  60. package/src/core/brain.py +645 -0
  61. package/src/core/cerebellum.py +175 -0
  62. package/src/core/investment_universe.py +423 -0
  63. package/src/core/knowledge.py +207 -0
  64. package/src/core/memory.py +115 -0
  65. package/src/hooks/__init__.py +3 -0
  66. package/src/hooks/session_end.py +57 -0
  67. package/src/hooks/session_start.py +75 -0
  68. package/src/knowledge/__init__.py +1 -0
  69. package/src/mcp/__init__.py +3 -0
  70. package/src/mcp/feishu.py +331 -0
  71. package/src/mcp/fund_tools.py +323 -0
  72. package/src/mcp/macro.py +452 -0
  73. package/src/mcp/market.py +331 -0
  74. package/src/mcp/registry.py +168 -0
  75. package/src/network/__init__.py +15 -0
  76. package/src/network/detector.py +125 -0
  77. package/src/network/proxy.py +199 -0
  78. package/src/network/router.py +103 -0
  79. package/src/prompts/__init__.py +1 -0
  80. package/src/prompts/analysis_framework.md +164 -0
  81. package/src/prompts/persona.md +65 -0
  82. package/src/prompts/report_template.md +144 -0
  83. package/src/skills/__init__.py +3 -0
  84. package/src/skills/framework.py +105 -0
  85. package/src/skills/templates.py +342 -0
  86. package/src/tools/__init__.py +1 -0
  87. package/src/tools/file_tools.py +209 -0
  88. package/src/tools/macro_tools.py +152 -0
  89. package/src/tools/market_tools.py +1172 -0
  90. package/src/tools/registry.py +398 -0
  91. package/src/tools/search_tools.py +777 -0
  92. package/tests/__init__.py +1 -0
  93. package/tests/test_erlangshen.py +140 -0
@@ -0,0 +1,147 @@
1
+ """
2
+ 多资产分析师 Agent
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+ from src.agents.base import BaseAgent
7
+ from src.brain import Brain
8
+ from src.mcp.registry import MCPRegistry
9
+
10
+
11
+ class MultiAssetAgent(BaseAgent):
12
+ """
13
+ 多资产分析师
14
+
15
+ 专注于跨资产配置、组合优化、风险预算
16
+ """
17
+
18
+ def __init__(self, brain: Brain, mcp: MCPRegistry):
19
+ super().__init__(brain, mcp)
20
+ self.role = "多资产分析师"
21
+
22
+ def _build_system_prompt(self) -> str:
23
+ return """你是一位资深多资产分析师,专注于:
24
+ - 大类资产配置 (股票、债券、商品、现金)
25
+ - 跨资产相关性分析
26
+ - 组合构建和优化
27
+ - 风险预算管理
28
+ - 因子暴露分析
29
+ - 绝对收益策略
30
+
31
+ 分析原则:
32
+ 1. 风险分散:通过多资产配置降低组合风险
33
+ 2. 风险预算:基于风险预算进行资产配置
34
+ 3. 动态调整:根据市场环境动态调整配置
35
+ 4. 成本控制:关注交易成本和滑点
36
+
37
+ 输出格式:
38
+ ## 核心观点
39
+ (当前资产配置的主要观点)
40
+
41
+ ## 资产配置
42
+ (各类资产配置比例及逻辑)
43
+
44
+ ## 相关性分析
45
+ (资产间相关性变化)
46
+
47
+ ## 风险分析
48
+ (组合整体风险暴露)
49
+
50
+ ## 业绩归因
51
+ (收益来源分析)
52
+
53
+ ## 调整建议
54
+ (近期配置调整建议)
55
+ """
56
+
57
+ async def process(self, query: str, **kwargs) -> str:
58
+ """
59
+ 处理多资产分析查询
60
+
61
+ Args:
62
+ query: 分析查询
63
+ **kwargs: 其他参数 (assets等)
64
+ """
65
+ assets = kwargs.get("assets", [])
66
+
67
+ data = {}
68
+
69
+ # 获取各类资产数据
70
+ for asset in assets:
71
+ asset_type = asset.get("type")
72
+ symbol = asset.get("symbol")
73
+
74
+ if asset_type == "stock":
75
+ result = await self.call_mcp_tool("get_stock_price", symbol=symbol)
76
+ data[f"stock_{symbol}"] = result
77
+ elif asset_type == "index":
78
+ result = await self.call_mcp_tool("get_index_quote", index_code=symbol)
79
+ data[f"index_{symbol}"] = result
80
+ elif asset_type == "futures":
81
+ result = await self.call_mcp_tool("get_futures_price", contract=symbol)
82
+ data[f"futures_{symbol}"] = result
83
+
84
+ return await self.analyze(query, data=data)
85
+
86
+ async def analyze_allocation(
87
+ self,
88
+ portfolio: Dict[str, float],
89
+ benchmark: Optional[Dict[str, float]] = None
90
+ ) -> str:
91
+ """
92
+ 分析资产配置
93
+
94
+ Args:
95
+ portfolio: 当前组合配置 (asset: percentage)
96
+ benchmark: 基准配置 (可选)
97
+ """
98
+ data = {
99
+ "portfolio": portfolio,
100
+ "benchmark": benchmark
101
+ }
102
+
103
+ prompt = "请分析当前资产配置"
104
+ return await self.analyze(prompt, framework="资产配置", data=data)
105
+
106
+ async def rebalance_suggestion(
107
+ self,
108
+ current_allocation: Dict[str, float],
109
+ target_allocation: Dict[str, float],
110
+ threshold: float = 0.05
111
+ ) -> str:
112
+ """
113
+ 生成再平衡建议
114
+
115
+ Args:
116
+ current_allocation: 当前配置
117
+ target_allocation: 目标配置
118
+ threshold: 再平衡阈值
119
+ """
120
+ data = {
121
+ "current": current_allocation,
122
+ "target": target_allocation,
123
+ "threshold": threshold
124
+ }
125
+
126
+ prompt = "请基于以下数据提供再平衡建议"
127
+ return await self.analyze(prompt, framework="再平衡分析", data=data)
128
+
129
+ async def risk_analysis(
130
+ self,
131
+ portfolio: Dict[str, float],
132
+ confidence_level: float = 0.95
133
+ ) -> str:
134
+ """
135
+ 组合风险分析
136
+
137
+ Args:
138
+ portfolio: 组合配置
139
+ confidence_level: 置信水平
140
+ """
141
+ data = {
142
+ "portfolio": portfolio,
143
+ "confidence_level": confidence_level
144
+ }
145
+
146
+ prompt = f"请进行组合风险分析 (置信水平: {confidence_level})"
147
+ return await self.analyze(prompt, framework="风险分析", data=data)
@@ -0,0 +1,87 @@
1
+ """
2
+ Multi Asset Agent - 多资产分析师
3
+ 跨资产类别分析、相对价值分析
4
+ """
5
+ from typing import Optional
6
+ from loguru import logger
7
+
8
+ from .base_agent import BaseAgent
9
+
10
+
11
+ class MultiAssetAgent(BaseAgent):
12
+ """
13
+ 多资产分析师智能体
14
+
15
+ 能力:
16
+ - 跨资产类别分析
17
+ - 相对价值比较
18
+ - 多资产组合分析
19
+ - 风险收益特征分析
20
+ """
21
+
22
+ def __init__(self, tools: Optional[dict] = None):
23
+ super().__init__(
24
+ name="多资产分析师",
25
+ description="专注于跨资产分析和相对价值比较",
26
+ tools=tools,
27
+ )
28
+
29
+ async def process(self, query: str, context: Optional[dict] = None) -> dict:
30
+ """
31
+ 处理多资产分析查询
32
+
33
+ Args:
34
+ query: 分析问题
35
+ context: 上下文
36
+
37
+ Returns:
38
+ dict 包含分析结论
39
+ """
40
+ logger.info(f"MultiAssetAgent processing: {query[:80]}")
41
+ context = context or {}
42
+
43
+ # 1. 确定涉及的资产类别
44
+ asset_classes = self._identify_assets(query)
45
+
46
+ # 2. 获取各类资产数据
47
+ data = {}
48
+ for asset_class in asset_classes:
49
+ if asset_class == "stock":
50
+ if "market_tools" in self.tools:
51
+ data["stocks"] = await self.call_tool("market_tools.get_stock_history", symbol="000001", days=30)
52
+ elif asset_class == "bond":
53
+ pass # 债券数据
54
+ elif asset_class == "gold":
55
+ pass # 黄金数据
56
+ elif asset_class == "fx":
57
+ if "macro_tools" in self.tools:
58
+ data["fx"] = await self.call_tool("macro_tools.get_currency_rates")
59
+
60
+ # 3. 相对价值分析
61
+ analysis = {
62
+ "query": query,
63
+ "asset_classes": asset_classes,
64
+ "data": data,
65
+ "relative_value": "相对价值分析结论",
66
+ "recommendation": "配置建议",
67
+ }
68
+
69
+ return analysis
70
+
71
+ def _identify_assets(self, query: str) -> list[str]:
72
+ """识别查询中涉及的资产类别"""
73
+ q = query.lower()
74
+ assets = []
75
+ if any(k in q for k in ["股票", "a股", "美股", "港股"]):
76
+ assets.append("stock")
77
+ if any(k in q for k in ["债券", "国债", "信用债"]):
78
+ assets.append("bond")
79
+ if any(k in q for k in ["黄金", "贵金属"]):
80
+ assets.append("gold")
81
+ if any(k in q for k in ["外汇", "汇率", "美元", "人民币"]):
82
+ assets.append("fx")
83
+ if any(k in q for k in ["原油", "大宗商品", "商品"]):
84
+ assets.append("commodity")
85
+ if not assets:
86
+ assets = ["stock", "bond", "gold", "fx"] # 默认全品类
87
+ return assets
@@ -0,0 +1 @@
1
+ """API server"""
package/src/api/cli.py ADDED
@@ -0,0 +1,435 @@
1
+ """
2
+ CLI - 二郎神命令行界面
3
+ 参考 Claude Code 的 UI 设计优化版本
4
+ """
5
+ import sys
6
+ import asyncio
7
+ import os
8
+ import time
9
+ import threading
10
+ from pathlib import Path
11
+ from dataclasses import dataclass
12
+ from typing import Optional, Callable
13
+
14
+ # 添加src到路径
15
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
16
+
17
+ from loguru import logger
18
+
19
+ from src.core.brain import Brain
20
+ from src.core.memory import Memory
21
+ from src.core.knowledge import KnowledgeBase
22
+ from src.tools.market_tools import MarketTools
23
+ from src.tools.macro_tools import MacroTools
24
+ from src.tools.search_tools import SearchTools
25
+ from src.tools.file_tools import FileTools
26
+ from src.agents.erlang import 二郎神
27
+
28
+
29
+ # ============================================================
30
+ # ANSI 颜色定义
31
+ # ============================================================
32
+ @dataclass
33
+ class Colors:
34
+ RESET = "\033[0m"
35
+ BOLD = "\033[1m"
36
+ DIM = "\033[2m"
37
+
38
+ # 前景色
39
+ BLACK = "\033[30m"
40
+ RED = "\033[31m"
41
+ GREEN = "\033[32m"
42
+ YELLOW = "\033[33m"
43
+ BLUE = "\033[34m"
44
+ MAGENTA = "\033[35m"
45
+ CYAN = "\033[36m"
46
+ WHITE = "\033[37m"
47
+
48
+ # 亮色
49
+ BRIGHT_RED = "\033[91m"
50
+ BRIGHT_GREEN = "\033[92m"
51
+ BRIGHT_YELLOW = "\033[93m"
52
+ BRIGHT_BLUE = "\033[94m"
53
+ BRIGHT_CYAN = "\033[96m"
54
+
55
+ # 背景色
56
+ BG_BLACK = "\033[40m"
57
+ BG_BLUE = "\033[44m"
58
+ BG_CYAN = "\033[46m"
59
+
60
+
61
+ def c(color: str, text: str) -> str:
62
+ """彩色输出辅助函数"""
63
+ return f"{color}{text}{Colors.RESET}"
64
+
65
+
66
+ # ============================================================
67
+ # Spinner 进度指示器
68
+ # ============================================================
69
+ class Spinner:
70
+ """优雅的加载动画"""
71
+
72
+ FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
73
+
74
+ def __init__(self, message: str = "二郎神正在思考"):
75
+ self.message = message
76
+ self.running = False
77
+ self.thread: Optional[threading.Thread] = None
78
+ self._stop_event = threading.Event()
79
+
80
+ def start(self):
81
+ """启动 spinner"""
82
+ self.running = True
83
+ self._stop_event.clear()
84
+ self.thread = threading.Thread(target=self._spin, daemon=True)
85
+ self.thread.start()
86
+
87
+ def stop(self, final_message: Optional[str] = None):
88
+ """停止 spinner"""
89
+ self.running = False
90
+ self._stop_event.set()
91
+ if self.thread:
92
+ self.thread.join(timeout=0.5)
93
+
94
+ # 清除当前行
95
+ sys.stdout.write('\r' + ' ' * (len(self.message) + 20) + '\r')
96
+ sys.stdout.flush()
97
+
98
+ if final_message:
99
+ print(c(Colors.BRIGHT_GREEN, f"✓ {final_message}"))
100
+
101
+ def _spin(self):
102
+ """Spinner 动画循环"""
103
+ idx = 0
104
+ while self.running and not self._stop_event.is_set():
105
+ frame = self.FRAMES[idx % len(self.FRAMES)]
106
+ msg = f"{frame} {self.message}..."
107
+ sys.stdout.write('\r' + msg)
108
+ sys.stdout.flush()
109
+ idx += 1
110
+ time.sleep(0.08)
111
+
112
+ if self.running:
113
+ sys.stdout.write('\r' + ' ' * (len(self.message) + 20) + '\r')
114
+ sys.stdout.flush()
115
+
116
+
117
+ # ============================================================
118
+ # 打字机效果
119
+ # ============================================================
120
+ async def typewriter_effect(
121
+ text: str,
122
+ delay: float = 0.01,
123
+ color: str = Colors.WHITE
124
+ ):
125
+ """模拟打字机效果输出"""
126
+ # 检测是否启用(终端宽度足够且不是太长的文本)
127
+ if not sys.stdout.isatty() or len(text) > 2000:
128
+ print(c(color, text))
129
+ return
130
+
131
+ for char in text:
132
+ print(c(color, char), end='', flush=True)
133
+ await asyncio.sleep(delay)
134
+ print()
135
+
136
+
137
+ # ============================================================
138
+ # UI 组件
139
+ # ============================================================
140
+ class UI:
141
+ """UI 组件库"""
142
+
143
+ @staticmethod
144
+ def banner():
145
+ """现代 ASCII 艺术横幅"""
146
+ banner = f"""
147
+ {Colors.CYAN}╔══════════════════════════════════════════════════════════════════╗
148
+ ║{Colors.RESET} {Colors.CYAN}║
149
+ ║{Colors.RESET} {Colors.BRIGHT_CYAN}{Colors.BOLD} ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ {Colors.RESET} {Colors.CYAN}║
150
+ ║{Colors.RESET} {Colors.BRIGHT_CYAN}{Colors.BOLD} █ 二 郎 神 - AI 投 资 智 能 体 █ {Colors.RESET} {Colors.CYAN}║
151
+ ║{Colors.RESET} {Colors.BRIGHT_CYAN}{Colors.BOLD} ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ {Colors.RESET} {Colors.CYAN}║
152
+ ║{Colors.RESET} {Colors.CYAN}║
153
+ ║{Colors.RESET} {Colors.DIM} 全知全能 · 天眼洞察 · 投资决策 · 风险评估 {Colors.RESET} {Colors.CYAN}║
154
+ ║{Colors.RESET} {Colors.CYAN}║
155
+ ╚══════════════════════════════════════════════════════════════════╝{Colors.RESET}
156
+
157
+ {Colors.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
158
+ {Colors.WHITE} 输入你的问题,二郎神将为你分析投资机会和风险{Colors.RESET}
159
+ {Colors.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
160
+ """
161
+ print(banner)
162
+
163
+ @staticmethod
164
+ def help_panel():
165
+ """帮助面板"""
166
+ help_text = f"""
167
+ {Colors.CYAN}╭─────────────────────────────────────────────────────────╮{Colors.RESET}
168
+ {Colors.CYAN}│{Colors.RESET} {Colors.BOLD}快捷命令{Colors.RESET} {Colors.CYAN}│{Colors.RESET}
169
+ {Colors.CYAN}├─────────────────────────────────────────────────────────┤{Colors.RESET}
170
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/help{Colors.RESET} 显示帮助信息 {Colors.CYAN}│{Colors.RESET}
171
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/market{Colors.RESET} 查询股票行情 例: /market 000001 {Colors.CYAN}│{Colors.RESET}
172
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/macro{Colors.RESET} 查询宏观数据 例: /macro CPI {Colors.CYAN}│{Colors.RESET}
173
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/report{Colors.RESET} 生成报告 例: /report 周报 {Colors.CYAN}│{Colors.RESET}
174
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/status{Colors.RESET} 查看状态 {Colors.CYAN}│{Colors.RESET}
175
+ {Colors.CYAN}│{Colors.RESET} {Colors.GREEN}/clear{Colors.RESET} 清除对话历史 {Colors.CYAN}│{Colors.RESET}
176
+ {Colors.CYAN}│{Colors.RESET} {Colors.RED}/quit{Colors.RESET} 退出程序 {Colors.CYAN}│{Colors.RESET}
177
+ {Colors.CYAN}╰─────────────────────────────────────────────────────────╯{Colors.RESET}
178
+
179
+ {Colors.DIM}示例:{Colors.RESET}
180
+ {Colors.WHITE}→{Colors.RESET} 分析美联储加息对A股的影响
181
+ {Colors.WHITE}→{Colors.RESET} 茅台现在值得买入吗?
182
+ {Colors.WHITE}→{Colors.RESET} 当前应该怎么配置资产?
183
+ {Colors.WHITE}→{Colors.RESET} 分析半导体行业前景
184
+ """
185
+ print(help_text)
186
+
187
+ @staticmethod
188
+ def status_bar():
189
+ """底部状态栏"""
190
+ # 获取终端宽度
191
+ try:
192
+ size = os.get_terminal_size()
193
+ width = size.columns
194
+ except:
195
+ width = 80
196
+
197
+ bar = "─" * (width - 6)
198
+ print(f"\n{Colors.DIM}┌{bar}┐{Colors.RESET}")
199
+ print(f"{Colors.DIM}│{Colors.RESET} {Colors.DIM}Ctrl+L: 清屏{Colors.RESET} | {Colors.DIM}Ctrl+C: 中断{Colors.RESET} | {Colors.DIM}Tab: 自动补全{Colors.RESET} {Colors.DIM}│{Colors.RESET}")
200
+ print(f"{Colors.DIM}└{bar}┘{Colors.RESET}")
201
+
202
+ @staticmethod
203
+ def thinking_indicator():
204
+ """思考中指示"""
205
+ return f"{Colors.YELLOW}⏳ 二郎神正在思考...{Colors.RESET}"
206
+
207
+ @staticmethod
208
+ def success(msg: str):
209
+ """成功消息"""
210
+ print(c(Colors.BRIGHT_GREEN, f"✓ {msg}"))
211
+
212
+ @staticmethod
213
+ def error(msg: str):
214
+ """错误消息"""
215
+ print(c(Colors.BRIGHT_RED, f"✗ {msg}"))
216
+
217
+ @staticmethod
218
+ def warning(msg: str):
219
+ """警告消息"""
220
+ print(c(Colors.YELLOW, f"⚠ {msg}"))
221
+
222
+ @staticmethod
223
+ def info(msg: str):
224
+ """信息消息"""
225
+ print(c(Colors.CYAN, f"ℹ {msg}"))
226
+
227
+
228
+ # ============================================================
229
+ # CLI 主类
230
+ # ============================================================
231
+ class CLI:
232
+ """二郎神命令行界面 - 优化版"""
233
+
234
+ def __init__(self):
235
+ self.erlangshen = self._init_erlangshen()
236
+ self.running = True
237
+ self.spinner = Spinner()
238
+ self.ui = UI()
239
+ logger.info("CLI initialized")
240
+
241
+ def _init_erlangshen(self) -> 二郎神:
242
+ """初始化二郎神"""
243
+ logger.info("Initializing 二郎神...")
244
+
245
+ # 加载配置
246
+ try:
247
+ from src.config import load_config
248
+ config = load_config()
249
+ deepseek_key = config.deepseek_api_key if config.deepseek_api_key else os.getenv("DEEPSEEK_API_KEY")
250
+ if deepseek_key:
251
+ os.environ["DEEPSEEK_API_KEY"] = deepseek_key
252
+ except Exception as e:
253
+ logger.warning(f"Config load failed: {e}, using env var")
254
+
255
+ brain = Brain()
256
+ memory = Memory()
257
+ knowledge = KnowledgeBase()
258
+
259
+ tools = {
260
+ "market_tools": MarketTools(),
261
+ "macro_tools": MacroTools(),
262
+ "search_tools": SearchTools(),
263
+ "file_tools": FileTools(),
264
+ }
265
+
266
+ return 二郎神(
267
+ brain=brain,
268
+ memory=memory,
269
+ knowledge=knowledge,
270
+ tools=tools,
271
+ )
272
+
273
+ def _format_result(self, result: dict) -> str:
274
+ """美化输出格式"""
275
+ lines = []
276
+
277
+ # 标题框
278
+ lines.append(f"\n{Colors.CYAN}╭{'─' * 58}╮{Colors.RESET}")
279
+ lines.append(f"{Colors.CYAN}│{Colors.RESET}{Colors.BOLD}{Colors.BRIGHT_CYAN} 二郎神分析结果{Colors.RESET}{' ' * 38}{Colors.CYAN}│{Colors.RESET}")
280
+ lines.append(f"{Colors.CYAN}╰{'─' * 58}╯{Colors.RESET}\n")
281
+
282
+ # 核心结论
283
+ if "conclusion" in result:
284
+ lines.append(f"{Colors.BOLD}📊 核心结论{Colors.RESET}")
285
+ lines.append(f"{Colors.DIM}{'─' * 40}{Colors.RESET}")
286
+ lines.append(f"{Colors.WHITE}{result['conclusion'][:500]}{Colors.RESET}")
287
+ lines.append("")
288
+
289
+ # 分析结论
290
+ if "analysis" in result:
291
+ analysis = result["analysis"]
292
+ if "conclusion" in analysis:
293
+ lines.append(f"{Colors.BOLD}📈 分析结论{Colors.RESET}")
294
+ lines.append(f"{Colors.DIM}{'─' * 40}{Colors.RESET}")
295
+ lines.append(f"{Colors.WHITE}{analysis['conclusion'][:500]}{Colors.RESET}")
296
+ lines.append("")
297
+
298
+ # 置信度
299
+ if "confidence" in analysis:
300
+ conf = analysis["confidence"]
301
+ stars = "★" * int(conf * 5) + "☆" * (5 - int(conf * 5))
302
+ conf_color = Colors.GREEN if conf > 0.7 else Colors.YELLOW if conf > 0.4 else Colors.RED
303
+ lines.append(f"{Colors.BOLD}🎯 置信度{Colors.RESET} {c(conf_color, f'{stars} ({conf:.0%})')}")
304
+ lines.append("")
305
+
306
+ # 二郎神洞察
307
+ if "erlangshen_insight" in result:
308
+ lines.append(f"{Colors.BOLD}💡 二郎神洞察{Colors.RESET}")
309
+ lines.append(f"{Colors.DIM}{'─' * 40}{Colors.RESET}")
310
+ lines.append(f"{Colors.CYAN}{result['erlangshen_insight'][:300]}{Colors.RESET}")
311
+ lines.append("")
312
+
313
+ # 建议
314
+ if "recommendation" in result:
315
+ lines.append(f"{Colors.BOLD}📋 建议{Colors.RESET}")
316
+ lines.append(f"{Colors.DIM}{'─' * 40}{Colors.RESET}")
317
+ lines.append(f"{Colors.WHITE}{result['recommendation'][:300]}{Colors.RESET}")
318
+ lines.append("")
319
+
320
+ # 底部边框
321
+ lines.append(f"{Colors.DIM}{'─' * 60}{Colors.RESET}")
322
+
323
+ return "\n".join(lines)
324
+
325
+ async def process_command(self, cmd: str) -> Optional[str]:
326
+ """处理命令"""
327
+ cmd = cmd.strip()
328
+
329
+ if not cmd:
330
+ return None
331
+
332
+ # 命令处理
333
+ if cmd == "/quit":
334
+ self.running = False
335
+ return c(Colors.CYAN, "\n👋 再见!二郎神退下了。\n")
336
+
337
+ if cmd == "/help":
338
+ self.ui.help_panel()
339
+ return None
340
+
341
+ if cmd == "/clear":
342
+ os.system('cls' if os.name == 'nt' else 'clear')
343
+ self.ui.banner()
344
+ return None
345
+
346
+ if cmd == "/status":
347
+ stats = self.erlangshen.knowledge.stats()
348
+ return f"\n{Colors.CYAN}📊 知识库状态{Colors.RESET}\n{stats}"
349
+
350
+ if cmd.startswith("/market "):
351
+ symbol = cmd.split()[1]
352
+ try:
353
+ result = await self.erlangshen.tools["market_tools"].get_stock_price(symbol)
354
+ return f"\n{Colors.GREEN}📈 {symbol} 行情:{Colors.RESET}\n{result}"
355
+ except Exception as e:
356
+ return c(Colors.RED, f"查询失败: {e}")
357
+
358
+ if cmd.startswith("/macro "):
359
+ indicator = cmd.split()[1]
360
+ try:
361
+ result = await self.erlangshen.tools["macro_tools"].get_macro_indicator(indicator)
362
+ return f"\n{Colors.GREEN}📊 {indicator} 数据:{Colors.RESET}\n{result}"
363
+ except Exception as e:
364
+ return c(Colors.RED, f"查询失败: {e}")
365
+
366
+ if cmd.startswith("/report "):
367
+ title = cmd[8:].strip()
368
+ if not title:
369
+ return c(Colors.YELLOW, "请提供报告标题: /report <标题>")
370
+ return c(Colors.YELLOW, f"报告功能开发中: /report {title}")
371
+
372
+ # 默认作为分析查询处理
373
+ print(f"\n{self.ui.thinking_indicator()}")
374
+ self.spinner.message = "二郎神正在思考"
375
+ self.spinner.start()
376
+
377
+ try:
378
+ result = await self.erlangshen.process(cmd)
379
+ self.spinner.stop("分析完成")
380
+ return self._format_result(result)
381
+ except Exception as e:
382
+ self.spinner.stop()
383
+ logger.error(f"Error processing query: {e}")
384
+ return c(Colors.RED, f"\n❌ 处理出错: {e}")
385
+
386
+ async def run(self):
387
+ """运行CLI"""
388
+ os.system('cls' if os.name == 'nt' else 'clear')
389
+ self.ui.banner()
390
+ self.ui.help_panel()
391
+
392
+ while self.running:
393
+ try:
394
+ # 显示状态栏
395
+ self.ui.status_bar()
396
+
397
+ # 获取输入
398
+ try:
399
+ user_input = await asyncio.get_event_loop().run_in_executor(
400
+ None,
401
+ lambda: input(f"{Colors.BRIGHT_CYAN}❓ 你:{Colors.RESET} ").strip()
402
+ )
403
+ except EOFError:
404
+ break
405
+ except KeyboardInterrupt:
406
+ print(c(Colors.YELLOW, "\n\n⚠ 使用 /quit 退出"))
407
+ continue
408
+
409
+ if not user_input:
410
+ continue
411
+
412
+ # 处理命令/查询
413
+ result = await self.process_command(user_input)
414
+ if result:
415
+ print(result)
416
+
417
+ except KeyboardInterrupt:
418
+ print(c(Colors.YELLOW, "\n\n⚠ 使用 /quit 退出"))
419
+ continue
420
+ except Exception as e:
421
+ logger.error(f"CLI error: {e}")
422
+ print(c(Colors.RED, f"\n❌ 错误: {e}"))
423
+
424
+ print(c(Colors.CYAN, "\n👋 二郎神已退出。再见!\n"))
425
+
426
+
427
+ async def main():
428
+ """主入口"""
429
+ print(f"{Colors.DIM}启动二郎神CLI...{Colors.RESET}")
430
+ cli = CLI()
431
+ await cli.run()
432
+
433
+
434
+ if __name__ == "__main__":
435
+ asyncio.run(main())