Skip to content

Commit 4faab2b

Browse files
committed
feat(crawl): add local THS data reading operator with configuration
1 parent c71a2c5 commit 4faab2b

6 files changed

Lines changed: 236 additions & 5 deletions

File tree

finance_mcp/config/ths_local.yaml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
flow:
2+
crawl_ths_company:
3+
flow_content: |
4+
ReadLocalThsOp(tag="company")
5+
enable_cache: false
6+
cache_expire_hours: 1
7+
description: "通过A股股票代码获取公司资料信息,例如:详细情况,高管介绍,发行相关,参控股公司,最后返回和query相关的信息。"
8+
input_schema:
9+
code:
10+
type: string
11+
description: "stock code"
12+
required: true
13+
14+
crawl_ths_holder:
15+
flow_content: |
16+
ReadLocalThsOp(tag="holder")
17+
enable_cache: false
18+
cache_expire_hours: 1
19+
description: "通过A股股票代码获取股东研究信息,例如:股东人数、十大流通股东、十大股东、十大债券持有人、控股层级关系,最后返回和query相关的信息。"
20+
input_schema:
21+
code:
22+
type: string
23+
description: "stock code"
24+
required: true
25+
26+
crawl_ths_operate:
27+
flow_content: |
28+
ReadLocalThsOp(tag="operate")
29+
enable_cache: false
30+
cache_expire_hours: 1
31+
description: "通过A股股票代码获取经营分析信息,例如:主营介绍、运营业务数据、主营构成分析、主要客户及供应商、董事会经营评述、产品价格,最后返回和query相关的信息。"
32+
input_schema:
33+
code:
34+
type: string
35+
description: "stock code"
36+
required: true
37+
38+
crawl_ths_equity:
39+
flow_content: |
40+
ReadLocalThsOp(tag="equity")
41+
enable_cache: false
42+
cache_expire_hours: 1
43+
description: "通过A股股票代码获取股本结构信息,例如:解禁时间表、总股本构成、A股结构图、历次股本变动,最后返回和query相关的信息。"
44+
input_schema:
45+
code:
46+
type: string
47+
description: "stock code"
48+
required: true
49+
50+
crawl_ths_capital:
51+
flow_content: |
52+
ReadLocalThsOp(tag="capital")
53+
enable_cache: false
54+
cache_expire_hours: 1
55+
description: "通过A股股票代码获取资本运作信息,例如:募集资金来源、项目投资、收购兼并、股权投资、参股IPO、股权转让、关联交易、质押解冻,最后返回和query相关的信息。"
56+
input_schema:
57+
code:
58+
type: string
59+
description: "stock code"
60+
required: true
61+
62+
crawl_ths_worth:
63+
flow_content: |
64+
ReadLocalThsOp(tag="worth")
65+
enable_cache: false
66+
cache_expire_hours: 1
67+
description: "通过A股股票代码获取盈利预测信息,例如:业绩预测、业绩预测详表、研报评级,最后返回和query相关的信息。"
68+
input_schema:
69+
code:
70+
type: string
71+
description: "stock code"
72+
required: true
73+
74+
crawl_ths_news:
75+
flow_content: |
76+
ReadLocalThsOp(tag="news")
77+
enable_cache: false
78+
cache_expire_hours: 1
79+
description: "通过A股股票代码获取新闻公告信息,例如:新闻与股价联动、公告列表、热点新闻列表、研报列表,最后返回和query相关的信息。"
80+
input_schema:
81+
code:
82+
type: string
83+
description: "stock code"
84+
required: true
85+
86+
crawl_ths_concept:
87+
flow_content: |
88+
ReadLocalThsOp(tag="concept")
89+
enable_cache: false
90+
cache_expire_hours: 1
91+
description: "通过A股股票代码获取概念题材信息,例如:常规概念、其他概念、题材要点、概念对比,最后返回和query相关的信息。"
92+
input_schema:
93+
code:
94+
type: string
95+
description: "stock code"
96+
required: true
97+
98+
crawl_ths_position:
99+
flow_content: |
100+
ReadLocalThsOp(tag="position")
101+
enable_cache: false
102+
cache_expire_hours: 1
103+
description: "通过A股股票代码获取主力持仓信息,例如:机构持股汇总、机构持股明细、被举牌情况、IPO获配机构,最后返回和query相关的信息。"
104+
input_schema:
105+
code:
106+
type: string
107+
description: "stock code"
108+
required: true
109+
110+
crawl_ths_finance:
111+
flow_content: |
112+
ReadLocalThsOp(tag="finance")
113+
enable_cache: false
114+
cache_expire_hours: 1
115+
description: "通过A股股票代码获取财务分析信息,例如:财务诊断、财务指标、指标变动说明、资产负债构成、财务报告、杜邦分析,最后返回和query相关的信息。"
116+
input_schema:
117+
code:
118+
type: string
119+
description: "stock code"
120+
required: true
121+
122+
crawl_ths_bonus:
123+
flow_content: |
124+
ReadLocalThsOp(tag="bonus")
125+
enable_cache: false
126+
cache_expire_hours: 1
127+
description: "通过A股股票代码获取分红融资信息,例如:分红诊断、分红情况、增发机构获配明细、增发概况、配股概况,最后返回和query相关的信息。"
128+
input_schema:
129+
code:
130+
type: string
131+
description: "stock code"
132+
required: true
133+
134+
crawl_ths_event:
135+
flow_content: |
136+
ReadLocalThsOp(tag="event")
137+
enable_cache: false
138+
cache_expire_hours: 1
139+
description: "通过A股股票代码获取公司大事信息,例如:高管持股变动、股东持股变动、担保明细、违规处理、机构调研、投资者互动,最后返回和query相关的信息。"
140+
input_schema:
141+
code:
142+
type: string
143+
description: "stock code"
144+
required: true
145+
146+
crawl_ths_field:
147+
flow_content: |
148+
ReadLocalThsOp(tag="field")
149+
enable_cache: false
150+
cache_expire_hours: 1
151+
description: "通过A股股票代码获取行业对比信息,例如:行业地位、行业新闻,最后返回和query相关的信息。"
152+
input_schema:
153+
code:
154+
type: string
155+
description: "stock code"
156+
required: true

