isage-middleware 0.2.4.3__cp311-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.
Files changed (94) hide show
  1. isage_middleware-0.2.4.3.dist-info/METADATA +266 -0
  2. isage_middleware-0.2.4.3.dist-info/RECORD +94 -0
  3. isage_middleware-0.2.4.3.dist-info/WHEEL +5 -0
  4. isage_middleware-0.2.4.3.dist-info/top_level.txt +1 -0
  5. sage/middleware/__init__.py +59 -0
  6. sage/middleware/_version.py +6 -0
  7. sage/middleware/components/__init__.py +30 -0
  8. sage/middleware/components/extensions_compat.py +141 -0
  9. sage/middleware/components/sage_db/__init__.py +116 -0
  10. sage/middleware/components/sage_db/backend.py +136 -0
  11. sage/middleware/components/sage_db/service.py +15 -0
  12. sage/middleware/components/sage_flow/__init__.py +76 -0
  13. sage/middleware/components/sage_flow/python/__init__.py +14 -0
  14. sage/middleware/components/sage_flow/python/micro_service/__init__.py +4 -0
  15. sage/middleware/components/sage_flow/python/micro_service/sage_flow_service.py +88 -0
  16. sage/middleware/components/sage_flow/python/sage_flow.py +30 -0
  17. sage/middleware/components/sage_flow/service.py +14 -0
  18. sage/middleware/components/sage_mem/__init__.py +83 -0
  19. sage/middleware/components/sage_sias/__init__.py +59 -0
  20. sage/middleware/components/sage_sias/continual_learner.py +184 -0
  21. sage/middleware/components/sage_sias/coreset_selector.py +302 -0
  22. sage/middleware/components/sage_sias/types.py +94 -0
  23. sage/middleware/components/sage_tsdb/__init__.py +81 -0
  24. sage/middleware/components/sage_tsdb/python/__init__.py +21 -0
  25. sage/middleware/components/sage_tsdb/python/_sage_tsdb.pyi +17 -0
  26. sage/middleware/components/sage_tsdb/python/algorithms/__init__.py +17 -0
  27. sage/middleware/components/sage_tsdb/python/algorithms/base.py +51 -0
  28. sage/middleware/components/sage_tsdb/python/algorithms/out_of_order_join.py +248 -0
  29. sage/middleware/components/sage_tsdb/python/algorithms/window_aggregator.py +296 -0
  30. sage/middleware/components/sage_tsdb/python/micro_service/__init__.py +7 -0
  31. sage/middleware/components/sage_tsdb/python/micro_service/sage_tsdb_service.py +365 -0
  32. sage/middleware/components/sage_tsdb/python/sage_tsdb.py +523 -0
  33. sage/middleware/components/sage_tsdb/service.py +17 -0
  34. sage/middleware/components/vector_stores/__init__.py +25 -0
  35. sage/middleware/components/vector_stores/chroma.py +483 -0
  36. sage/middleware/components/vector_stores/chroma_adapter.py +185 -0
  37. sage/middleware/components/vector_stores/milvus.py +677 -0
  38. sage/middleware/operators/__init__.py +56 -0
  39. sage/middleware/operators/agent/__init__.py +24 -0
  40. sage/middleware/operators/agent/planning/__init__.py +5 -0
  41. sage/middleware/operators/agent/planning/llm_adapter.py +41 -0
  42. sage/middleware/operators/agent/planning/planner_adapter.py +98 -0
  43. sage/middleware/operators/agent/planning/router.py +107 -0
  44. sage/middleware/operators/agent/runtime.py +296 -0
  45. sage/middleware/operators/agentic/__init__.py +41 -0
  46. sage/middleware/operators/agentic/config.py +254 -0
  47. sage/middleware/operators/agentic/planning_operator.py +125 -0
  48. sage/middleware/operators/agentic/refined_searcher.py +132 -0
  49. sage/middleware/operators/agentic/runtime.py +241 -0
  50. sage/middleware/operators/agentic/timing_operator.py +125 -0
  51. sage/middleware/operators/agentic/tool_selection_operator.py +127 -0
  52. sage/middleware/operators/context/__init__.py +17 -0
  53. sage/middleware/operators/context/critic_evaluation.py +16 -0
  54. sage/middleware/operators/context/model_context.py +565 -0
  55. sage/middleware/operators/context/quality_label.py +12 -0
  56. sage/middleware/operators/context/search_query_results.py +61 -0
  57. sage/middleware/operators/context/search_result.py +42 -0
  58. sage/middleware/operators/context/search_session.py +79 -0
  59. sage/middleware/operators/filters/__init__.py +26 -0
  60. sage/middleware/operators/filters/context_sink.py +387 -0
  61. sage/middleware/operators/filters/context_source.py +376 -0
  62. sage/middleware/operators/filters/evaluate_filter.py +83 -0
  63. sage/middleware/operators/filters/tool_filter.py +74 -0
  64. sage/middleware/operators/llm/__init__.py +18 -0
  65. sage/middleware/operators/llm/sagellm_generator.py +432 -0
  66. sage/middleware/operators/rag/__init__.py +147 -0
  67. sage/middleware/operators/rag/arxiv.py +331 -0
  68. sage/middleware/operators/rag/chunk.py +13 -0
  69. sage/middleware/operators/rag/document_loaders.py +23 -0
  70. sage/middleware/operators/rag/evaluate.py +658 -0
  71. sage/middleware/operators/rag/generator.py +340 -0
  72. sage/middleware/operators/rag/index_builder/__init__.py +48 -0
  73. sage/middleware/operators/rag/index_builder/builder.py +363 -0
  74. sage/middleware/operators/rag/index_builder/manifest.py +101 -0
  75. sage/middleware/operators/rag/index_builder/storage.py +131 -0
  76. sage/middleware/operators/rag/pipeline.py +46 -0
  77. sage/middleware/operators/rag/profiler.py +59 -0
  78. sage/middleware/operators/rag/promptor.py +400 -0
  79. sage/middleware/operators/rag/refiner.py +231 -0
  80. sage/middleware/operators/rag/reranker.py +364 -0
  81. sage/middleware/operators/rag/retriever.py +1308 -0
  82. sage/middleware/operators/rag/searcher.py +37 -0
  83. sage/middleware/operators/rag/types.py +28 -0
  84. sage/middleware/operators/rag/writer.py +80 -0
  85. sage/middleware/operators/tools/__init__.py +71 -0
  86. sage/middleware/operators/tools/arxiv_paper_searcher.py +175 -0
  87. sage/middleware/operators/tools/arxiv_searcher.py +102 -0
  88. sage/middleware/operators/tools/duckduckgo_searcher.py +105 -0
  89. sage/middleware/operators/tools/image_captioner.py +104 -0
  90. sage/middleware/operators/tools/nature_news_fetcher.py +224 -0
  91. sage/middleware/operators/tools/searcher_tool.py +514 -0
  92. sage/middleware/operators/tools/text_detector.py +185 -0
  93. sage/middleware/operators/tools/url_text_extractor.py +104 -0
  94. sage/middleware/py.typed +2 -0
