aigroup-econ-mcp 0.3.8__py3-none-any.whl → 0.4.1__py3-none-any.whl

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.

Potentially problematic release.


This version of aigroup-econ-mcp might be problematic. Click here for more details.

@@ -3,7 +3,7 @@
3
3
  """
4
4
 
5
5
  from . import regression, statistics, time_series, machine_learning, panel_data
6
- from . import validation, cache, monitoring
6
+ from . import validation, cache, monitoring, file_parser
7
7
 
8
8
  __all__ = [
9
9
  "regression",
@@ -13,5 +13,6 @@ __all__ = [
13
13
  "panel_data",
14
14
  "validation",
15
15
  "cache",
16
- "monitoring"
16
+ "monitoring",
17
+ "file_parser"
17
18
  ]
@@ -0,0 +1,171 @@
1
+ """
2
+ 数据加载辅助模块
3
+ 提供通用的CSV文件加载功能
4
+ """
5
+
6
+ from typing import Dict, List, Union
7
+ from pathlib import Path
8
+ import pandas as pd
9
+
10
+
11
+ async def load_data_if_path(
12
+ data: Union[Dict[str, List[float]], str],
13
+ ctx = None
14
+ ) -> Dict[str, List[float]]:
15
+ """
16
+ 智能加载数据:如果是字符串则作为文件路径加载,否则直接返回
17
+
18
+ Args:
19
+ data: 数据字典或CSV文件路径
20
+ ctx: MCP上下文对象(可选,用于日志)
21
+
22
+ Returns:
23
+ 数据字典
24
+
25
+ Raises:
26
+ ValueError: 文件不存在或读取失败
27
+ """
28
+ # 如果已经是字典,直接返回
29
+ if isinstance(data, dict):
30
+ return data
31
+
32
+ # 如果是字符串,作为文件路径处理
33
+ if isinstance(data, str):
34
+ if ctx:
35
+ await ctx.info(f"📁 检测到文件路径,正在加载: {data}")
36
+
37
+ try:
38
+ # 检查文件是否存在
39
+ path = Path(data)
40
+ if not path.exists():
41
+ raise ValueError(f"文件不存在: {data}")
42
+
43
+ # 读取CSV文件
44
+ df = pd.read_csv(path)
45
+
46
+ # 转换为字典格式
47
+ result = {col: df[col].tolist() for col in df.columns}
48
+
49
+ if ctx:
50
+ await ctx.info(f"✅ CSV文件加载成功:{len(df.columns)}个变量,{len(df)}个观测")
51
+
52
+ return result
53
+
54
+ except FileNotFoundError:
55
+ raise ValueError(f"文件不存在: {data}")
56
+ except Exception as e:
57
+ raise ValueError(f"CSV文件读取失败: {str(e)}")
58
+
59
+ # 其他类型报错
60
+
61
+ async def load_single_var_if_path(
62
+ data: Union[List[float], str],
63
+ ctx = None,
64
+ column_name: str = None
65
+ ) -> List[float]:
66
+ """
67
+ 智能加载单变量数据:如果是字符串则作为文件路径加载,否则直接返回
68
+
69
+ Args:
70
+ data: 数据列表或CSV文件路径
71
+ ctx: MCP上下文对象(可选,用于日志)
72
+ column_name: CSV文件中要读取的列名(可选,默认读取第一列)
73
+
74
+ Returns:
75
+ 数据列表
76
+
77
+ Raises:
78
+ ValueError: 文件不存在或读取失败
79
+ """
80
+ # 如果已经是列表,直接返回
81
+ if isinstance(data, list):
82
+ return data
83
+
84
+ # 如果是字符串,作为文件路径处理
85
+ if isinstance(data, str):
86
+ if ctx:
87
+ await ctx.info(f"📁 检测到文件路径,正在加载: {data}")
88
+
89
+ try:
90
+ # 检查文件是否存在
91
+ path = Path(data)
92
+ if not path.exists():
93
+ raise ValueError(f"文件不存在: {data}")
94
+
95
+ # 读取CSV文件
96
+ df = pd.read_csv(path)
97
+
98
+ # 确定要读取的列
99
+ if column_name:
100
+ if column_name not in df.columns:
101
+ raise ValueError(f"列'{column_name}'不存在于CSV文件中。可用列: {list(df.columns)}")
102
+ result = df[column_name].tolist()
103
+ else:
104
+ # 默认读取第一列
105
+ result = df.iloc[:, 0].tolist()
106
+ if ctx:
107
+ await ctx.info(f"未指定列名,使用第一列: {df.columns[0]}")
108
+
109
+ if ctx:
110
+ await ctx.info(f"✅ CSV文件加载成功:{len(result)}个观测")
111
+
112
+ return result
113
+
114
+ except FileNotFoundError:
115
+ raise ValueError(f"文件不存在: {data}")
116
+ except Exception as e:
117
+ raise ValueError(f"CSV文件读取失败: {str(e)}")
118
+
119
+ # 其他类型报错
120
+ raise TypeError(f"不支持的数据类型: {type(data)},期望List或str")
121
+ async def load_x_data_if_path(
122
+ data: Union[List[List[float]], str],
123
+ ctx = None
124
+ ) -> List[List[float]]:
125
+ """
126
+ 智能加载自变量数据:如果是字符串则作为文件路径加载,否则直接返回
127
+
128
+ Args:
129
+ data: 自变量数据(二维列表)或CSV文件路径
130
+ ctx: MCP上下文对象(可选,用于日志)
131
+
132
+ Returns:
133
+ 自变量数据(二维列表)
134
+
135
+ Raises:
136
+ ValueError: 文件不存在或读取失败
137
+ """
138
+ # 如果已经是二维列表,直接返回
139
+ if isinstance(data, list) and all(isinstance(item, list) for item in data):
140
+ return data
141
+
142
+ # 如果是字符串,作为文件路径处理
143
+ if isinstance(data, str):
144
+ if ctx:
145
+ await ctx.info(f"📁 检测到自变量文件路径,正在加载: {data}")
146
+
147
+ try:
148
+ # 检查文件是否存在
149
+ path = Path(data)
150
+ if not path.exists():
151
+ raise ValueError(f"文件不存在: {data}")
152
+
153
+ # 读取CSV文件
154
+ df = pd.read_csv(path)
155
+
156
+ # 转换为二维列表格式
157
+ result = df.values.tolist()
158
+
159
+ if ctx:
160
+ await ctx.info(f"✅ 自变量CSV文件加载成功:{len(result)}个观测,{len(result[0]) if result else 0}个自变量")
161
+
162
+ return result
163
+
164
+ except FileNotFoundError:
165
+ raise ValueError(f"文件不存在: {data}")
166
+ except Exception as e:
167
+ raise ValueError(f"自变量CSV文件读取失败: {str(e)}")
168
+
169
+ # 其他类型报错
170
+ raise TypeError(f"不支持的数据类型: {type(data)},期望List[List[float]]或str")
171
+ raise TypeError(f"不支持的数据类型: {type(data)},期望Dict或str")
@@ -0,0 +1,178 @@
1
+ """
2
+ 工具装饰器模块
3
+ 提供自动文件输入处理、错误处理等功能
4
+ """
5
+
6
+ from typing import Callable, Optional, Dict, Any, List
7
+ from functools import wraps
8
+ from mcp.server.session import ServerSession
9
+ from mcp.server.fastmcp import Context
10
+ from mcp.types import CallToolResult, TextContent
11
+
12
+ from .file_parser import FileParser
13
+
14
+
15
+ def with_file_input(tool_type: str):
16
+ """
17
+ 为工具函数添加文件输入支持的装饰器
18
+
19
+ 支持两种输入方式:
20
+ 1. file_path: CSV/JSON文件路径
21
+ 2. file_content: 文件内容字符串
22
+
23
+ Args:
24
+ tool_type: 工具类型 ('single_var', 'multi_var_dict', 'regression', 'panel', 'time_series')
25
+
26
+ 使用示例:
27
+ @with_file_input('regression')
28
+ async def my_tool(ctx, y_data=None, x_data=None, file_path=None, file_content=None, file_format='auto', **kwargs):
29
+ # 如果提供了file_path或file_content,数据会被自动填充
30
+ pass
31
+ """
32
+ def decorator(func: Callable) -> Callable:
33
+ @wraps(func)
34
+ async def wrapper(*args, **kwargs):
35
+ # 提取上下文和文件参数
36
+ ctx = args[0] if args else kwargs.get('ctx')
37
+ file_path = kwargs.get('file_path')
38
+ file_content = kwargs.get('file_content')
39
+ file_format = kwargs.get('file_format', 'auto')
40
+
41
+ # 优先处理file_path
42
+ if file_path:
43
+ try:
44
+ await ctx.info(f"检测到文件路径输入: {file_path}")
45
+
46
+ # 从文件路径解析
47
+ parsed = FileParser.parse_file_path(file_path, file_format)
48
+
49
+ await ctx.info(
50
+ f"文件解析成功:{parsed['n_variables']}个变量,"
51
+ f"{parsed['n_observations']}个观测"
52
+ )
53
+
54
+ # 转换为工具格式
55
+ converted = FileParser.convert_to_tool_format(parsed, tool_type)
56
+
57
+ # 更新kwargs
58
+ kwargs.update(converted)
59
+
60
+ await ctx.info(f"数据已转换为{tool_type}格式")
61
+
62
+ except Exception as e:
63
+ await ctx.error(f"文件解析失败: {str(e)}")
64
+ return CallToolResult(
65
+ content=[TextContent(type="text", text=f"文件解析错误: {str(e)}")],
66
+ isError=True
67
+ )
68
+
69
+ # 如果没有file_path但有file_content,处理文件内容
70
+ elif file_content:
71
+ try:
72
+ await ctx.info("检测到文件内容输入,开始解析...")
73
+
74
+ # 解析文件内容
75
+ parsed = FileParser.parse_file_content(file_content, file_format)
76
+
77
+ await ctx.info(
78
+ f"文件解析成功:{parsed['n_variables']}个变量,"
79
+ f"{parsed['n_observations']}个观测"
80
+ )
81
+
82
+ # 转换为工具格式
83
+ converted = FileParser.convert_to_tool_format(parsed, tool_type)
84
+
85
+ # 更新kwargs
86
+ kwargs.update(converted)
87
+
88
+ await ctx.info(f"数据已转换为{tool_type}格式")
89
+
90
+ except Exception as e:
91
+ await ctx.error(f"文件解析失败: {str(e)}")
92
+ return CallToolResult(
93
+ content=[TextContent(type="text", text=f"文件解析错误: {str(e)}")],
94
+ isError=True
95
+ )
96
+
97
+ # 调用原函数
98
+ return await func(*args, **kwargs)
99
+
100
+ return wrapper
101
+ return decorator
102
+
103
+
104
+ def with_error_handling(func: Callable) -> Callable:
105
+ """
106
+ 为工具函数添加统一错误处理的装饰器
107
+ """
108
+ @wraps(func)
109
+ async def wrapper(*args, **kwargs):
110
+ ctx = args[0] if args else kwargs.get('ctx')
111
+ tool_name = func.__name__
112
+
113
+ try:
114
+ return await func(*args, **kwargs)
115
+ except Exception as e:
116
+ await ctx.error(f"{tool_name}执行出错: {str(e)}")
117
+ return CallToolResult(
118
+ content=[TextContent(type="text", text=f"错误: {str(e)}")],
119
+ isError=True
120
+ )
121
+
122
+ return wrapper
123
+
124
+
125
+ def with_logging(func: Callable) -> Callable:
126
+ """
127
+ 为工具函数添加日志记录的装饰器
128
+ """
129
+ @wraps(func)
130
+ async def wrapper(*args, **kwargs):
131
+ ctx = args[0] if args else kwargs.get('ctx')
132
+ tool_name = func.__name__
133
+
134
+ await ctx.info(f"开始执行 {tool_name}")
135
+ result = await func(*args, **kwargs)
136
+ await ctx.info(f"{tool_name} 执行完成")
137
+
138
+ return result
139
+
140
+ return wrapper
141
+
142
+
143
+ def econometric_tool(
144
+ tool_type: str,
145
+ with_file_support: bool = True,
146
+ with_error_handling: bool = True,
147
+ with_logging: bool = True
148
+ ):
149
+ """
150
+ 组合装饰器:为计量经济学工具添加所有标准功能
151
+
152
+ Args:
153
+ tool_type: 工具类型
154
+ with_file_support: 是否启用文件输入支持
155
+ with_error_handling: 是否启用错误处理
156
+ with_logging: 是否启用日志记录
157
+
158
+ 使用示例:
159
+ @econometric_tool('regression')
160
+ async def ols_regression(ctx, y_data=None, x_data=None, **kwargs):
161
+ # 只需要编写核心业务逻辑
162
+ pass
163
+ """
164
+ def decorator(func: Callable) -> Callable:
165
+ wrapped = func
166
+
167
+ if with_error_handling:
168
+ wrapped = globals()['with_error_handling'](wrapped)
169
+
170
+ if with_file_support:
171
+ wrapped = with_file_input(tool_type)(wrapped)
172
+
173
+ if with_logging:
174
+ wrapped = globals()['with_logging'](wrapped)
175
+
176
+ return wrapped
177
+
178
+ return decorator
@@ -0,0 +1,268 @@
1
+ """
2
+ 文件输入处理组件
3
+ 提供统一的文件输入处理接口,支持所有工具
4
+ """
5
+
6
+ from typing import Dict, List, Any, Optional, Callable
7
+ from functools import wraps
8
+ from .file_parser import FileParser
9
+
10
+
11
+ class FileInputHandler:
12
+ """
13
+ 文件输入处理组件
14
+
15
+ 使用组件模式,为任何工具函数添加文件输入支持
16
+ """
17
+
18
+ @staticmethod
19
+ def process_input(
20
+ file_content: Optional[str],
21
+ file_format: str,
22
+ tool_type: str,
23
+ data_params: Dict[str, Any]
24
+ ) -> Dict[str, Any]:
25
+ """
26
+ 处理文件输入并转换为工具参数
27
+
28
+ Args:
29
+ file_content: 文件内容
30
+ file_format: 文件格式
31
+ tool_type: 工具类型
32
+ data_params: 当前数据参数
33
+
34
+ Returns:
35
+ 更新后的参数字典
36
+ """
37
+ # 如果没有文件输入,直接返回原参数
38
+ if file_content is None:
39
+ return data_params
40
+
41
+ # 解析文件
42
+ parsed = FileParser.parse_file_content(file_content, file_format)
43
+
44
+ # 转换为工具格式
45
+ converted = FileParser.convert_to_tool_format(parsed, tool_type)
46
+
47
+ # 合并参数(文件数据优先)
48
+ result = {**data_params, **converted}
49
+
50
+ return result
51
+
52
+ @staticmethod
53
+ def with_file_support(tool_type: str):
54
+ """
55
+ 装饰器:为工具函数添加文件输入支持
56
+
57
+ Args:
58
+ tool_type: 工具类型(single_var, multi_var_dict, regression, panel等)
59
+
60
+ Returns:
61
+ 装饰后的函数
62
+
63
+ 使用示例:
64
+ @FileInputHandler.with_file_support('regression')
65
+ async def my_regression_tool(y_data, x_data, file_content=None, file_format='auto'):
66
+ # 函数会自动处理file_content并填充y_data和x_data
67
+ pass
68
+ """
69
+ def decorator(func: Callable) -> Callable:
70
+ @wraps(func)
71
+ async def wrapper(*args, **kwargs):
72
+ # 提取文件相关参数
73
+ file_content = kwargs.get('file_content')
74
+ file_format = kwargs.get('file_format', 'auto')
75
+
76
+ if file_content is not None:
77
+ # 处理文件输入
78
+ processed = FileInputHandler.process_input(
79
+ file_content=file_content,
80
+ file_format=file_format,
81
+ tool_type=tool_type,
82
+ data_params=kwargs
83
+ )
84
+
85
+ # 更新kwargs
86
+ kwargs.update(processed)
87
+
88
+ # 调用原函数
89
+ return await func(*args, **kwargs)
90
+
91
+ return wrapper
92
+ return decorator
93
+
94
+
95
+ class FileInputMixin:
96
+ """
97
+ 文件输入混入类
98
+
99
+ 为类提供文件输入处理能力
100
+ """
101
+
102
+ def parse_file_input(
103
+ self,
104
+ file_content: Optional[str],
105
+ file_format: str = "auto"
106
+ ) -> Optional[Dict[str, Any]]:
107
+ """解析文件输入"""
108
+ if file_content is None:
109
+ return None
110
+ return FileParser.parse_file_content(file_content, file_format)
111
+
112
+ def convert_for_tool(
113
+ self,
114
+ parsed_data: Dict[str, Any],
115
+ tool_type: str
116
+ ) -> Dict[str, Any]:
117
+ """转换为工具格式"""
118
+ return FileParser.convert_to_tool_format(parsed_data, tool_type)
119
+
120
+ def get_recommendations(
121
+ self,
122
+ parsed_data: Dict[str, Any]
123
+ ) -> Dict[str, Any]:
124
+ """获取工具推荐"""
125
+ return FileParser.auto_detect_tool_params(parsed_data)
126
+
127
+
128
+ def create_file_params(
129
+ description: str = "CSV或JSON文件内容"
130
+ ) -> Dict[str, Any]:
131
+ """
132
+ 创建标准的文件输入参数定义
133
+
134
+ Args:
135
+ description: 参数描述
136
+
137
+ Returns:
138
+ 参数定义字典,可直接用于Field()
139
+ """
140
+ return {
141
+ "file_content": {
142
+ "default": None,
143
+ "description": f"""{description}
144
+
145
+ 📁 支持格式:
146
+ - CSV: 带表头的列数据,自动检测分隔符
147
+ - JSON: {{"变量名": [数据], ...}} 或 [{{"变量1": 值, ...}}, ...]
148
+
149
+ 💡 使用方式:
150
+ - 提供文件内容字符串(可以是base64编码)
151
+ - 系统会自动解析并识别变量
152
+ - 优先使用file_content,如果提供则忽略其他数据参数"""
153
+ },
154
+ "file_format": {
155
+ "default": "auto",
156
+ "description": """文件格式
157
+
158
+ 可选值:
159
+ - "auto": 自动检测(默认)
160
+ - "csv": CSV格式
161
+ - "json": JSON格式"""
162
+ }
163
+ }
164
+
165
+
166
+ class UnifiedFileInput:
167
+ """
168
+ 统一文件输入接口
169
+
170
+ 所有工具通过此类统一处理文件输入
171
+ """
172
+
173
+ @staticmethod
174
+ async def handle(
175
+ ctx: Any,
176
+ file_content: Optional[str],
177
+ file_format: str,
178
+ tool_type: str,
179
+ original_params: Dict[str, Any]
180
+ ) -> Dict[str, Any]:
181
+ """
182
+ 统一处理文件输入
183
+
184
+ Args:
185
+ ctx: MCP上下文
186
+ file_content: 文件内容
187
+ file_format: 文件格式
188
+ tool_type: 工具类型
189
+ original_params: 原始参数
190
+
191
+ Returns:
192
+ 处理后的参数
193
+ """
194
+ if file_content is None:
195
+ return original_params
196
+
197
+ try:
198
+ # 记录日志
199
+ await ctx.info("检测到文件输入,开始解析...")
200
+
201
+ # 解析文件
202
+ parsed = FileParser.parse_file_content(file_content, file_format)
203
+
204
+ # 记录解析结果
205
+ await ctx.info(
206
+ f"文件解析成功:{parsed['n_variables']}个变量,"
207
+ f"{parsed['n_observations']}个观测,"
208
+ f"数据类型={parsed['data_type']}"
209
+ )
210
+
211
+ # 转换为工具格式
212
+ converted = FileParser.convert_to_tool_format(parsed, tool_type)
213
+
214
+ # 合并参数
215
+ result = {**original_params}
216
+ result.update(converted)
217
+
218
+ # 记录转换结果
219
+ if tool_type == 'regression':
220
+ await ctx.info(
221
+ f"数据已转换:因变量={converted.get('y_variable')},"
222
+ f"自变量={converted.get('feature_names')}"
223
+ )
224
+ elif tool_type == 'panel':
225
+ await ctx.info(
226
+ f"面板数据已识别:{len(set(converted.get('entity_ids', [])))}个实体,"
227
+ f"{len(set(converted.get('time_periods', [])))}个时间点"
228
+ )
229
+ else:
230
+ await ctx.info(f"数据已转换为{tool_type}格式")
231
+
232
+ return result
233
+
234
+ except Exception as e:
235
+ await ctx.error(f"文件解析失败: {str(e)}")
236
+ raise ValueError(f"文件解析失败: {str(e)}")
237
+
238
+
239
+ # 便捷函数
240
+ async def process_file_for_tool(
241
+ ctx: Any,
242
+ file_content: Optional[str],
243
+ file_format: str,
244
+ tool_type: str,
245
+ **kwargs
246
+ ) -> Dict[str, Any]:
247
+ """
248
+ 为工具处理文件输入的便捷函数
249
+
250
+ 使用示例:
251
+ params = await process_file_for_tool(
252
+ ctx=ctx,
253
+ file_content=file_content,
254
+ file_format=file_format,
255
+ tool_type='regression',
256
+ y_data=y_data,
257
+ x_data=x_data,
258
+ feature_names=feature_names
259
+ )
260
+ # params 现在包含处理后的所有参数
261
+ """
262
+ return await UnifiedFileInput.handle(
263
+ ctx=ctx,
264
+ file_content=file_content,
265
+ file_format=file_format,
266
+ tool_type=tool_type,
267
+ original_params=kwargs
268
+ )