finance_mcp/core/agent/conduct_research_op.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def build_tool_call(self) -> ToolCall:
7474
)
7575

7676
async def async_execute(self):
77-
"""Run the multi-step research loop and produce a final answer.
77+
"""Run the multistep research loop and produce a final answer.
7878
7979
The method performs the following high-level steps:
8080

finance_mcp/core/crawl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
"""
1414

1515
from .crawl4ai_op import Crawl4aiOp, Crawl4aiLongTextOp
16+
from .read_local_ths_op import ReadLocalThsOp
1617
from .ths_url_op import ThsUrlOp
1718

1819
__all__ = [
1920
"Crawl4aiOp",
2021
"Crawl4aiLongTextOp",
2122
"ThsUrlOp",
23+
"ReadLocalThsOp",
2224
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import json
2+
from pathlib import Path
3+
from typing import Dict
4+
5+
from flowllm.core.context import C
6+
from flowllm.core.op import BaseAsyncOp
7+
from loguru import logger
8+
9+
10+
@C.register_op()
11+
class ReadLocalThsOp(BaseAsyncOp):
12+
# Class-level cache: {tag: {code: tool_result}}
13+
_cache: Dict[str, Dict[str, str]] = None
14+
15+
def __init__(self, tag: str = "", **kwargs):
16+
super().__init__(**kwargs)
17+
self.tag: str = tag
18+
# Initialize class-level cache if not exists
19+
if ReadLocalThsOp._cache is None:
20+
ReadLocalThsOp._cache = {}
21+
22+
def _load_cache(self) -> Dict[str, str]:
23+
"""Load all crawl_ths_{tag}*.json files and build code->tool_result mapping."""
24+
tool_cache_dir = Path("tool_cache")
25+
pattern = f"crawl_ths_{self.tag}*.json"
26+
matching_files = list(tool_cache_dir.glob(pattern))
27+
28+
total_files = len(matching_files)
29+
logger.info(f"Found {total_files} files matching pattern '{pattern}'")
30+
31+
result_dict = {}
32+
for idx, file_path in enumerate(matching_files, 1):
33+
logger.info(f"Loading file [{idx}/{total_files}]: {file_path.name}")
34+
35+
with open(file_path, 'r', encoding='utf-8') as f:
36+
data = json.load(f)
37+
38+
file_records = 0
39+
for item in data:
40+
code = item['tool_args']['code']
41+
result_dict[code] = item['tool_result']
42+
file_records += 1
43+
44+
logger.info(f" → Processed {file_records} records from {file_path.name}")
45+
46+
logger.info(f"✓ Successfully loaded {len(result_dict)} total records for tag={self.tag}")
47+
return result_dict
48+
49+
async def async_execute(self):
50+
"""Read tool_result for self.context.code from cached data."""
51+
# Load cache if not exists
52+
if self.tag not in ReadLocalThsOp._cache:
53+
ReadLocalThsOp._cache[self.tag] = self._load_cache()
54+
55+
# Get code from context
56+
code = self.context.code
57+
if not code:
58+
self.context.response.answer = f"No code={code} found in context."
59+
logger.info(self.context.response.answer)
60+
return
61+
62+
# Get tool_result from cache
63+
tool_result = ReadLocalThsOp._cache[self.tag].get(code)
64+
self.context.response.answer = tool_result

test_op/test_crawl4ai_op.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@
66
"""
77