@@ -0,0 +1,376 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from sage.common.core import SourceFunction
6
+ from sage.common.utils.logging.custom_logger import CustomLogger
7
+ from sage.middleware.operators.context.model_context import ModelContext
8
+
9
+
10
+ class ContextFileSource(SourceFunction):
11
+ """
12
+ 从文件加载ModelContext的数据源
13
+ 每次execute读取一个模板文件并返回
14
+ """
15
+
16
+ @staticmethod
17
+ def get_default_template_directory() -> str:
18
+ """
19
+ 获取默认的模板数据目录,与TemplateFileSink保持一致
20
+ """
21
+ project_root = Path(os.getcwd()) # 获取当前工作目录
22
+ template_data_dir = project_root / "data" / "template_data"
23
+ return str(template_data_dir)
24
+
25
+ def __init__(
26
+ self,
27
+ base_directory: str | None = None,
28
+ load_mode: str = "sequential", # "sequential", "recent", "random"
29
+ time_range: tuple[int, int] | None = None,
30
+ sequence_range: tuple[int, int] | None = None,
31
+ include_pattern: str | None = None,
32
+ auto_reset: bool = True,
33
+ **kwargs,
34
+ ):
35
+ """
36
+ 初始化TemplateFileSource
37
+
38
+ Args:
39
+ base_directory: 模板文件基础目录,如果为None则使用默认目录
40
+ load_mode: 加载模式 ("sequential", "recent", "random")
41
+ time_range: 时间范围过滤 (start_timestamp, end_timestamp)
42
+ sequence_range: 序列号范围过滤
43
+ include_pattern: 文件名包含模式
44
+ auto_reset: 当所有文件读完后是否自动重置到开始
45
+ """
46
+ super().__init__(**kwargs)
47
+
48
+ # 如果没有指定base_directory,使用默认目录
49
+ if base_directory is None:
50
+ base_directory = self.get_default_template_directory()
51
+
52
+ self.base_directory = Path(base_directory)
53
+ self.load_mode = load_mode
54
+ self.time_range = time_range
55
+ self.sequence_range = sequence_range
56
+ self.include_pattern = include_pattern
57
+ self.auto_reset = auto_reset
58
+
59
+ self.index_file = self.base_directory / "template_index.json"
60
+
61
+ # 内部状态管理
62
+ self.loaded_count = 0
63
+ self.current_file_index = 0
64
+ self.template_files: list[Path] = []
65
+ self.index_data = None
66
+
67
+ # 初始化文件列表
68
+ self._initialize_file_list()
69
+
70
+ # self.logger.info(f"ContextFileSource initialized: {base_directory}, mode: {load_mode}")
71
+ # self.logger.info(f"Found {len(self.template_files)} template files")
72
+
73
+ def _initialize_file_list(self):
74
+ """初始化文件列表"""
75
+ # 检查目录是否存在
76
+ if not self.base_directory.exists():
77
+ self.logger.warning(f"Template directory does not exist: {self.base_directory}")
78
+ self.template_files = []
79
+ return
80
+
81
+ # 加载索引文件(如果存在)
82
+ self.index_data = self._load_index()
83
+
84
+ if self.load_mode == "recent" and self.index_data:
85
+ # 基于索引按时间排序
86
+ templates_info = list(self.index_data.get("templates", {}).values())
87
+ templates_info.sort(key=lambda x: x["timestamp"], reverse=True)
88
+
89
+ self.template_files = []
90
+ for template_info in templates_info:
91
+ file_path = self.base_directory / template_info["file_path"]
92
+ if file_path.exists():
93
+ self.template_files.append(file_path)
94
+ else:
95
+ # 直接扫描文件系统
96
+ self.template_files = self._find_template_files()
97
+
98
+ if self.load_mode == "sequential":
99
+ # 按文件修改时间排序
100
+ self.template_files.sort(key=lambda f: f.stat().st_mtime)
101
+ elif self.load_mode == "random":
102
+ # 随机打乱
103
+ import random
104
+
105
+ random.shuffle(self.template_files)
106
+
107
+ def _load_index(self) -> dict | None:
108
+ """加载索引文件"""
109
+ if not self.index_file.exists():
110
+ self.logger.debug(f"Index file not found: {self.index_file}")
111
+ return None
112
+
113
+ try:
114
+ with open(self.index_file, encoding="utf-8") as f:
115
+ return json.load(f)
116
+ except Exception as e:
117
+ self.logger.error(f"Failed to load index: {e}")
118
+ return None
119
+
120
+ def _find_template_files(self) -> list[Path]:
121
+ """查找所有模板文件"""
122
+ template_files = []
123
+
124
+ # 递归搜索所有JSON文件
125
+ for json_file in self.base_directory.rglob("*.json"):
126
+ if json_file.name == "template_index.json":
127
+ continue
128
+
129
+ # 应用文件名过滤
130
+ if self.include_pattern and self.include_pattern not in json_file.name:
131
+ continue
132
+
133
+ template_files.append(json_file)
134
+
135
+ return template_files
136
+
137
+ def _load_template_from_file(self, file_path: Path) -> ModelContext | None:
138
+ """从文件加载单个模板"""
139
+ try:
140
+ template = ModelContext.load_from_file(str(file_path))
141
+
142
+ # 应用过滤条件
143
+ if not self._filter_template(template):
144
+ return None
145
+
146
+ return template
147
+ except Exception as e:
148
+ self.logger.error(f"Failed to load template from {file_path}: {e}")
149
+ return None
150
+
151
+ def _filter_template(self, template: ModelContext) -> bool:
152
+ """根据条件过滤单个模板"""
153
+ # 时间范围过滤
154
+ if self.time_range:
155
+ start_time, end_time = self.time_range
156
+ if not (start_time <= template.timestamp <= end_time):
157
+ return False
158
+
159
+ # 序列号范围过滤
160
+ if self.sequence_range:
161
+ start_seq, end_seq = self.sequence_range
162
+ if not (start_seq <= template.sequence <= end_seq):
163
+ return False
164
+
165
+ return True
166
+
167
+ def _get_next_file(self) -> Path | None:
168
+ """获取下一个要读取的文件"""
169
+ if not self.template_files:
170
+ return None
171
+
172
+ # 检查是否已经读完所有文件
173
+ if self.current_file_index >= len(self.template_files):
174
+ if self.auto_reset:
175
+ self.logger.info("All template files processed, resetting to beginning")
176
+ self.current_file_index = 0
177
+
178
+ # 如果是随机模式,重新洗牌
179
+ if self.load_mode == "random":
180
+ import random
181
+
182
+ random.shuffle(self.template_files)
183
+ else:
184
+ self.logger.info("All template files processed, no more files to read")
185
+ return None
186
+
187
+ # 返回当前文件并递增索引
188
+ file_path = self.template_files[self.current_file_index]
189
+ self.current_file_index += 1
190
+
191
+ return file_path
192
+
193
+ def execute(self) -> ModelContext | None:
194
+ """
195
+ 读取下一个ModelContext
196
+
197
+ Returns:
198
+ Optional[ModelContext]: 加载的模板,如果没有更多文件则返回None
199
+ """
200
+ # 最多尝试读取10个文件(避免无限循环)
201
+ max_attempts = 10
202
+ attempts = 0
203
+
204
+ while attempts < max_attempts:
205
+ attempts += 1
206
+
207
+ # 获取下一个文件
208
+ file_path = self._get_next_file()
209
+
210
+ if file_path is None:
211
+ # 没有更多文件可读
212
+ return None
213
+
214
+ # 尝试加载模板
215
+ template = self._load_template_from_file(file_path)
216
+
217
+ if template is not None:
218
+ self.loaded_count += 1
219
+ self.logger.debug(f"Loaded template {template.uuid} from {file_path.name}")
220
+
221
+ # 每加载10个模板记录一次统计
222
+ if self.loaded_count % 10 == 0:
223
+ self.logger.info(f"ContextFileSource: {self.loaded_count} templates loaded")
224
+
225
+ return template
226
+
227
+ # 如果当前文件加载失败,继续尝试下一个文件
228
+ self.logger.debug(f"Failed to load template from {file_path}, trying next file")
229
+
230
+ # 尝试次数用完,返回None
231
+ self.logger.warning(f"Failed to load template after {max_attempts} attempts")
232
+ return None
233
+
234
+ def reset(self):
235
+ """重置数据源到初始状态"""
236
+ self.current_file_index = 0
237
+ self.loaded_count = 0
238
+ self.logger.info("ContextFileSource reset to initial state")
239
+
240
+ def skip_to_index(self, index: int):
241
+ """跳转到指定的文件索引"""
242
+ if 0 <= index < len(self.template_files):
243
+ self.current_file_index = index
244
+ self.logger.info(f"ContextFileSource skipped to index {index}")
245
+ else:
246
+ self.logger.warning(
247
+ f"Invalid index {index}, valid range: 0-{len(self.template_files) - 1}"
248
+ )
249
+
250
+ def get_source_info(self) -> dict:
251
+ """
252
+ 获取数据源信息
253
+
254
+ Returns:
255
+ dict: 数据源统计信息
256
+ """
257
+ return {
258
+ "base_directory": str(self.base_directory),
259
+ "load_mode": self.load_mode,
260
+ "total_files": len(self.template_files),
261
+ "current_index": self.current_file_index,
262
+ "loaded_count": self.loaded_count,
263
+ "directory_exists": self.base_directory.exists(),
264
+ "index_exists": self.index_file.exists(),
265
+ "auto_reset": self.auto_reset,
266
+ "has_more_files": self.current_file_index < len(self.template_files),
267
+ }
268
+
269
+ def has_more_data(self) -> bool:
270
+ """
271
+ 检查是否还有更多数据可读
272
+
273
+ Returns:
274
+ bool: 是否还有更多数据
275
+ """
276
+ if self.auto_reset:
277
+ # 如果自动重置,总是有数据(除非没有文件)
278
+ return len(self.template_files) > 0
279
+ else:
280
+ # 否则检查是否还有未读文件
281
+ return self.current_file_index < len(self.template_files)
282
+
283
+
284
+ class TemplateIndexManager:
285
+ """
286
+ 模板索引管理器,提供高级查询功能
287
+ """
288
+
289
+ def __init__(self, base_directory: str | None = None):
290
+ if base_directory is None:
291
+ base_directory = ContextFileSource.get_default_template_directory()
292
+
293
+ self.base_directory = Path(base_directory)
294
+ self.index_file = self.base_directory / "template_index.json"
295
+
296
+ def search_templates(
297
+ self,
298
+ question_contains: str | None = None,
299
+ has_response: bool | None = None,
300
+ min_chunks: int | None = None,
301
+ time_after: int | None = None,
302
+ ) -> list[dict]:
303
+ """
304
+ 搜索模板记录
305
+
306
+ Args:
307
+ question_contains: 问题包含的文本
308
+ has_response: 是否有响应
309
+ min_chunks: 最小chunk数量
310
+ time_after: 时间戳之后
311
+
312
+ Returns:
313
+ List[dict]: 匹配的模板记录
314
+ """
315
+ try:
316
+ with open(self.index_file, encoding="utf-8") as f:
317
+ index_data = json.load(f)
318
+
319
+ templates = list(index_data.get("templates", {}).values())
320
+
321
+ # 应用过滤条件
322
+ if question_contains:
323
+ templates = [
324
+ t
325
+ for t in templates
326
+ if t.get("raw_question_preview")
327
+ and question_contains.lower() in t["raw_question_preview"].lower()
328
+ ]
329
+
330
+ if has_response is not None:
331
+ templates = [t for t in templates if t.get("has_response") == has_response]
332
+
333
+ if min_chunks is not None:
334
+ templates = [t for t in templates if t.get("chunks_count", 0) >= min_chunks]
335
+
336
+ if time_after is not None:
337
+ templates = [t for t in templates if t.get("timestamp", 0) > time_after]
338
+
339
+ return templates
340
+
341
+ except Exception as e:
342
+ logger = CustomLogger(outputs=[("console", "INFO")], name=__name__)
343
+ logger.error(f"Failed to search templates: {e}")
344
+ return []
345
+
346
+ def get_statistics(self) -> dict:
347
+ """获取模板统计信息"""
348
+ try:
349
+ with open(self.index_file, encoding="utf-8") as f:
350
+ index_data = json.load(f)
351
+
352
+ templates = list(index_data.get("templates", {}).values())
353
+
354
+ stats = {
355
+ "total_templates": len(templates),
356
+ "with_response": sum(1 for t in templates if t.get("has_response")),
357
+ "without_response": sum(1 for t in templates if not t.get("has_response")),
358
+ "avg_chunks": (
359
+ sum(t.get("chunks_count", 0) for t in templates) / len(templates)
360
+ if templates
361
+ else 0
362
+ ),
363
+ "earliest_timestamp": (
364
+ min(t.get("timestamp", 0) for t in templates) if templates else 0
365
+ ),
366
+ "latest_timestamp": (
367
+ max(t.get("timestamp", 0) for t in templates) if templates else 0
368
+ ),
369
+ }
370
+
371
+ return stats
372
+
373
+ except Exception as e:
374
+ logger = CustomLogger(outputs=[("console", "INFO")], name=__name__)
375
+ logger.error(f"Failed to get statistics: {e}")
376
+ return {}
@@ -0,0 +1,83 @@
1
+ from sage.common.core import FilterFunction
2
+ from sage.middleware.operators.context.model_context import ModelContext, QualityLabel
3
+
4
+
5
+ class EvaluateFilter(FilterFunction):
6
+ """
7
+ 评估过滤器 - 基于质量标签上下界过滤
8
+ """
9
+
10
+ def __init__(self, config: dict | None = None, **kwargs):
11
+ """
12
+ 初始化评估过滤器
13
+
14
+ Args:
15
+ config: 配置字典,支持:
16
+ - "upper_bound": str - 质量上界标签
17
+ - "lower_bound": str - 质量下界标签
18
+
19
+ 质量标签优先级(从高到低):
20
+ 1. COMPLETE_EXCELLENT
21
+ 2. COMPLETE_GOOD
22
+ 3. PARTIAL_NEEDS_IMPROVEMENT
23
+ 4. INCOMPLETE_MISSING_INFO
24
+ 5. FAILED_POOR_QUALITY
25
+ 6. ERROR_INVALID
26
+ """
27
+ super().__init__(**kwargs)
28
+
29
+ if not config:
30
+ config = {}
31
+
32
+ # 质量标签优先级映射
33
+ self.quality_priority = {
34
+ QualityLabel.COMPLETE_EXCELLENT: 1,
35
+ QualityLabel.COMPLETE_GOOD: 2,
36
+ QualityLabel.PARTIAL_NEEDS_IMPROVEMENT: 3,
37
+ QualityLabel.INCOMPLETE_MISSING_INFO: 4,
38
+ QualityLabel.FAILED_POOR_QUALITY: 5,
39
+ QualityLabel.ERROR_INVALID: 6,
40
+ }
41
+
42
+ # 解析上下界
43
+ self.upper_bound = self._parse_label(config.get("upper_bound"))
44
+ self.lower_bound = self._parse_label(config.get("lower_bound"))
45
+
46
+ # 计算上下界优先级
47
+ self.upper_priority = (
48
+ self.quality_priority.get(self.upper_bound, 1) if self.upper_bound else 1
49
+ )
50
+ self.lower_priority = (
51
+ self.quality_priority.get(self.lower_bound, 6) if self.lower_bound else 6
52
+ )
53
+
54
+ def _parse_label(self, label_input) -> QualityLabel | None:
55
+ """解析质量标签"""
56
+ if not label_input:
57
+ return None
58
+
59
+ if isinstance(label_input, QualityLabel):
60
+ return label_input
61
+
62
+ if isinstance(label_input, str):
63
+ try:
64
+ return QualityLabel(label_input)
65
+ except ValueError:
66
+ return None
67
+
68
+ return None
69
+
70
+ def execute(self, template: ModelContext) -> bool:
71
+ """执行评估过滤逻辑"""
72
+ evaluation = template.evaluation
73
+
74
+ # 如果没有评估,返回False
75
+ if not evaluation:
76
+ return False
77
+
78
+ # 获取当前质量标签的优先级
79
+ current_priority = self.quality_priority.get(evaluation.label, 6)
80
+
81
+ # 检查是否在上下界范围内
82
+ # 优先级数字越小质量越高,所以要在upper_priority和lower_priority之间
83
+ return self.upper_priority <= current_priority <= self.lower_priority
@@ -0,0 +1,74 @@
1
+ import json
2
+
3
+ from sage.common.core import FilterFunction
4
+ from sage.middleware.operators.context.model_context import ModelContext
5
+
6
+
7
+ class ToolFilter(FilterFunction):
8
+ """
9
+ 工具过滤器 - 只接受config配置
10
+ """
11
+
12
+ def __init__(self, config: dict | None = None, **kwargs):
13
+ """
14
+ 初始化工具过滤器
15
+
16
+ Args:
17
+ config: 配置字典,支持:
18
+ - "tools": str | List[str] | JSON字符串 - 目标工具列表
19
+ - "exclude": str | List[str] | JSON字符串 - 排除工具列表
20
+ - "include_unknown": bool - 是否接受无工具名的模板
21
+ """
22
+ super().__init__(**kwargs)
23
+
24
+ if not config:
25
+ config = {}
26
+
27
+ self.target_tools: set[str] = self._parse_tools(config.get("tools"))
28
+ self.exclude_tools: set[str] = self._parse_tools(config.get("exclude"))
29
+ self.include_unknown: bool = config.get("include_unknown", False)
30
+
31
+ def _parse_tools(self, tools_input) -> set[str]:
32
+ """解析工具输入为工具集合"""
33
+ if not tools_input:
34
+ return set()
35
+
36
+ if isinstance(tools_input, (list, set)):
37
+ return {str(tool) for tool in tools_input}
38
+
39
+ if isinstance(tools_input, str):
40
+ # JSON字符串
41
+ if tools_input.strip().startswith("["):
42
+ try:
43
+ parsed = json.loads(tools_input)
44
+ return {str(tool) for tool in parsed}
45
+ except json.JSONDecodeError:
46
+ pass
47
+
48
+ # 逗号分隔
49
+ if "," in tools_input:
50
+ return {tool.strip() for tool in tools_input.split(",") if tool.strip()}
51
+
52
+ # 单个工具
53
+ return {tools_input.strip()}
54
+
55
+ return set()
56
+
57
+ def execute(self, template: ModelContext) -> bool:
58
+ """执行工具过滤逻辑"""
59
+ tool_name = template.tool_name
60
+
61
+ # 排除列表检查
62
+ if tool_name and tool_name in self.exclude_tools:
63
+ return False
64
+
65
+ # 无工具名处理
66
+ if not tool_name:
67
+ return self.include_unknown
68
+
69
+ # 目标工具检查
70
+ if self.target_tools:
71
+ return tool_name in self.target_tools
72
+
73
+ # 默认接受(如果没有指定目标工具且不在排除列表中)
74
+ return True
@@ -0,0 +1,18 @@
1
+ """
2
+ LLM Operators - 大语言模型推理算子
3
+
4
+ 这个模块包含 LLM 服务的算子实现。
5
+
6
+ 推荐使用 SageLLMGenerator,支持多种后端:
7
+ - backend_type="cuda": NVIDIA GPU (HFCudaEngine)
8
+ - backend_type="mock": 测试模式 (MockEngine)
9
+ - backend_type="ascend": 华为昇腾 (TODO)
10
+
11
+ Breaking Change (v0.3.0):
12
+ VLLMGenerator 和 VLLMEmbedding 已移除。
13
+ 请迁移到 SageLLMGenerator(backend_type="cuda")。
14
+ """
15
+
16
+ from sage.middleware.operators.llm.sagellm_generator import SageLLMGenerator
17
+
18
+ __all__ = ["SageLLMGenerator"]