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.
- package/.claude/agents/equity-agent.md +26 -0
- package/.claude/agents/macro-agent.md +25 -0
- package/.claude/commands/analyze.md +40 -0
- package/.claude/commands/macro.md +29 -0
- package/.claude/settings.json +12 -0
- package/CODEX_GOAL.md +46 -0
- package/README.md +206 -0
- package/bin/cli.js +67 -0
- package/bin/erlangshen +2 -0
- package/bin/xiaoergod +2 -0
- package/frontend/index.html +700 -0
- package/knowledge/crypto_guide.md +147 -0
- package/knowledge/economic_indicators.md +125 -0
- package/knowledge/financial_glossary.md +148 -0
- package/knowledge/first_principles.md +50 -0
- package/knowledge/first_principles_deep.md +115 -0
- package/knowledge/global_markets.md +173 -0
- package/knowledge/insights.md +141 -0
- package/knowledge/market_basics.md +116 -0
- package/knowledge/memos/session_20260513_003616.json +6 -0
- package/knowledge/memos/session_20260513_003822.json +6 -0
- package/knowledge/risk_management.md +151 -0
- package/knowledge/team_context.md +42 -0
- package/knowledge/trading_strategies.md +114 -0
- package/package.json +42 -0
- package/requirements.txt +14 -0
- package/scripts/postinstall.js +188 -0
- package/scripts/preuninstall.js +22 -0
- package/src/__init__.py +4 -0
- package/src/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/agents/__init__.py +3 -0
- package/src/agents/base.py +103 -0
- package/src/agents/base_agent.py +86 -0
- package/src/agents/equity.py +136 -0
- package/src/agents/equity_agent.py +91 -0
- package/src/agents/erlang.py +165 -0
- package/src/agents/macro.py +137 -0
- package/src/agents/macro_agent.py +81 -0
- package/src/agents/multi_asset.py +147 -0
- package/src/agents/multi_asset_agent.py +87 -0
- package/src/api/__init__.py +1 -0
- package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/api/__pycache__/server.cpython-313.pyc +0 -0
- package/src/api/cli.py +435 -0
- package/src/api/cli_enhanced.py +537 -0
- package/src/api/server.py +266 -0
- package/src/brain.py +200 -0
- package/src/cli.py +153 -0
- package/src/commands/__init__.py +3 -0
- package/src/commands/analyze.py +131 -0
- package/src/commands/macro.py +100 -0
- package/src/commands/memo.py +216 -0
- package/src/commands/portfolio.py +154 -0
- package/src/commands/report.py +228 -0
- package/src/commands/risk.py +183 -0
- package/src/commands/search.py +183 -0
- package/src/commands/stock.py +124 -0
- package/src/config.py +327 -0
- package/src/core/__init__.py +1 -0
- package/src/core/brain.py +645 -0
- package/src/core/cerebellum.py +175 -0
- package/src/core/investment_universe.py +423 -0
- package/src/core/knowledge.py +207 -0
- package/src/core/memory.py +115 -0
- package/src/hooks/__init__.py +3 -0
- package/src/hooks/session_end.py +57 -0
- package/src/hooks/session_start.py +75 -0
- package/src/knowledge/__init__.py +1 -0
- package/src/mcp/__init__.py +3 -0
- package/src/mcp/feishu.py +331 -0
- package/src/mcp/fund_tools.py +323 -0
- package/src/mcp/macro.py +452 -0
- package/src/mcp/market.py +331 -0
- package/src/mcp/registry.py +168 -0
- package/src/network/__init__.py +15 -0
- package/src/network/detector.py +125 -0
- package/src/network/proxy.py +199 -0
- package/src/network/router.py +103 -0
- package/src/prompts/__init__.py +1 -0
- package/src/prompts/analysis_framework.md +164 -0
- package/src/prompts/persona.md +65 -0
- package/src/prompts/report_template.md +144 -0
- package/src/skills/__init__.py +3 -0
- package/src/skills/framework.py +105 -0
- package/src/skills/templates.py +342 -0
- package/src/tools/__init__.py +1 -0
- package/src/tools/file_tools.py +209 -0
- package/src/tools/macro_tools.py +152 -0
- package/src/tools/market_tools.py +1172 -0
- package/src/tools/registry.py +398 -0
- package/src/tools/search_tools.py +777 -0
- package/tests/__init__.py +1 -0
- package/tests/test_erlangshen.py +140 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Server - 二郎神API服务
|
|
3
|
+
提供REST API接口
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
# 添加src到路径
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
12
|
+
|
|
13
|
+
from fastapi import FastAPI, HTTPException
|
|
14
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
from loguru import logger
|
|
17
|
+
import uvicorn
|
|
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.core.cerebellum import Cerebellum
|
|
23
|
+
from src.tools.market_tools import MarketTools
|
|
24
|
+
from src.tools.macro_tools import MacroTools
|
|
25
|
+
from src.tools.search_tools import SearchTools
|
|
26
|
+
from src.tools.file_tools import FileTools
|
|
27
|
+
from src.agents.erlang import 二郎神
|
|
28
|
+
|
|
29
|
+
# 初始化应用
|
|
30
|
+
app = FastAPI(
|
|
31
|
+
title="二郎神 API",
|
|
32
|
+
description="全知全能的AI投资智能体API",
|
|
33
|
+
version="0.1.0",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
app.add_middleware(
|
|
37
|
+
CORSMiddleware,
|
|
38
|
+
allow_origins=["*"],
|
|
39
|
+
allow_credentials=True,
|
|
40
|
+
allow_methods=["*"],
|
|
41
|
+
allow_headers=["*"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# 全局实例
|
|
45
|
+
_brain: Optional[Brain] = None
|
|
46
|
+
_memory: Optional[Memory] = None
|
|
47
|
+
_knowledge: Optional[KnowledgeBase] = None
|
|
48
|
+
_cerebellum: Optional[Cerebellum] = None
|
|
49
|
+
_erlangshen: Optional[二郎神] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_erlangshen() -> 二郎神:
|
|
53
|
+
"""获取或初始化二郎神实例"""
|
|
54
|
+
global _brain, _memory, _knowledge, _cerebellum, _erlangshen
|
|
55
|
+
if _erlangshen is None:
|
|
56
|
+
logger.info("Initializing 二郎神...")
|
|
57
|
+
_brain = Brain()
|
|
58
|
+
_memory = Memory()
|
|
59
|
+
_knowledge = KnowledgeBase()
|
|
60
|
+
_cerebellum = Cerebellum(brain=_brain, memory=_memory, knowledge=_knowledge)
|
|
61
|
+
|
|
62
|
+
# 初始化工具
|
|
63
|
+
market_tools = MarketTools()
|
|
64
|
+
macro_tools = MacroTools()
|
|
65
|
+
search_tools = SearchTools()
|
|
66
|
+
file_tools = FileTools()
|
|
67
|
+
|
|
68
|
+
tools = {
|
|
69
|
+
"market_tools": market_tools,
|
|
70
|
+
"macro_tools": macro_tools,
|
|
71
|
+
"search_tools": search_tools,
|
|
72
|
+
"file_tools": file_tools,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_erlangshen = 二郎神(
|
|
76
|
+
brain=_brain,
|
|
77
|
+
memory=_memory,
|
|
78
|
+
knowledge=_knowledge,
|
|
79
|
+
tools=tools,
|
|
80
|
+
)
|
|
81
|
+
logger.info("二郎神 initialized")
|
|
82
|
+
return _erlangshen
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# === 请求/响应模型 ===
|
|
86
|
+
|
|
87
|
+
class AnalyzeRequest(BaseModel):
|
|
88
|
+
query: str = Field(description="分析问题")
|
|
89
|
+
context: Optional[dict] = Field(default=None, description="额外上下文")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AnalyzeResponse(BaseModel):
|
|
93
|
+
status: str
|
|
94
|
+
result: dict
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class MarketRequest(BaseModel):
|
|
98
|
+
symbol: str = Field(description="股票代码")
|
|
99
|
+
days: int = Field(default=30, description="历史天数")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ReportRequest(BaseModel):
|
|
103
|
+
title: str = Field(description="报告标题")
|
|
104
|
+
content: str = Field(description="报告内容")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ThinkRequest(BaseModel):
|
|
108
|
+
prompt: str = Field(description="用户输入")
|
|
109
|
+
context: Optional[dict] = Field(default=None, description="额外上下文")
|
|
110
|
+
system: Optional[str] = Field(default=None, description="系统提示")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# === API端点 ===
|
|
114
|
+
|
|
115
|
+
@app.get("/")
|
|
116
|
+
async def root():
|
|
117
|
+
"""API根路径"""
|
|
118
|
+
return {
|
|
119
|
+
"name": "二郎神 API",
|
|
120
|
+
"version": "0.1.0",
|
|
121
|
+
"status": "running",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.get("/health")
|
|
126
|
+
async def health():
|
|
127
|
+
"""健康检查"""
|
|
128
|
+
return {"status": "healthy"}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.post("/api/brain/think")
|
|
132
|
+
async def brain_think(request: ThinkRequest):
|
|
133
|
+
"""
|
|
134
|
+
直接调用 Brain 进行深度思考
|
|
135
|
+
前端全息界面调用此接口
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
erlangshen = get_erlangshen()
|
|
139
|
+
result = await erlangshen.brain.think(
|
|
140
|
+
prompt=request.prompt,
|
|
141
|
+
context=request.context,
|
|
142
|
+
system=request.system,
|
|
143
|
+
)
|
|
144
|
+
return {"status": "success", "result": result}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"Brain think failed: {e}")
|
|
147
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@app.post("/analyze", response_model=AnalyzeResponse)
|
|
151
|
+
async def analyze(request: AnalyzeRequest):
|
|
152
|
+
"""
|
|
153
|
+
分析接口 - 二郎神的核心分析能力
|
|
154
|
+
|
|
155
|
+
支持:
|
|
156
|
+
- 宏观经济分析
|
|
157
|
+
- 股票分析
|
|
158
|
+
- 多资产配置建议
|
|
159
|
+
- 综合投资分析
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
erlangshen = get_erlangshen()
|
|
163
|
+
result = await erlangshen.process(request.query, request.context)
|
|
164
|
+
return AnalyzeResponse(status="success", result=result)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"Analyze failed: {e}")
|
|
167
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@app.post("/query")
|
|
171
|
+
async def query(request: AnalyzeRequest):
|
|
172
|
+
"""
|
|
173
|
+
查询接口 - 简化的查询接口
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
erlangshen = get_erlangshen()
|
|
177
|
+
result = await erlangshen.process(request.query, request.context)
|
|
178
|
+
return result
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Query failed: {e}")
|
|
181
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@app.get("/market/{symbol}")
|
|
185
|
+
async def get_market(symbol: str, days: int = 30):
|
|
186
|
+
"""获取股票行情"""
|
|
187
|
+
try:
|
|
188
|
+
erlangshen = get_erlangshen()
|
|
189
|
+
market_tools = erlangshen.tools.get("market_tools")
|
|
190
|
+
if market_tools:
|
|
191
|
+
result = await market_tools.get_stock_history(symbol, days)
|
|
192
|
+
return result
|
|
193
|
+
raise HTTPException(status_code=503, detail="Market tools not available")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"Market query failed: {e}")
|
|
196
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@app.get("/macro/{indicator}")
|
|
200
|
+
async def get_macro(indicator: str):
|
|
201
|
+
"""获取宏观指标"""
|
|
202
|
+
try:
|
|
203
|
+
erlangshen = get_erlangshen()
|
|
204
|
+
macro_tools = erlangshen.tools.get("macro_tools")
|
|
205
|
+
if macro_tools:
|
|
206
|
+
result = await macro_tools.get_macro_indicator(indicator)
|
|
207
|
+
return result
|
|
208
|
+
raise HTTPException(status_code=503, detail="Macro tools not available")
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.error(f"Macro query failed: {e}")
|
|
211
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@app.post("/report")
|
|
215
|
+
async def create_report(request: ReportRequest):
|
|
216
|
+
"""创建报告"""
|
|
217
|
+
try:
|
|
218
|
+
erlangshen = get_erlangshen()
|
|
219
|
+
result = await erlangshen.generate_report(request.title, request.content)
|
|
220
|
+
return result
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(f"Report creation failed: {e}")
|
|
223
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.get("/knowledge/stats")
|
|
227
|
+
async def knowledge_stats():
|
|
228
|
+
"""知识库统计"""
|
|
229
|
+
try:
|
|
230
|
+
erlangshen = get_erlangshen()
|
|
231
|
+
if erlangshen.knowledge:
|
|
232
|
+
return erlangshen.knowledge.stats()
|
|
233
|
+
raise HTTPException(status_code=503, detail="Knowledge not available")
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.error(f"Knowledge stats failed: {e}")
|
|
236
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@app.get("/memory/stats")
|
|
240
|
+
async def memory_stats():
|
|
241
|
+
"""记忆统计"""
|
|
242
|
+
try:
|
|
243
|
+
erlangshen = get_erlangshen()
|
|
244
|
+
if erlangshen.memory:
|
|
245
|
+
return erlangshen.memory.export_state()
|
|
246
|
+
raise HTTPException(status_code=503, detail="Memory not available")
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.error(f"Memory stats failed: {e}")
|
|
249
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# === 启动函数 ===
|
|
253
|
+
|
|
254
|
+
def run_server(host: str = "0.0.0.0", port: int = 8000):
|
|
255
|
+
"""启动API服务器"""
|
|
256
|
+
logger.info(f"Starting 二郎神 API server on {host}:{port}")
|
|
257
|
+
uvicorn.run(app, host=host, port=port)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
import argparse
|
|
262
|
+
parser = argparse.ArgumentParser(description="二郎神API服务器")
|
|
263
|
+
parser.add_argument("--host", default="0.0.0.0", help="监听地址")
|
|
264
|
+
parser.add_argument("--port", type=int, default=8000, help="监听端口")
|
|
265
|
+
args = parser.parse_args()
|
|
266
|
+
run_server(args.host, args.port)
|
package/src/brain.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
二郎神大脑 - LLM调用层
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from typing import Optional, List, Dict, Any
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from src.config import get_config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Brain:
|
|
14
|
+
"""
|
|
15
|
+
二郎神大脑 - 统一的LLM调用接口
|
|
16
|
+
支持 DeepSeek、OpenAI 等多种模型
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.config = get_config()
|
|
21
|
+
self.max_context = self.config.llm_max_context # 1M tokens for deepseek-v4-pro
|
|
22
|
+
self._client = None
|
|
23
|
+
self.conversation_history: List[Dict[str, str]] = []
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def client(self):
|
|
27
|
+
"""懒加载 LLM 客户端"""
|
|
28
|
+
if self._client is None:
|
|
29
|
+
self._client = self._create_client()
|
|
30
|
+
return self._client
|
|
31
|
+
|
|
32
|
+
def _create_client(self):
|
|
33
|
+
"""创建 LLM 客户端"""
|
|
34
|
+
provider = self.config.llm_provider.lower()
|
|
35
|
+
|
|
36
|
+
if provider == "deepseek":
|
|
37
|
+
try:
|
|
38
|
+
from openai import OpenAI
|
|
39
|
+
# 优先使用环境变量
|
|
40
|
+
api_key = os.environ.get("DEEPSEEK_API_KEY") or self.config.deepseek_api_key
|
|
41
|
+
return OpenAI(
|
|
42
|
+
api_key=api_key,
|
|
43
|
+
base_url="https://api.deepseek.com"
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
raise ImportError("请安装 openai: pip install openai")
|
|
47
|
+
|
|
48
|
+
elif provider == "openai":
|
|
49
|
+
try:
|
|
50
|
+
from openai import OpenAI
|
|
51
|
+
return OpenAI(
|
|
52
|
+
api_key=self.config.llm_api_key or os.environ.get("OPENAI_API_KEY"),
|
|
53
|
+
base_url=self.config.llm_base_url
|
|
54
|
+
)
|
|
55
|
+
except ImportError:
|
|
56
|
+
raise ImportError("请安装 openai: pip install openai")
|
|
57
|
+
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"不支持的 LLM 提供商: {provider}")
|
|
60
|
+
|
|
61
|
+
def _get_model(self) -> str:
|
|
62
|
+
"""获取模型名称"""
|
|
63
|
+
if self.config.llm_provider.lower() == "deepseek":
|
|
64
|
+
# 优先使用环境变量
|
|
65
|
+
return os.environ.get("DEEPSEEK_MODEL") or self.config.deepseek_model
|
|
66
|
+
return self.config.llm_model
|
|
67
|
+
|
|
68
|
+
async def think(
|
|
69
|
+
self,
|
|
70
|
+
prompt: str,
|
|
71
|
+
system: Optional[str] = None,
|
|
72
|
+
temperature: Optional[float] = None,
|
|
73
|
+
max_tokens: Optional[int] = None,
|
|
74
|
+
context: Optional[List[Dict[str, str]]] = None,
|
|
75
|
+
) -> str:
|
|
76
|
+
"""
|
|
77
|
+
核心思考方法
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
prompt: 用户提示
|
|
81
|
+
system: 系统提示 (可选)
|
|
82
|
+
temperature: 温度参数 (可选)
|
|
83
|
+
max_tokens: 最大 token 数 (可选)
|
|
84
|
+
context: 对话上下文 (可选)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
LLM 生成的响应
|
|
88
|
+
"""
|
|
89
|
+
messages = []
|
|
90
|
+
|
|
91
|
+
# 系统提示
|
|
92
|
+
if system:
|
|
93
|
+
messages.append({"role": "system", "content": system})
|
|
94
|
+
|
|
95
|
+
# 上下文
|
|
96
|
+
if context:
|
|
97
|
+
messages.extend(context)
|
|
98
|
+
|
|
99
|
+
# 用户提示
|
|
100
|
+
messages.append({"role": "user", "content": prompt})
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
response = self.client.chat.completions.create(
|
|
104
|
+
model=self._get_model(),
|
|
105
|
+
messages=messages,
|
|
106
|
+
temperature=temperature or self.config.llm_temperature,
|
|
107
|
+
max_tokens=max_tokens or self.config.llm_max_tokens,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return response.choices[0].message.content
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
return f"思考过程出错: {str(e)}"
|
|
114
|
+
|
|
115
|
+
async def analyze(
|
|
116
|
+
self,
|
|
117
|
+
query: str,
|
|
118
|
+
framework: Optional[str] = None,
|
|
119
|
+
data: Optional[Dict[str, Any]] = None,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""
|
|
122
|
+
分析查询
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
query: 分析查询
|
|
126
|
+
framework: 分析框架 (可选)
|
|
127
|
+
data: 相关数据 (可选)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
分析结果
|
|
131
|
+
"""
|
|
132
|
+
system_prompt = self._build_analysis_prompt(framework)
|
|
133
|
+
|
|
134
|
+
user_prompt = query
|
|
135
|
+
if data:
|
|
136
|
+
user_prompt += f"\n\n相关数据:\n{json.dumps(data, ensure_ascii=False, indent=2)}"
|
|
137
|
+
|
|
138
|
+
return await self.think(user_prompt, system=system_prompt)
|
|
139
|
+
|
|
140
|
+
def _build_analysis_prompt(self, framework: Optional[str] = None) -> str:
|
|
141
|
+
"""构建分析提示"""
|
|
142
|
+
base = """你是二郎神,一个全知全能的AI投资智能体。
|
|
143
|
+
|
|
144
|
+
## 你的背景
|
|
145
|
+
- 你是小二配置团队的AI投资智能体
|
|
146
|
+
- 你具备团队全貌认知,了解9个专业Agent的能力和成果
|
|
147
|
+
- 你可以调用真实数据库获取行情和宏观数据
|
|
148
|
+
|
|
149
|
+
## 团队能力
|
|
150
|
+
- Agent-01/02: 基金筛选配置(ETF轮动V7年化16.1%)
|
|
151
|
+
- Agent-03: 交易执行验证
|
|
152
|
+
- Agent-04: CTA量化策略
|
|
153
|
+
- Agent-05: 宏观策略(V19年化25.6%,净值5.23)
|
|
154
|
+
- Agent-07: 数据采集治理
|
|
155
|
+
- Agent-08: 客户服务
|
|
156
|
+
- Agent-09: 个股基本面研究
|
|
157
|
+
|
|
158
|
+
## 核心数据资源
|
|
159
|
+
- 宏观数据库: 48个指标,SHIBOR_3M/CPI/GDP/PMI等
|
|
160
|
+
- 远程行情: 股票/指数/期货实时数据
|
|
161
|
+
- MCP工具: 东方财富dc-66,飞书文档
|
|
162
|
+
|
|
163
|
+
## 分析原则
|
|
164
|
+
1. 基于真实数据进行分析
|
|
165
|
+
2. 客观中立,不带偏见
|
|
166
|
+
3. 风险与收益并重
|
|
167
|
+
4. 考虑多种情景和可能性
|
|
168
|
+
5. 可以调用团队各Agent的专业能力
|
|
169
|
+
|
|
170
|
+
输出格式:
|
|
171
|
+
- 核心观点 (1-3句话)
|
|
172
|
+
- 详细分析
|
|
173
|
+
- 风险提示
|
|
174
|
+
- 行动建议 (如适用)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
if framework:
|
|
178
|
+
base += f"\n\n分析框架: {framework}"
|
|
179
|
+
|
|
180
|
+
return base
|
|
181
|
+
|
|
182
|
+
def reset_history(self):
|
|
183
|
+
"""重置对话历史"""
|
|
184
|
+
self.conversation_history = []
|
|
185
|
+
|
|
186
|
+
def add_to_history(self, role: str, content: str):
|
|
187
|
+
"""添加到对话历史"""
|
|
188
|
+
self.conversation_history.append({"role": role, "content": content})
|
|
189
|
+
|
|
190
|
+
async def think_with_history(
|
|
191
|
+
self,
|
|
192
|
+
prompt: str,
|
|
193
|
+
system: Optional[str] = None,
|
|
194
|
+
) -> str:
|
|
195
|
+
"""带历史的思考"""
|
|
196
|
+
return await self.think(
|
|
197
|
+
prompt,
|
|
198
|
+
system=system,
|
|
199
|
+
context=self.conversation_history
|
|
200
|
+
)
|
package/src/cli.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
二郎神 CLI - 投资分析智能体命令行入口
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from src.brain import Brain
|
|
12
|
+
from src.mcp.registry import MCPRegistry
|
|
13
|
+
from src.commands.analyze import AnalyzeCommand
|
|
14
|
+
from src.commands.macro import MacroCommand
|
|
15
|
+
from src.commands.stock import StockCommand
|
|
16
|
+
from src.commands.report import ReportCommand
|
|
17
|
+
from src.commands.search import SearchCommand
|
|
18
|
+
from src.commands.portfolio import PortfolioCommand
|
|
19
|
+
from src.commands.risk import RiskCommand
|
|
20
|
+
from src.commands.memo import MemoCommand
|
|
21
|
+
from src.hooks.session_start import SessionStartHook
|
|
22
|
+
from src.hooks.session_end import SessionEndHook
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CLI:
|
|
26
|
+
"""二郎神 CLI 主类"""
|
|
27
|
+
|
|
28
|
+
COMMANDS = {
|
|
29
|
+
"analyze": AnalyzeCommand,
|
|
30
|
+
"macro": MacroCommand,
|
|
31
|
+
"stock": StockCommand,
|
|
32
|
+
"report": ReportCommand,
|
|
33
|
+
"search": SearchCommand,
|
|
34
|
+
"portfolio": PortfolioCommand,
|
|
35
|
+
"risk": RiskCommand,
|
|
36
|
+
"memo": MemoCommand,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.brain = Brain()
|
|
41
|
+
self.mcp = MCPRegistry()
|
|
42
|
+
self.hooks = {
|
|
43
|
+
"session_start": SessionStartHook(self.brain, self.mcp),
|
|
44
|
+
"session_end": SessionEndHook(self.brain, self.mcp),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async def run_command(self, command: str, args: str) -> str:
|
|
48
|
+
"""执行命令"""
|
|
49
|
+
if command in self.COMMANDS:
|
|
50
|
+
cmd = self.COMMANDS[command](self.brain, self.mcp)
|
|
51
|
+
return await cmd.execute(args)
|
|
52
|
+
else:
|
|
53
|
+
return f"未知命令: /{command}\n\n可用命令:\n" + "\n".join(f" /{cmd}" for cmd in self.COMMANDS.keys())
|
|
54
|
+
|
|
55
|
+
async def interactive_mode(self):
|
|
56
|
+
"""交互模式"""
|
|
57
|
+
print("二郎神 v0.1.0 - 投资分析智能体")
|
|
58
|
+
print("输入 /help 查看帮助,输入 /exit 退出\n")
|
|
59
|
+
|
|
60
|
+
# Session start hook
|
|
61
|
+
await self.hooks["session_start"].run()
|
|
62
|
+
|
|
63
|
+
while True:
|
|
64
|
+
try:
|
|
65
|
+
user_input = input("二郎神> ").strip()
|
|
66
|
+
|
|
67
|
+
if not user_input:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if user_input in ["/exit", "/quit", "/q"]:
|
|
71
|
+
print("再见!")
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
if user_input == "/help":
|
|
75
|
+
self.print_help()
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# 处理斜杠命令
|
|
79
|
+
if user_input.startswith("/"):
|
|
80
|
+
parts = user_input[1:].split(maxsplit=1)
|
|
81
|
+
command = parts[0]
|
|
82
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
83
|
+
result = await self.run_command(command, args)
|
|
84
|
+
else:
|
|
85
|
+
# 默认执行分析
|
|
86
|
+
result = await self.run_command("analyze", user_input)
|
|
87
|
+
|
|
88
|
+
print(f"\n{result}\n")
|
|
89
|
+
|
|
90
|
+
except KeyboardInterrupt:
|
|
91
|
+
print("\n\n再见!")
|
|
92
|
+
break
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"\n错误: {e}\n")
|
|
95
|
+
|
|
96
|
+
# Session end hook
|
|
97
|
+
await self.hooks["session_end"].run()
|
|
98
|
+
|
|
99
|
+
def print_help(self):
|
|
100
|
+
"""打印帮助信息"""
|
|
101
|
+
print("""
|
|
102
|
+
二郎神 - 投资分析智能体
|
|
103
|
+
|
|
104
|
+
可用命令:
|
|
105
|
+
/analyze <query> 综合分析 (默认)
|
|
106
|
+
/macro <query> 宏观分析
|
|
107
|
+
/stock <query> 股票分析
|
|
108
|
+
/report <query> 报告生成
|
|
109
|
+
/search <query> 搜索
|
|
110
|
+
/portfolio <query> 组合分析
|
|
111
|
+
/risk <query> 风险分析
|
|
112
|
+
/memo <query> 纪要管理
|
|
113
|
+
|
|
114
|
+
示例:
|
|
115
|
+
二郎神 /analyze A股当前走势
|
|
116
|
+
二郎神 /macro CPI走势
|
|
117
|
+
二郎神 /stock 茅台
|
|
118
|
+
二郎神 /report 月度报告
|
|
119
|
+
""")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main():
|
|
123
|
+
"""主入口"""
|
|
124
|
+
cli = CLI()
|
|
125
|
+
|
|
126
|
+
if len(sys.argv) > 1:
|
|
127
|
+
command = sys.argv[1]
|
|
128
|
+
|
|
129
|
+
if command == "--help" or command == "-h":
|
|
130
|
+
cli.print_help()
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if command.startswith("/"):
|
|
134
|
+
# 斜杠命令格式
|
|
135
|
+
cmd_parts = command[1:].split(maxsplit=1)
|
|
136
|
+
cmd = cmd_parts[0]
|
|
137
|
+
args = cmd_parts[1] if len(cmd_parts) > 1 else ""
|
|
138
|
+
if len(sys.argv) > 2:
|
|
139
|
+
args = (args + " " + " ".join(sys.argv[2:])).strip() if args else " ".join(sys.argv[2:])
|
|
140
|
+
result = asyncio.run(cli.run_command(cmd, args))
|
|
141
|
+
else:
|
|
142
|
+
# 非斜杠命令格式
|
|
143
|
+
args = " ".join(sys.argv[1:])
|
|
144
|
+
result = asyncio.run(cli.run_command("analyze", args))
|
|
145
|
+
|
|
146
|
+
print(result)
|
|
147
|
+
else:
|
|
148
|
+
# 交互模式
|
|
149
|
+
asyncio.run(cli.interactive_mode())
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|