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,56 @@
1
+ """
2
+ SAGE Middleware Operators - 领域算子
3
+
4
+ 这个模块提供面向特定业务领域的算子实现:
5
+ - LLM算子: 大语言模型推理 (SageLLMGenerator)
6
+ - RAG算子: 检索增强生成算子 (Retriever, Refiner, Reranker, Generator等)
7
+ - Tool算子: 工具调用 + 领域特定工具 (arxiv, image_captioner等)
8
+ - Filters: 业务过滤器 (tool_filter, evaluate_filter, context source/sink)
9
+ - Agentic: Agent runtime operators (requires isage-agentic, optional)
10
+
11
+ 向量数据库集成位于: sage.middleware.components.vector_stores
12
+
13
+ 这些算子继承 sage.kernel.operators 的基础算子,实现具体业务逻辑。
14
+
15
+ 使用方式:
16
+ from sage.middleware.operators import rag, llm, tools, filters
17
+
18
+ # 或直接导入
19
+ from sage.middleware.operators.rag import ChromaRetriever
20
+ from sage.middleware.operators.llm import SageLLMGenerator
21
+ from sage.middleware.components.vector_stores import MilvusBackend, ChromaBackend
22
+
23
+ # Agentic operators (requires isage-agentic, install with: pip install isage-middleware[libs])
24
+ from sage.middleware.operators.agentic import PlanningOperator
25
+ """
26
+
27
+ import warnings
28
+
29
+ from sage.middleware.operators.llm.sagellm_generator import SageLLMGenerator
30
+
31
+ # 导出核心子模块 (always available)
32
+ from . import filters, llm, rag, tools
33
+
34
+ # Agentic operators are optional (requires isage-agentic)
35
+ try:
36
+ from . import agentic
37
+
38
+ _HAS_AGENTIC = True
39
+ except ImportError as e:
40
+ _HAS_AGENTIC = False
41
+ agentic = None # type: ignore
42
+ warnings.warn(
43
+ f"Agentic operators not available: {e}\n"
44
+ "Install with: pip install isage-middleware[libs] or pip install isage-agentic",
45
+ UserWarning,
46
+ stacklevel=2,
47
+ )
48
+
49
+ __all__ = [
50
+ "rag",
51
+ "llm",
52
+ "tools",
53
+ "filters",
54
+ "agentic",
55
+ "SageLLMGenerator",
56
+ ]
@@ -0,0 +1,24 @@
1
+ """Agent components for SAGE middleware.
2
+
3
+ Provides agent runtime and planning capabilities.
4
+
5
+ Note: This module requires isage-agentic. Install with:
6
+ pip install isage-middleware[libs] or pip install isage-agentic
7
+ """
8
+
9
+ import warnings
10
+
11
+ try:
12
+ from sage.middleware.operators.agent import runtime
13
+
14
+ _HAS_AGENTIC = True
15
+ __all__ = ["runtime"]
16
+ except ImportError as e:
17
+ _HAS_AGENTIC = False
18
+ runtime = None # type: ignore
19
+ __all__ = []
20
+ warnings.warn(
21
+ f"Agent runtime not available: {e}\nInstall with: pip install isage-agentic",
22
+ UserWarning,
23
+ stacklevel=2,
24
+ )
@@ -0,0 +1,5 @@
1
+ from .llm_adapter import GeneratorToClientAdapter
2
+ from .planner_adapter import SageLibsPlannerAdapter
3
+ from .router import PlannerRouter
4
+
5
+ __all__ = ["PlannerRouter", "SageLibsPlannerAdapter", "GeneratorToClientAdapter"]
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class GeneratorToClientAdapter:
7
+ """
8
+ Adapts OpenAIGenerator/HFGenerator (L4) to UnifiedInferenceClient interface (L2/L3).
9
+ """
10
+
11
+ def __init__(self, generator):
12
+ self.generator = generator
13
+
14
+ def chat(
15
+ self, messages: list[dict[str, str]], temperature: float = 0.7, max_tokens: int = 512
16
+ ) -> str:
17
+ """
18
+ Execute chat completion.
19
+ """
20
+ # OpenAIGenerator.execute takes [user_query, messages] or just messages depending on impl.
21
+ # Let's check OpenAIGenerator.execute signature.
22
+ # Based on usage in LLMPlanner: self.generator.execute([user_query, messages])
23
+ # But here we might not have user_query easily available if it's just a chat call.
24
+ # We can pass the last user message as user_query.
25
+
26
+ user_query = "Chat request"
27
+ for msg in reversed(messages):
28
+ if msg["role"] == "user":
29
+ user_query = msg["content"]
30
+ break
31
+
32
+ # The generator returns (token_usage, text_output)
33
+ _, output = self.generator.execute([user_query, messages])
34
+ return output
35
+
36
+ def generate(self, prompt: str, **kwargs) -> list[dict[str, Any]]:
37
+ """
38
+ Execute text generation.
39
+ """
40
+ _, output = self.generator.execute([prompt, [{"role": "user", "content": prompt}]])
41
+ return [{"generations": [{"text": output}]}]
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any
5
+
6
+ from sage_libs.sage_agentic.agents.planning.schemas import PlannerConfig, PlanRequest, ToolMetadata
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class SageLibsPlannerAdapter:
12
+ """
13
+ Adapts sage-libs planners (ReAct, ToT, Hierarchical) to the AgentRuntime interface.
14
+ """
15
+
16
+ def __init__(self, planner_cls, config: PlannerConfig, llm_client):
17
+ self.planner = planner_cls(config=config, llm_client=llm_client)
18
+
19
+ def plan(
20
+ self,
21
+ profile_system_prompt: str,
22
+ user_query: str,
23
+ tools: dict[str, dict[str, Any]],
24
+ ) -> list[dict[str, Any]]:
25
+ """
26
+ Convert inputs to PlanRequest, call planner, and convert PlanResult to list[dict].
27
+ """
28
+ # 1. Convert tools dict to List[ToolMetadata]
29
+ tool_metadata_list = []
30
+ for name, meta in tools.items():
31
+ tool_metadata_list.append(
32
+ ToolMetadata(
33
+ tool_id=name,
34
+ name=name,
35
+ description=meta.get("description", ""),
36
+ category=meta.get("category", "general"),
37
+ input_schema=meta.get("input_schema", {}),
38
+ )
39
+ )
40
+
41
+ # 2. Create PlanRequest
42
+ request = PlanRequest(
43
+ goal=user_query,
44
+ context={"system_prompt": profile_system_prompt},
45
+ tools=tool_metadata_list,
46
+ max_steps=10, # Default
47
+ min_steps=1,
48
+ )
49
+
50
+ # 3. Call planner
51
+ try:
52
+ result = self.planner.plan(request)
53
+ except Exception as e:
54
+ logger.error(f"Planner {self.planner.name} failed: {e}")
55
+ return [{"type": "reply", "text": f"Planning failed: {str(e)}"}]
56
+
57
+ # 4. Convert PlanResult to list[dict]
58
+ # AgentRuntime expects: [{"type": "tool", "name": "...", "arguments": {...}}, ...]
59
+ runtime_steps = []
60
+
61
+ if not result.steps:
62
+ return [{"type": "reply", "text": "No plan generated."}]
63
+
64
+ for step in result.steps:
65
+ if step.action == "finish":
66
+ # Some planners might use a 'finish' action
67
+ continue
68
+
69
+ # Check if it's a tool call
70
+ # In sage-libs, 'action' is usually the tool name
71
+ # 'inputs' are arguments
72
+
73
+ # Heuristic: if action matches a tool name, it's a tool call
74
+ if step.action in tools:
75
+ runtime_steps.append(
76
+ {"type": "tool", "name": step.action, "arguments": step.inputs}
77
+ )
78
+ else:
79
+ # Treat as thought or unknown action?
80
+ # AgentRuntime doesn't support "thought" steps explicitly in the loop yet,
81
+ # but we can log them or ignore them.
82
+ # If it's a reply-like action?
83
+ pass
84
+
85
+ # If no tool steps, maybe it's a direct reply?
86
+ if not runtime_steps:
87
+ # Try to find a final thought or result
88
+ final_thought = getattr(result, "final_thought", None) or "Plan completed."
89
+ runtime_steps.append({"type": "reply", "text": final_thought})
90
+ else:
91
+ # Append a final reply step if not present?
92
+ # AgentRuntime loop executes steps. If the last step is a tool, it will execute it.
93
+ # Then what? The loop continues?
94
+ # AgentRuntime loop: for step in plan: execute.
95
+ # If plan is static, it executes all steps.
96
+ pass
97
+
98
+ return runtime_steps
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ from typing import Any
6
+
7
+ from sage_libs.sage_agentic.agents.planning.hierarchical_planner import HierarchicalPlanner
8
+ from sage_libs.sage_agentic.agents.planning.react_planner import ReActConfig, ReActPlanner
9
+ from sage_libs.sage_agentic.agents.planning.schemas import PlannerConfig
10
+ from sage_libs.sage_agentic.agents.planning.simple_llm_planner import SimpleLLMPlanner
11
+ from sage_libs.sage_agentic.agents.planning.tot_planner import ToTConfig
12
+ from sage_libs.sage_agentic.agents.planning.tot_planner import TreeOfThoughtsPlanner as ToTPlanner
13
+
14
+ from .llm_adapter import GeneratorToClientAdapter
15
+ from .planner_adapter import SageLibsPlannerAdapter
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class PlannerRouter:
21
+ """
22
+ Routes user queries to the appropriate planner based on intent classification.
23
+ """
24
+
25
+ def __init__(self, generator, default_planner="llm"):
26
+ self.generator = generator
27
+ self.llm_client = GeneratorToClientAdapter(generator)
28
+ self.default_planner_type = default_planner
29
+
30
+ # Initialize planners
31
+ # 1. Simple LLM Planner (Baseline)
32
+ self.simple_planner = SimpleLLMPlanner(generator=generator)
33
+
34
+ # 2. ReAct Planner (Reasoning)
35
+ self.react_planner = SageLibsPlannerAdapter(
36
+ ReActPlanner, ReActConfig(max_iterations=5), self.llm_client
37
+ )
38
+
39
+ # 3. ToT Planner (Complex/Exploratory)
40
+ self.tot_planner = SageLibsPlannerAdapter(
41
+ ToTPlanner, ToTConfig(max_depth=3, branch_factor=3), self.llm_client
42
+ )
43
+
44
+ # 4. Hierarchical Planner (Long-horizon)
45
+ self.hierarchical_planner = SageLibsPlannerAdapter(
46
+ HierarchicalPlanner, PlannerConfig(), self.llm_client
47
+ )
48
+
49
+ def _classify_intent(self, user_query: str) -> str:
50
+ """
51
+ Classify the user query into one of the planner types.
52
+ """
53
+ prompt = """
54
+ You are an expert intent classifier for an AI agent.
55
+ Analyze the user's query and select the most suitable planning strategy.
56
+
57
+ Strategies:
58
+ 1. "simple": For direct questions, simple tasks, or when no tools are needed. (e.g., "Hello", "What is 2+2?")
59
+ 2. "react": For tasks requiring multi-step reasoning and tool usage. (e.g., "Search for X and summarize it")
60
+ 3. "tot": For complex problems requiring exploration of multiple possibilities or creative writing. (e.g., "Write a novel outline", "Solve a complex riddle")
61
+ 4. "hierarchical": For very long, complex tasks with many sub-tasks. (e.g., "Plan a 3-day trip including flights, hotels, and restaurants")
62
+
63
+ User Query: "{query}"
64
+
65
+ Return ONLY the strategy name (simple, react, tot, hierarchical) in JSON format: {{"strategy": "..."}}
66
+ """
67
+ try:
68
+ response = self.llm_client.chat(
69
+ [
70
+ {"role": "system", "content": "You are an intent classifier."},
71
+ {"role": "user", "content": prompt.format(query=user_query)},
72
+ ],
73
+ temperature=0.1,
74
+ )
75
+
76
+ # Parse JSON
77
+ import re
78
+
79
+ match = re.search(r"\{.*\}", response, re.DOTALL)
80
+ if match:
81
+ data = json.loads(match.group(0))
82
+ return data.get("strategy", "simple").lower()
83
+ except Exception as e:
84
+ logger.warning(f"Intent classification failed: {e}. Using default.")
85
+
86
+ return "simple"
87
+
88
+ def plan(
89
+ self,
90
+ profile_system_prompt: str,
91
+ user_query: str,
92
+ tools: dict[str, dict[str, Any]],
93
+ ) -> list[dict[str, Any]]:
94
+ """
95
+ Route to the appropriate planner.
96
+ """
97
+ strategy = self._classify_intent(user_query)
98
+ logger.info(f"Selected planning strategy: {strategy}")
99
+
100
+ if strategy == "react":
101
+ return self.react_planner.plan(profile_system_prompt, user_query, tools)
102
+ elif strategy == "tot":
103
+ return self.tot_planner.plan(profile_system_prompt, user_query, tools)
104
+ elif strategy == "hierarchical":
105
+ return self.hierarchical_planner.plan(profile_system_prompt, user_query, tools)
106
+ else:
107
+ return self.simple_planner.plan(profile_system_prompt, user_query, tools)
@@ -0,0 +1,296 @@
1
+ """
2
+ Agent Runtime (Middleware Layer)
3
+
4
+ This component acts as a Dynamic Pipeline Orchestrator.
5
+ It takes a user query, generates a dynamic execution plan (DAG), and executes it using available tools.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+ from typing import Any
13
+
14
+ # Import from L3 (Libs) - Allowed dependency direction (L4 -> L3)
15
+ from sage_libs.sage_agentic.agents.action.mcp_registry import MCPRegistry
16
+ from sage_libs.sage_agentic.agents.planning import PlanStep, SimpleLLMPlanner
17
+ from sage_libs.sage_agentic.agents.profile.profile import BaseProfile
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _missing_required(arguments: dict[str, Any], input_schema: dict[str, Any]) -> list[str]:
23
+ """基于 MCP JSON Schema 做最小必填参数校验。"""
24
+ req = (input_schema or {}).get("required") or []
25
+ return [k for k in req if k not in arguments]
26
+
27
+
28
+ class AgentRuntime:
29
+ """
30
+ Production-Ready Runtime (Middleware Layer):
31
+ - Input: user_query
32
+ - Process: Planner generates JSON plan -> Step-by-step execution -> Optional LLM summary -> Return
33
+ - Features: Safety checks, Error handling, Structured logging/output
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ profile: BaseProfile,
39
+ planner: SimpleLLMPlanner,
40
+ tools: MCPRegistry,
41
+ summarizer=None,
42
+ max_steps: int = 6,
43
+ ):
44
+ self.profile = profile
45
+ self.planner = planner
46
+ self.tools = tools
47
+ self.summarizer = summarizer
48
+ self.max_steps = max_steps
49
+
50
+ def step_stream(self, user_query: str):
51
+ """
52
+ Execute a single turn of conversation with streaming feedback.
53
+
54
+ Yields:
55
+ Dict containing event type and data
56
+ """
57
+ logger.info(f"AgentRuntime (Middleware) step_stream started for query: {user_query}")
58
+
59
+ observations: list[dict[str, Any]] = []
60
+ plan: list[PlanStep] = []
61
+
62
+ # 1) 生成计划(流式)
63
+ try:
64
+ # 检查 planner 是否支持流式
65
+ if hasattr(self.planner, "plan_stream"):
66
+ for event in self.planner.plan_stream(
67
+ profile_system_prompt=self.profile.render_system_prompt(),
68
+ user_query=user_query,
69
+ tools=self.tools.describe(),
70
+ ):
71
+ if event["type"] == "thought":
72
+ yield {"type": "planning_thought", "content": event["content"]}
73
+ elif event["type"] == "plan":
74
+ plan = event["steps"]
75
+ yield {"type": "plan_generated", "plan": plan}
76
+ else:
77
+ # 降级到非流式
78
+ yield {"type": "planning_thought", "content": "正在生成计划..."}
79
+ plan = self.planner.plan(
80
+ profile_system_prompt=self.profile.render_system_prompt(),
81
+ user_query=user_query,
82
+ tools=self.tools.describe(),
83
+ )
84
+ yield {"type": "plan_generated", "plan": plan}
85
+
86
+ logger.info(f"Plan generated with {len(plan)} steps")
87
+ except Exception as e:
88
+ logger.error(f"Planning failed: {e}")
89
+ yield {"type": "error", "content": f"Planning failed: {str(e)}"}
90
+ return
91
+
92
+ reply_text: str | None = None
93
+
94
+ # 2) 逐步执行
95
+ for i, step in enumerate(plan[: self.max_steps]):
96
+ logger.debug(f"Executing step {i}: {step}")
97
+
98
+ if step.get("type") == "reply":
99
+ reply_text = step.get("text", "").strip()
100
+ logger.info("Plan reached reply step")
101
+ yield {"type": "reply", "content": reply_text}
102
+ break
103
+
104
+ if step.get("type") == "tool":
105
+ name = step.get("name")
106
+ arguments = step.get("arguments", {}) or {}
107
+
108
+ yield {"type": "tool_start", "tool": name, "arguments": arguments}
109
+
110
+ # Safety Check: Validate arguments against schema
111
+ tools_meta = self.tools.describe()
112
+ tool_desc = tools_meta.get(name) if isinstance(name, str) else None
113
+
114
+ if not tool_desc:
115
+ error_msg = f"Tool '{name}' not found in registry"
116
+ logger.warning(error_msg)
117
+ obs = {
118
+ "step": i,
119
+ "tool": name,
120
+ "ok": False,
121
+ "error": error_msg,
122
+ "arguments": arguments,
123
+ }
124
+ observations.append(obs)
125
+ yield {"type": "tool_error", "tool": name, "error": error_msg}
126
+ continue
127
+
128
+ schema = tool_desc.get("input_schema", {}) if tool_desc else {}
129
+ miss = _missing_required(arguments, schema)
130
+
131
+ if miss:
132
+ error_msg = f"Missing required fields: {miss}"
133
+ logger.warning(f"Tool '{name}' validation failed: {error_msg}")
134
+ obs = {
135
+ "step": i,
136
+ "tool": name,
137
+ "ok": False,
138
+ "error": error_msg,
139
+ "arguments": arguments,
140
+ }
141
+ observations.append(obs)
142
+ yield {"type": "tool_error", "tool": name, "error": error_msg}
143
+ continue
144
+
145
+ t0 = time.time()
146
+ try:
147
+ logger.info(f"Calling tool '{name}' with args: {arguments}")
148
+ out = self.tools.call(name, arguments) # type: ignore[arg-type]
149
+ latency = int((time.time() - t0) * 1000)
150
+
151
+ obs = {
152
+ "step": i,
153
+ "tool": name,
154
+ "ok": True,
155
+ "latency_ms": latency,
156
+ "result": out,
157
+ }
158
+ observations.append(obs)
159
+ logger.info(f"Tool '{name}' success ({latency}ms)")
160
+ yield {"type": "tool_result", "tool": name, "result": out}
161
+
162
+ except Exception as e:
163
+ latency = int((time.time() - t0) * 1000)
164
+ logger.error(f"Tool '{name}' failed: {e}")
165
+ obs = {
166
+ "step": i,
167
+ "tool": name,
168
+ "ok": False,
169
+ "latency_ms": latency,
170
+ "error": str(e),
171
+ "arguments": arguments,
172
+ }
173
+ observations.append(obs)
174
+ yield {"type": "tool_error", "tool": name, "error": str(e)}
175
+
176
+ # 3) 汇总输出
177
+ final_reply = ""
178
+
179
+ if reply_text:
180
+ final_reply = reply_text
181
+ elif not observations:
182
+ final_reply = "(没有可执行的步骤或工具返回空结果)"
183
+ elif self.summarizer:
184
+ yield {"type": "planning_thought", "content": "正在汇总执行结果..."}
185
+ # 用你的生成器来生成自然语言总结
186
+ profile_hint = self.profile.render_system_prompt()
187
+ prompt = f"""请将以下工具步骤结果用中文简洁汇总给用户,保留关键信息和结论。
188
+
189
+ [Profile]
190
+ {profile_hint}
191
+
192
+ [Observations]
193
+ {observations}
194
+
195
+ 只输出给用户的总结文本。"""
196
+ messages = [
197
+ {
198
+ "role": "system",
199
+ "content": "你是一个严谨的助理。只输出中文总结,不要额外解释。",
200
+ },
201
+ {"role": "user", "content": prompt},
202
+ ]
203
+ try:
204
+ _, summary = self.summarizer.execute([None, messages])
205
+ final_reply = summary.strip()
206
+ yield {"type": "reply", "content": final_reply}
207
+ except Exception as e:
208
+ logger.error(f"Summarization failed: {e}")
209
+ final_reply = "Summarization failed."
210
+ yield {"type": "error", "content": "Summarization failed."}
211
+ else:
212
+ # 简单模板
213
+ lines = []
214
+ for obs in observations:
215
+ if obs.get("ok"):
216
+ lines.append(f"#{obs['step'] + 1} 工具 {obs['tool']} 成功:{obs.get('result')}")
217
+ else:
218
+ lines.append(f"#{obs['step'] + 1} 工具 {obs['tool']} 失败:{obs.get('error')}")
219
+ final_reply = "\n".join(lines)
220
+ yield {"type": "reply", "content": final_reply}
221
+
222
+ yield {
223
+ "type": "completed",
224
+ "observations": observations,
225
+ "plan": plan,
226
+ "reply": final_reply,
227
+ }
228
+
229
+ def step(self, user_query: str) -> dict[str, Any]:
230
+ """
231
+ Execute a single turn of conversation.
232
+
233
+ Returns:
234
+ Dict containing:
235
+ - reply: The final text response
236
+ - observations: List of execution steps and results
237
+ - plan: The original plan
238
+ """
239
+ # 兼容旧接口,收集流式结果
240
+ result = {"reply": "", "observations": [], "plan": []}
241
+
242
+ for event in self.step_stream(user_query):
243
+ if event["type"] == "completed":
244
+ result["reply"] = event.get("reply", "")
245
+ result["observations"] = event.get("observations", [])
246
+ result["plan"] = event.get("plan", [])
247
+
248
+ return result
249
+
250
+ def execute(self, data: Any) -> dict[str, Any]:
251
+ """
252
+ Unified Entry Point.
253
+
254
+ Args:
255
+ data: str (query) or dict (config + query)
256
+
257
+ Returns:
258
+ Dict containing 'reply', 'observations', 'plan'
259
+ """
260
+ # 形态 1:直接字符串
261
+ if isinstance(data, str):
262
+ return self.step(data)
263
+
264
+ # 形态 2:字典
265
+ if isinstance(data, dict):
266
+ user_query = data.get("user_query") or data.get("query")
267
+ if not isinstance(user_query, str) or not user_query.strip():
268
+ raise ValueError(
269
+ "AgentRuntime.execute(dict) 需要提供 'user_query' 或 'query'(非空字符串)。"
270
+ )
271
+
272
+ # 临时覆写 max_steps
273
+ original_max = self.max_steps
274
+ if "max_steps" in data:
275
+ ms = data["max_steps"]
276
+ if not isinstance(ms, int) or ms <= 0:
277
+ raise ValueError("'max_steps' 必须是正整数。")
278
+ self.max_steps = ms
279
+
280
+ # 临时覆写 profile(一次性,不污染实例)
281
+ original_profile = self.profile
282
+ if "profile_overrides" in data and isinstance(data["profile_overrides"], dict):
283
+ try:
284
+ self.profile = self.profile.merged(**data["profile_overrides"])
285
+ except Exception:
286
+ # 失败则回退,不中断主流程
287
+ self.profile = original_profile
288
+
289
+ try:
290
+ return self.step(user_query)
291
+ finally:
292
+ # 还原
293
+ self.max_steps = original_max
294
+ self.profile = original_profile
295
+
296
+ raise TypeError("AgentRuntime.execute 仅接受 str 或 dict 两种输入。")
@@ -0,0 +1,41 @@
1
+ """L4 Agentic Operators.
2
+
3
+ This package exposes ready-to-use operator wrappers (MapOperators) built on
4
+ sage.libs.agentic components so Studio and pipeline builders can drag-and-drop
5
+ agent runtimes without wiring boilerplate.
6
+
7
+ Supports engine_type switching for LLM generators:
8
+ - sagellm (default): SageLLMGenerator with configurable backend
9
+ - backend_type="auto": Automatically select best available backend
10
+ - backend_type="mock": Mock backend for testing without GPU
11
+ - backend_type="cuda": NVIDIA CUDA backend
12
+ - backend_type="ascend": Huawei Ascend NPU backend
13
+ - openai: OpenAIGenerator for OpenAI-compatible APIs
14
+ - hf: HFGenerator for HuggingFace models
15
+ """
16
+
17
+ from .config import (
18
+ AgentRuntimeConfig,
19
+ GeneratorConfig,
20
+ ProfileConfig,
21
+ RuntimeSettings,
22
+ )
23
+ from .planning_operator import PlanningOperator
24
+ from .refined_searcher import RefinedSearcherOperator
25
+ from .runtime import AgentRuntimeOperator
26
+ from .timing_operator import TimingOperator
27
+ from .tool_selection_operator import ToolSelectionOperator
28
+
29
+ __all__ = [
30
+ # Operators
31
+ "AgentRuntimeOperator",
32
+ "ToolSelectionOperator",
33
+ "PlanningOperator",
34
+ "TimingOperator",
35
+ "RefinedSearcherOperator",
36
+ # Config classes
37
+ "AgentRuntimeConfig",
38
+ "GeneratorConfig",
39
+ "ProfileConfig",
40
+ "RuntimeSettings",
41
+ ]