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,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
分析框架 Skill
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AnalysisFramework:
|
|
9
|
+
"""
|
|
10
|
+
分析框架库
|
|
11
|
+
|
|
12
|
+
提供各种专业分析框架模板
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
FRAMEWORKS = {
|
|
16
|
+
"macro": {
|
|
17
|
+
"name": "宏观分析框架",
|
|
18
|
+
"description": "用于宏观经济分析的标准框架",
|
|
19
|
+
"structure": {
|
|
20
|
+
"1. 经济周期": ["GDP增速", "产出缺口", "CPI/PPI"],
|
|
21
|
+
"2. 货币金融": ["M2增速", "社融", "利率水平"],
|
|
22
|
+
"3. 财政政策": ["赤字率", "专项债", "税收"],
|
|
23
|
+
"4. 外部环境": ["出口", "汇率", "外资"],
|
|
24
|
+
"5. 政策展望": ["逆周期调节", "改革举措"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"equity": {
|
|
28
|
+
"name": "股票分析框架",
|
|
29
|
+
"description": "用于个股和行业分析的标准框架",
|
|
30
|
+
"structure": {
|
|
31
|
+
"1. 行业分析": ["行业空间", "竞争格局", "景气度"],
|
|
32
|
+
"2. 公司分析": ["商业模式", "核心竞争力", "管理层"],
|
|
33
|
+
"3. 财务分析": ["盈利能力", "成长性", "现金流"],
|
|
34
|
+
"4. 估值分析": ["PE/PB", "DCF", "相对估值"],
|
|
35
|
+
"5. 风险因素": ["行业风险", "公司风险", "市场风险"]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"multi_asset": {
|
|
39
|
+
"name": "多资产配置框架",
|
|
40
|
+
"description": "用于大类资产配置的分析框架",
|
|
41
|
+
"structure": {
|
|
42
|
+
"1. 宏观研判": ["经济周期", "政策取向", "风险偏好"],
|
|
43
|
+
"2. 资产筛选": ["股票", "债券", "商品", "另类"],
|
|
44
|
+
"3. 相关性分析": ["资产相关性", "动态相关性"],
|
|
45
|
+
"4. 组合优化": ["风险预算", "收益预期", "约束条件"],
|
|
46
|
+
"5. 动态调整": ["再平衡", "择时", "应急计划"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"risk": {
|
|
50
|
+
"name": "风险管理框架",
|
|
51
|
+
"description": "用于风险识别和评估的框架",
|
|
52
|
+
"structure": {
|
|
53
|
+
"1. 风险识别": ["市场风险", "信用风险", "流动性风险", "操作风险"],
|
|
54
|
+
"2. 风险测量": ["VaR", "ES", "波动率", "最大回撤"],
|
|
55
|
+
"3. 风险监控": ["预警指标", "限额管理", "压力测试"],
|
|
56
|
+
"4. 风险缓释": ["对冲", "分散化", "保险"]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"performance": {
|
|
60
|
+
"name": "业绩归因框架",
|
|
61
|
+
"description": "用于组合业绩归因的框架",
|
|
62
|
+
"structure": {
|
|
63
|
+
"1. 收益分解": ["资产配置", "个股选择", "交互效应"],
|
|
64
|
+
"2. 因子暴露": ["市场因子", "风格因子", "行业因子"],
|
|
65
|
+
"3. 风险归因": ["各类资产贡献", "各类因子贡献"],
|
|
66
|
+
"4. 基准对比": ["相对收益", "信息比率", "跟踪误差"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def get_framework(cls, name: str) -> Dict[str, Any]:
|
|
73
|
+
"""获取指定框架"""
|
|
74
|
+
return cls.FRAMEWORKS.get(name, {})
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def list_frameworks(cls) -> list:
|
|
78
|
+
"""列出所有框架"""
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
"name": key,
|
|
82
|
+
"name_cn": value["name"],
|
|
83
|
+
"description": value["description"]
|
|
84
|
+
}
|
|
85
|
+
for key, value in cls.FRAMEWORKS.items()
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def apply_framework(cls, framework_name: str, content: str) -> str:
|
|
90
|
+
"""应用框架格式化内容"""
|
|
91
|
+
framework = cls.get_framework(framework_name)
|
|
92
|
+
|
|
93
|
+
if not framework:
|
|
94
|
+
return content
|
|
95
|
+
|
|
96
|
+
output = f"# {framework['name']}\n\n"
|
|
97
|
+
output += f"*{framework['description']}*\n\n"
|
|
98
|
+
|
|
99
|
+
for section, items in framework["structure"].items():
|
|
100
|
+
output += f"## {section}\n"
|
|
101
|
+
for item in items:
|
|
102
|
+
output += f"- {item}\n"
|
|
103
|
+
output += "\n"
|
|
104
|
+
|
|
105
|
+
return output
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""
|
|
2
|
+
报告模板 Skill
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ReportTemplates:
|
|
10
|
+
"""
|
|
11
|
+
报告模板库
|
|
12
|
+
|
|
13
|
+
提供各种标准化的报告模板
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
TEMPLATES = {
|
|
17
|
+
"daily": {
|
|
18
|
+
"name": "日报模板",
|
|
19
|
+
"description": "每日市场总结报告模板",
|
|
20
|
+
"template": """# {title}
|
|
21
|
+
|
|
22
|
+
**日期**: {date}
|
|
23
|
+
**分析师**: 二郎神
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 今日市场回顾
|
|
28
|
+
|
|
29
|
+
### 宏观面
|
|
30
|
+
- 重要经济数据:
|
|
31
|
+
- 政策动向:
|
|
32
|
+
|
|
33
|
+
### 市场走势
|
|
34
|
+
| 指数 | 收盘价 | 涨跌幅 |
|
|
35
|
+
|------|--------|--------|
|
|
36
|
+
| 上证指数 | - | - |
|
|
37
|
+
| 深证成指 | - | - |
|
|
38
|
+
| 创业板指 | - | - |
|
|
39
|
+
| 沪深300 | - | - |
|
|
40
|
+
|
|
41
|
+
### 资金面
|
|
42
|
+
- 成交额:
|
|
43
|
+
- 北向资金:
|
|
44
|
+
- 融资融券:
|
|
45
|
+
|
|
46
|
+
## 热点聚焦
|
|
47
|
+
|
|
48
|
+
## 明日展望
|
|
49
|
+
|
|
50
|
+
## 风险提示
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
*本报告仅供参考,不构成投资建议*
|
|
54
|
+
"""
|
|
55
|
+
},
|
|
56
|
+
"weekly": {
|
|
57
|
+
"name": "周报模板",
|
|
58
|
+
"description": "每周市场总结报告模板",
|
|
59
|
+
"template": """# {title}
|
|
60
|
+
|
|
61
|
+
**周期**: {date}
|
|
62
|
+
**分析师**: 二郎神
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 本周市场概述
|
|
67
|
+
|
|
68
|
+
### 宏观环境
|
|
69
|
+
- 国内经济数据:
|
|
70
|
+
- 政策动态:
|
|
71
|
+
- 海外市场:
|
|
72
|
+
|
|
73
|
+
### 市场表现
|
|
74
|
+
| 指数 | 本周涨跌幅 | 成交额变化 |
|
|
75
|
+
|------|-----------|-----------|
|
|
76
|
+
| 上证指数 | - | - |
|
|
77
|
+
| 深证成指 | - | - |
|
|
78
|
+
| 创业板指 | - | - |
|
|
79
|
+
|
|
80
|
+
### 行业表现
|
|
81
|
+
本周涨幅前五:
|
|
82
|
+
本周跌幅前五:
|
|
83
|
+
|
|
84
|
+
### 资金动向
|
|
85
|
+
- 北向资金:
|
|
86
|
+
- 融资余额:
|
|
87
|
+
|
|
88
|
+
## 下周展望
|
|
89
|
+
|
|
90
|
+
### 关注事件
|
|
91
|
+
1.
|
|
92
|
+
2.
|
|
93
|
+
3.
|
|
94
|
+
|
|
95
|
+
### 投资建议
|
|
96
|
+
|
|
97
|
+
## 风险提示
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
*本报告仅供参考,不构成投资建议*
|
|
101
|
+
"""
|
|
102
|
+
},
|
|
103
|
+
"monthly": {
|
|
104
|
+
"name": "月度报告模板",
|
|
105
|
+
"description": "每月投资总结报告模板",
|
|
106
|
+
"template": """# {title}
|
|
107
|
+
|
|
108
|
+
**期间**: {date}
|
|
109
|
+
**分析师**: 二郎神
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 本月宏观经济
|
|
114
|
+
|
|
115
|
+
### 经济数据
|
|
116
|
+
- GDP:
|
|
117
|
+
- CPI:
|
|
118
|
+
- PPI:
|
|
119
|
+
- PMI:
|
|
120
|
+
- 社融:
|
|
121
|
+
|
|
122
|
+
### 政策取向
|
|
123
|
+
- 货币政策:
|
|
124
|
+
- 财政政策:
|
|
125
|
+
|
|
126
|
+
## 本月市场表现
|
|
127
|
+
|
|
128
|
+
### 股票市场
|
|
129
|
+
| 指数 | 本月涨跌幅 | 成交额 |
|
|
130
|
+
|------|-----------|--------|
|
|
131
|
+
| 上证指数 | - | - |
|
|
132
|
+
| 深证成指 | - | - |
|
|
133
|
+
| 创业板指 | - | - |
|
|
134
|
+
| 沪深300 | - | - |
|
|
135
|
+
|
|
136
|
+
### 债券市场
|
|
137
|
+
### 商品市场
|
|
138
|
+
### 外汇市场
|
|
139
|
+
|
|
140
|
+
## 组合表现
|
|
141
|
+
|
|
142
|
+
### 收益表现
|
|
143
|
+
- 本月收益:
|
|
144
|
+
- YTD收益:
|
|
145
|
+
- 波动率:
|
|
146
|
+
- 最大回撤:
|
|
147
|
+
|
|
148
|
+
### 归因分析
|
|
149
|
+
- 资产配置贡献:
|
|
150
|
+
- 个券选择贡献:
|
|
151
|
+
|
|
152
|
+
## 下月展望
|
|
153
|
+
|
|
154
|
+
### 宏观判断
|
|
155
|
+
### 市场观点
|
|
156
|
+
### 配置建议
|
|
157
|
+
|
|
158
|
+
## 风险提示
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
*本报告仅供参考,不构成投资建议*
|
|
162
|
+
"""
|
|
163
|
+
},
|
|
164
|
+
"research": {
|
|
165
|
+
"name": "个股研究报告模板",
|
|
166
|
+
"description": "个股深度研究报告模板",
|
|
167
|
+
"template": """# {title}
|
|
168
|
+
|
|
169
|
+
**股票代码**:
|
|
170
|
+
**股票名称**:
|
|
171
|
+
**报告日期**: {date}
|
|
172
|
+
**分析师**: 二郎神
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 投资摘要
|
|
177
|
+
|
|
178
|
+
### 核心观点
|
|
179
|
+
(1-2句话概括投资逻辑)
|
|
180
|
+
|
|
181
|
+
### 评级
|
|
182
|
+
- 评级:
|
|
183
|
+
- 目标价:
|
|
184
|
+
- 当前价:
|
|
185
|
+
- 上涨空间:
|
|
186
|
+
|
|
187
|
+
### 投资亮点
|
|
188
|
+
|
|
189
|
+
### 主要风险
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 公司概况
|
|
194
|
+
|
|
195
|
+
### 主营业务
|
|
196
|
+
### 发展历程
|
|
197
|
+
### 股权结构
|
|
198
|
+
|
|
199
|
+
## 行业分析
|
|
200
|
+
|
|
201
|
+
### 行业空间
|
|
202
|
+
### 竞争格局
|
|
203
|
+
### 发展趋势
|
|
204
|
+
|
|
205
|
+
## 业务分析
|
|
206
|
+
|
|
207
|
+
### 业务结构
|
|
208
|
+
### 核心竞争力
|
|
209
|
+
### 成长性分析
|
|
210
|
+
|
|
211
|
+
## 财务分析
|
|
212
|
+
|
|
213
|
+
### 盈利能力
|
|
214
|
+
| 指标 | 2021 | 2022 | 2023 | 2024E |
|
|
215
|
+
|------|------|------|------|--------|
|
|
216
|
+
| 营收(亿) | - | - | - | - |
|
|
217
|
+
| 净利润(亿) | - | - | - | - |
|
|
218
|
+
| 毛利率 | - | - | - | - |
|
|
219
|
+
| 净利率 | - | - | - | - |
|
|
220
|
+
|
|
221
|
+
### 成长性
|
|
222
|
+
### 现金流
|
|
223
|
+
### 资产负债
|
|
224
|
+
|
|
225
|
+
## 估值分析
|
|
226
|
+
|
|
227
|
+
### 相对估值
|
|
228
|
+
### 绝对估值
|
|
229
|
+
### 估值结论
|
|
230
|
+
|
|
231
|
+
## 风险提示
|
|
232
|
+
|
|
233
|
+
1.
|
|
234
|
+
2.
|
|
235
|
+
3.
|
|
236
|
+
|
|
237
|
+
## 投资建议
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
*本报告仅供参考,不构成投资建议*
|
|
241
|
+
"""
|
|
242
|
+
},
|
|
243
|
+
"strategy": {
|
|
244
|
+
"name": "策略报告模板",
|
|
245
|
+
"description": "投资策略报告模板",
|
|
246
|
+
"template": """# {title}
|
|
247
|
+
|
|
248
|
+
**类型**: {date}
|
|
249
|
+
**分析师**: 二郎神
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 核心观点
|
|
254
|
+
|
|
255
|
+
## 宏观背景
|
|
256
|
+
|
|
257
|
+
### 经济周期
|
|
258
|
+
### 政策环境
|
|
259
|
+
### 外部环境
|
|
260
|
+
|
|
261
|
+
## 市场判断
|
|
262
|
+
|
|
263
|
+
### 股票市场
|
|
264
|
+
### 债券市场
|
|
265
|
+
### 商品市场
|
|
266
|
+
### 外汇市场
|
|
267
|
+
|
|
268
|
+
## 资产配置建议
|
|
269
|
+
|
|
270
|
+
### 战略配置
|
|
271
|
+
| 资产类别 | 配置比例 | 变化 |
|
|
272
|
+
|----------|---------|------|
|
|
273
|
+
| 股票 | - | - |
|
|
274
|
+
| 债券 | - | - |
|
|
275
|
+
| 商品 | - | - |
|
|
276
|
+
| 现金 | - | - |
|
|
277
|
+
|
|
278
|
+
### 战术调整
|
|
279
|
+
|
|
280
|
+
## 行业配置
|
|
281
|
+
|
|
282
|
+
### 超配行业
|
|
283
|
+
### 低配行业
|
|
284
|
+
### 规避行业
|
|
285
|
+
|
|
286
|
+
## 风险提示
|
|
287
|
+
|
|
288
|
+
## 跟踪指标
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
*本报告仅供参考,不构成投资建议*
|
|
292
|
+
"""
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def get_template(cls, template_name: str) -> Dict[str, Any]:
|
|
298
|
+
"""获取指定模板"""
|
|
299
|
+
template = cls.TEMPLATES.get(template_name, {})
|
|
300
|
+
|
|
301
|
+
if not template:
|
|
302
|
+
return {}
|
|
303
|
+
|
|
304
|
+
# 填充模板
|
|
305
|
+
filled = template["template"].format(
|
|
306
|
+
title="标题",
|
|
307
|
+
date=datetime.now().strftime("%Y年%m月%d日")
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
"name": template["name"],
|
|
312
|
+
"description": template["description"],
|
|
313
|
+
"template": filled
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def list_templates(cls) -> list:
|
|
318
|
+
"""列出所有模板"""
|
|
319
|
+
return [
|
|
320
|
+
{
|
|
321
|
+
"id": key,
|
|
322
|
+
"name": value["name"],
|
|
323
|
+
"description": value["description"]
|
|
324
|
+
}
|
|
325
|
+
for key, value in cls.TEMPLATES.items()
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
@classmethod
|
|
329
|
+
def render_template(cls, template_name: str, **kwargs) -> str:
|
|
330
|
+
"""渲染模板"""
|
|
331
|
+
template = cls.TEMPLATES.get(template_name)
|
|
332
|
+
|
|
333
|
+
if not template:
|
|
334
|
+
return ""
|
|
335
|
+
|
|
336
|
+
defaults = {
|
|
337
|
+
"title": kwargs.get("title", "报告标题"),
|
|
338
|
+
"date": kwargs.get("date", datetime.now().strftime("%Y年%m月%d日"))
|
|
339
|
+
}
|
|
340
|
+
defaults.update(kwargs)
|
|
341
|
+
|
|
342
|
+
return template["template"].format(**defaults)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tools: market, macro, search, file"""
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Tools - 文件/报告工具
|
|
3
|
+
知识沉淀、纪要写入、报告生成
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileTools:
|
|
13
|
+
"""
|
|
14
|
+
文件工具集
|
|
15
|
+
|
|
16
|
+
工具函数:
|
|
17
|
+
- write_memo: 写入纪要
|
|
18
|
+
- write_report: 写入报告
|
|
19
|
+
- search_knowledge: 搜索知识库
|
|
20
|
+
- append_insight: 追加洞察
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, base_path: Optional[str] = None):
|
|
24
|
+
if base_path is None:
|
|
25
|
+
base_path = Path.home() / ".openclaw-agent-06" / "workspace" / "erlangshen" / "knowledge"
|
|
26
|
+
self.base_path = Path(base_path)
|
|
27
|
+
self.memos_path = self.base_path / "memos"
|
|
28
|
+
self.reports_path = self.base_path / "reports"
|
|
29
|
+
self.insights_path = self.base_path / "insights"
|
|
30
|
+
|
|
31
|
+
for p in [self.memos_path, self.reports_path, self.insights_path]:
|
|
32
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
logger.info(f"FileTools initialized at {self.base_path}")
|
|
35
|
+
|
|
36
|
+
async def execute(self, tool_name: str, **kwargs) -> any:
|
|
37
|
+
"""执行指定工具"""
|
|
38
|
+
method = getattr(self, tool_name, None)
|
|
39
|
+
if method and callable(method):
|
|
40
|
+
return await method(**kwargs)
|
|
41
|
+
return {"error": f"Unknown tool: {tool_name}"}
|
|
42
|
+
|
|
43
|
+
async def write_memo(
|
|
44
|
+
self,
|
|
45
|
+
content: str,
|
|
46
|
+
title: Optional[str] = None,
|
|
47
|
+
) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
写入纪要
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
content: 纪要内容
|
|
53
|
+
title: 标题
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
dict 操作结果
|
|
57
|
+
"""
|
|
58
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
59
|
+
title_str = title or "memo"
|
|
60
|
+
filename = f"{timestamp}_{title_str}.md"
|
|
61
|
+
filepath = self.memos_path / filename
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
filepath.write_text(content, encoding="utf-8")
|
|
65
|
+
logger.info(f"Wrote memo: {filename}")
|
|
66
|
+
return {
|
|
67
|
+
"status": "success",
|
|
68
|
+
"filename": filename,
|
|
69
|
+
"path": str(filepath),
|
|
70
|
+
}
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to write memo: {e}")
|
|
73
|
+
return {"status": "error", "message": str(e)}
|
|
74
|
+
|
|
75
|
+
async def write_report(
|
|
76
|
+
self,
|
|
77
|
+
content: str,
|
|
78
|
+
title: str,
|
|
79
|
+
tags: Optional[list[str]] = None,
|
|
80
|
+
) -> dict:
|
|
81
|
+
"""
|
|
82
|
+
写入报告
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
content: 报告内容
|
|
86
|
+
title: 标题
|
|
87
|
+
tags: 标签
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
dict 操作结果
|
|
91
|
+
"""
|
|
92
|
+
timestamp = datetime.now().strftime("%Y%m%d")
|
|
93
|
+
filename = f"{timestamp}_{title}.md"
|
|
94
|
+
filepath = self.reports_path / filename
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# 添加元数据头
|
|
98
|
+
header = f"""---
|
|
99
|
+
title: {title}
|
|
100
|
+
date: {datetime.now().isoformat()}
|
|
101
|
+
tags: {tags or []}
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
filepath.write_text(header + content, encoding="utf-8")
|
|
106
|
+
logger.info(f"Wrote report: {filename}")
|
|
107
|
+
return {
|
|
108
|
+
"status": "success",
|
|
109
|
+
"filename": filename,
|
|
110
|
+
"path": str(filepath),
|
|
111
|
+
}
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Failed to write report: {e}")
|
|
114
|
+
return {"status": "error", "message": str(e)}
|
|
115
|
+
|
|
116
|
+
async def append_insight(
|
|
117
|
+
self,
|
|
118
|
+
insight: str,
|
|
119
|
+
category: str = "general",
|
|
120
|
+
) -> dict:
|
|
121
|
+
"""
|
|
122
|
+
追加洞察到洞察文件
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
insight: 洞察内容
|
|
126
|
+
category: 分类
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
dict 操作结果
|
|
130
|
+
"""
|
|
131
|
+
timestamp = datetime.now().strftime("%Y%m%d")
|
|
132
|
+
filename = f"insights_{timestamp}.md"
|
|
133
|
+
filepath = self.insights_path / filename
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
content = f"""# 洞察 {datetime.now().strftime("%Y-%m-%d %H:%M")}
|
|
137
|
+
|
|
138
|
+
**分类**: {category}
|
|
139
|
+
|
|
140
|
+
{insight}
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
"""
|
|
144
|
+
filepath.write_text(content, encoding="utf-8")
|
|
145
|
+
logger.info(f"Appended insight: {filename}")
|
|
146
|
+
return {
|
|
147
|
+
"status": "success",
|
|
148
|
+
"filename": filename,
|
|
149
|
+
"path": str(filepath),
|
|
150
|
+
}
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(f"Failed to append insight: {e}")
|
|
153
|
+
return {"status": "error", "message": str(e)}
|
|
154
|
+
|
|
155
|
+
async def search_knowledge(
|
|
156
|
+
self,
|
|
157
|
+
query: str,
|
|
158
|
+
path: Optional[str] = None,
|
|
159
|
+
) -> list[str]:
|
|
160
|
+
"""
|
|
161
|
+
搜索知识库文件
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
query: 搜索关键词
|
|
165
|
+
path: 搜索路径
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
list[str] 匹配的文件列表
|
|
169
|
+
"""
|
|
170
|
+
search_path = Path(path) if path else self.base_path
|
|
171
|
+
results = []
|
|
172
|
+
query_lower = query.lower()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
for filepath in search_path.rglob("*.md"):
|
|
176
|
+
try:
|
|
177
|
+
content = filepath.read_text(encoding="utf-8").lower()
|
|
178
|
+
if query_lower in content:
|
|
179
|
+
results.append(str(filepath))
|
|
180
|
+
except Exception:
|
|
181
|
+
continue
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Search failed: {e}")
|
|
184
|
+
|
|
185
|
+
return results
|
|
186
|
+
|
|
187
|
+
async def list_memos(self) -> list[dict]:
|
|
188
|
+
"""列出所有纪要"""
|
|
189
|
+
memos = []
|
|
190
|
+
for f in sorted(self.memos_path.glob("*.md"), reverse=True):
|
|
191
|
+
memos.append({
|
|
192
|
+
"name": f.name,
|
|
193
|
+
"path": str(f),
|
|
194
|
+
"size": f.stat().st_size,
|
|
195
|
+
"modified": datetime.fromtimestamp(f.stat().st_mtime).isoformat(),
|
|
196
|
+
})
|
|
197
|
+
return memos
|
|
198
|
+
|
|
199
|
+
async def list_reports(self) -> list[dict]:
|
|
200
|
+
"""列出所有报告"""
|
|
201
|
+
reports = []
|
|
202
|
+
for f in sorted(self.reports_path.glob("*.md"), reverse=True):
|
|
203
|
+
reports.append({
|
|
204
|
+
"name": f.name,
|
|
205
|
+
"path": str(f),
|
|
206
|
+
"size": f.stat().st_size,
|
|
207
|
+
"modified": datetime.fromtimestamp(f.stat().st_mtime).isoformat(),
|
|
208
|
+
})
|
|
209
|
+
return reports
|