88
import asyncio
9+
import time
910

1011
from finance_mcp import FinanceMcpApp
1112
from finance_mcp.core.crawl import Crawl4aiOp
1213

1314

1415
async def main() -> None:
1516
"""Execute the crawl operation for a sample stock information page."""
16-
17+
t1 = time.time()
1718
async with FinanceMcpApp():
1819
# Instantiate and run the crawling operator against a THS stock page.
1920
op = Crawl4aiOp()
20-
await op.async_call(url="https://stockpage.10jqka.com.cn/601899/")
21+
# await op.async_call(url="https://stockpage.10jqka.com.cn/601899/")
22+
await op.async_call(url="https://basic.10jqka.com.cn/601899/equity.html#stockpage")
2123
print(op.output)
2224

25+
print(f"Total time: {time.time() - t1:.2f}s")
2326

2427
if __name__ == "__main__":
2528
asyncio.run(main())

test_op/test_project_http.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
# Service configuration
1717
service_args = [
1818
"finance-mcp",
19-
"config=default,stream_agent",
19+
"backend=http",
20+
# "config=default,stream_agent",
21+
"config=default,ths_local",
2022
"llm.default.model_name=qwen3-30b-a3b-thinking-2507",
2123
]
2224

@@ -113,7 +115,11 @@ def main() -> None:
113115
for endpoint, data in [
114116
# ("conduct_research", {"research_topic": "茅台怎么样?"}),
115117
# ("dashscope_deep_research", {"query": "茅台怎么样?"}),
116-
("langchain_deep_research", {"query": "茅台怎么样?"}),
118+
# ("langchain_deep_research", {"query": "茅台怎么样?"}),
119+
("crawl_ths_company", {"code": "000001"}),
120+
("crawl_ths_company", {"code": "000002"}),
121+
("crawl_ths_company", {"code": "000004"}),
122+
("crawl_ths_holder", {"code": "000004"}),
117123
]:
118124
test_http_service(
119125
endpoint=endpoint,

0 commit comments

Comments
 (0)