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,254 @@
1
+ """Agent Runtime Operator Configuration.
2
+
3
+ Provides dataclass-based configuration for AgentRuntimeOperator.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Literal
10
+
11
+
12
+ @dataclass
13
+ class GeneratorConfig:
14
+ """Generator configuration for agent LLM calls.
15
+
16
+ Attributes:
17
+ engine_type: Engine type to use:
18
+ - "sagellm" (default): SageLLMGenerator with configurable backend
19
+ - "openai": OpenAIGenerator for OpenAI-compatible APIs
20
+ - "hf": HFGenerator for HuggingFace models
21
+ backend_type: Backend type for sagellm engine:
22
+ - "auto" (default): Automatically select best available backend
23
+ - "mock": Mock backend for testing without GPU
24
+ - "cuda": NVIDIA CUDA backend
25
+ - "ascend": Huawei Ascend NPU backend
26
+ model_path: Model path or HuggingFace model ID (sagellm only)
27
+ device_map: Device mapping strategy (auto/cuda:0/cpu)
28
+ dtype: Data type (auto/float16/bfloat16)
29
+ max_tokens: Maximum generation tokens
30
+ temperature: Sampling temperature
31
+ top_p: Nucleus sampling parameter
32
+ top_k: Top-k sampling parameter
33
+ timeout: Request timeout in seconds
34
+ default_options: Default generation options
35
+ model_name: Model name for OpenAI (openai only)
36
+ base_url: API base URL (openai only)
37
+ api_key: API key (openai only)
38
+ """
39
+
40
+ engine_type: Literal["sagellm", "openai", "hf"] = "sagellm"
41
+
42
+ # SageLLM options
43
+ backend_type: str = "auto"
44
+ model_path: str = ""
45
+ device_map: str = "auto"
46
+ dtype: str = "auto"
47
+ max_tokens: int = 2048
48
+ temperature: float = 0.7
49
+ top_p: float = 0.95
50
+ top_k: int = 50
51
+ timeout: float = 120.0
52
+ default_options: dict[str, Any] = field(default_factory=dict)
53
+
54
+ # OpenAI options
55
+ model_name: str = ""
56
+ base_url: str = ""
57
+ api_key: str = ""
58
+
59
+ def to_dict(self) -> dict[str, Any]:
60
+ """Convert to dictionary for operator initialization."""
61
+ return {
62
+ "engine_type": self.engine_type,
63
+ "backend_type": self.backend_type,
64
+ "model_path": self.model_path,
65
+ "device_map": self.device_map,
66
+ "dtype": self.dtype,
67
+ "max_tokens": self.max_tokens,
68
+ "temperature": self.temperature,
69
+ "top_p": self.top_p,
70
+ "top_k": self.top_k,
71
+ "timeout": self.timeout,
72
+ "default_options": self.default_options,
73
+ "model_name": self.model_name,
74
+ "base_url": self.base_url,
75
+ "api_key": self.api_key,
76
+ }
77
+
78
+
79
+ @dataclass
80
+ class ProfileConfig:
81
+ """Agent profile configuration.
82
+
83
+ Attributes:
84
+ name: Agent name
85
+ description: Agent description
86
+ role: Agent role (assistant/user/system)
87
+ system_prompt: System prompt for the agent
88
+ """
89
+
90
+ name: str = "DefaultAgent"
91
+ description: str = "A general-purpose AI assistant"
92
+ role: str = "assistant"
93
+ system_prompt: str = "You are a helpful assistant."
94
+
95
+ def to_dict(self) -> dict[str, Any]:
96
+ """Convert to dictionary for operator initialization."""
97
+ return {
98
+ "name": self.name,
99
+ "description": self.description,
100
+ "role": self.role,
101
+ "system_prompt": self.system_prompt,
102
+ }
103
+
104
+
105
+ @dataclass
106
+ class RuntimeSettings:
107
+ """Runtime settings for agent execution.
108
+
109
+ Attributes:
110
+ max_steps: Maximum execution steps
111
+ summarizer: Summarizer config (null/"reuse_generator"/dict)
112
+ """
113
+
114
+ max_steps: int = 6
115
+ summarizer: str | dict[str, Any] | None = "reuse_generator"
116
+
117
+ def to_dict(self) -> dict[str, Any]:
118
+ """Convert to dictionary for operator initialization."""
119
+ return {
120
+ "max_steps": self.max_steps,
121
+ "summarizer": self.summarizer,
122
+ }
123
+
124
+
125
+ @dataclass
126
+ class AgentRuntimeConfig:
127
+ """Complete configuration for AgentRuntimeOperator.
128
+
129
+ Example:
130
+ ```python
131
+ # Create config with mock backend for testing
132
+ config = AgentRuntimeConfig(
133
+ generator=GeneratorConfig(
134
+ engine_type="sagellm",
135
+ backend_type="mock",
136
+ ),
137
+ profile=ProfileConfig(name="TestBot"),
138
+ )
139
+ operator = AgentRuntimeOperator(config=config.to_dict())
140
+
141
+ # Create config with OpenAI
142
+ config = AgentRuntimeConfig(
143
+ generator=GeneratorConfig(
144
+ engine_type="openai",
145
+ model_name="gpt-4o-mini",
146
+ api_key="sk-xxx", # pragma: allowlist secret
147
+ ),
148
+ )
149
+ ```
150
+
151
+ Attributes:
152
+ generator: Generator configuration
153
+ profile: Agent profile configuration
154
+ planner: Planner configuration (optional)
155
+ tools: List of tool specifications
156
+ runtime: Runtime settings
157
+ """
158
+
159
+ generator: GeneratorConfig = field(default_factory=GeneratorConfig)
160
+ profile: ProfileConfig = field(default_factory=ProfileConfig)
161
+ planner: dict[str, Any] = field(default_factory=dict)
162
+ tools: list[dict[str, Any]] = field(default_factory=list)
163
+ runtime: RuntimeSettings = field(default_factory=RuntimeSettings)
164
+
165
+ def to_dict(self) -> dict[str, Any]:
166
+ """Convert to dictionary for operator initialization."""
167
+ return {
168
+ "generator": self.generator.to_dict(),
169
+ "profile": self.profile.to_dict(),
170
+ "planner": self.planner,
171
+ "tools": self.tools,
172
+ "runtime": self.runtime.to_dict(),
173
+ }
174
+
175
+ @classmethod
176
+ def for_mock_testing(cls, profile_name: str = "TestBot") -> AgentRuntimeConfig:
177
+ """Create a configuration for mock testing.
178
+
179
+ Args:
180
+ profile_name: Name for the test agent profile
181
+
182
+ Returns:
183
+ AgentRuntimeConfig configured for mock backend
184
+ """
185
+ return cls(
186
+ generator=GeneratorConfig(
187
+ engine_type="sagellm",
188
+ backend_type="mock",
189
+ ),
190
+ profile=ProfileConfig(name=profile_name),
191
+ )
192
+
193
+ @classmethod
194
+ def for_openai(
195
+ cls,
196
+ model_name: str = "gpt-4o-mini",
197
+ api_key: str = "",
198
+ base_url: str = "https://api.openai.com/v1",
199
+ profile_name: str = "OpenAIAgent",
200
+ ) -> AgentRuntimeConfig:
201
+ """Create a configuration for OpenAI.
202
+
203
+ Args:
204
+ model_name: OpenAI model name
205
+ api_key: OpenAI API key
206
+ base_url: API base URL
207
+ profile_name: Name for the agent profile
208
+
209
+ Returns:
210
+ AgentRuntimeConfig configured for OpenAI
211
+ """
212
+ return cls(
213
+ generator=GeneratorConfig(
214
+ engine_type="openai",
215
+ model_name=model_name,
216
+ api_key=api_key,
217
+ base_url=base_url,
218
+ ),
219
+ profile=ProfileConfig(name=profile_name),
220
+ )
221
+
222
+ @classmethod
223
+ def for_sagellm(
224
+ cls,
225
+ model_path: str,
226
+ backend_type: str = "auto",
227
+ profile_name: str = "SageLLMAgent",
228
+ ) -> AgentRuntimeConfig:
229
+ """Create a configuration for SageLLM.
230
+
231
+ Args:
232
+ model_path: Model path or HuggingFace model ID
233
+ backend_type: Backend type (auto/mock/cuda/ascend)
234
+ profile_name: Name for the agent profile
235
+
236
+ Returns:
237
+ AgentRuntimeConfig configured for SageLLM
238
+ """
239
+ return cls(
240
+ generator=GeneratorConfig(
241
+ engine_type="sagellm",
242
+ backend_type=backend_type,
243
+ model_path=model_path,
244
+ ),
245
+ profile=ProfileConfig(name=profile_name),
246
+ )
247
+
248
+
249
+ __all__ = [
250
+ "AgentRuntimeConfig",
251
+ "GeneratorConfig",
252
+ "ProfileConfig",
253
+ "RuntimeSettings",
254
+ ]
@@ -0,0 +1,125 @@
1
+ """
2
+ Planning Operator
3
+
4
+ Middleware operator for planning using runtime components.
5
+
6
+ Supports engine_type switching:
7
+ - sagellm (default): SageLLMGenerator with configurable backend
8
+ - backend_type="auto": Automatically select best available backend
9
+ - backend_type="mock": Mock backend for testing without GPU
10
+ - backend_type="cuda": NVIDIA CUDA backend
11
+ - backend_type="ascend": Huawei Ascend NPU backend
12
+ - openai: OpenAIGenerator for OpenAI-compatible APIs
13
+ - hf: HFGenerator for HuggingFace models
14
+ """
15
+
16
+ from typing import Any, Optional
17
+
18
+ from sage_libs.sage_agentic.agents.runtime import BenchmarkAdapter, Orchestrator, RuntimeConfig
19
+ from sage_libs.sage_agentic.agents.runtime.config import PlannerConfig
20
+
21
+ from sage.common.core.functions import MapFunction
22
+
23
+ from .runtime import _build_generator
24
+
25
+
26
+ class PlanningOperator(MapFunction):
27
+ """
28
+ Operator for planning.
29
+
30
+ Wraps runtime planner in a middleware operator interface.
31
+
32
+ Args:
33
+ planner: Planner instance (optional)
34
+ config: Configuration dictionary with optional keys:
35
+ - planner: Planner-specific config
36
+ - generator: Generator config with engine_type/backend_type
37
+ - engine_type: Shorthand for generator.engine_type (sagellm/openai/hf)
38
+ - backend_type: Shorthand for generator.backend_type (auto/mock/cuda/ascend)
39
+
40
+ Example:
41
+ ```python
42
+ # Using sagellm with mock backend (for testing)
43
+ operator = PlanningOperator(config={
44
+ "generator": {
45
+ "engine_type": "sagellm",
46
+ "backend_type": "mock",
47
+ },
48
+ })
49
+
50
+ # Using default sagellm with auto backend
51
+ operator = PlanningOperator(config={
52
+ "engine_type": "sagellm",
53
+ "backend_type": "auto",
54
+ })
55
+ ```
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ planner: Optional[Any] = None,
61
+ config: Optional[dict[str, Any]] = None,
62
+ ):
63
+ """Initialize planning operator.
64
+
65
+ Args:
66
+ planner: Planner instance (optional)
67
+ config: Configuration dictionary
68
+ """
69
+ super().__init__()
70
+
71
+ # Parse configuration
72
+ if config is None:
73
+ config = {}
74
+
75
+ self.config = config
76
+
77
+ # Build generator if config provided
78
+ generator_conf = config.get("generator", {})
79
+ # Allow shorthand engine_type/backend_type at top level
80
+ if "engine_type" in config and "engine_type" not in generator_conf:
81
+ generator_conf["engine_type"] = config["engine_type"]
82
+ if "backend_type" in config and "backend_type" not in generator_conf:
83
+ generator_conf["backend_type"] = config["backend_type"]
84
+
85
+ # Build generator (defaults to sagellm with auto backend)
86
+ if generator_conf or not planner:
87
+ engine_type = generator_conf.get("engine_type", "sagellm")
88
+ # Ensure we have at least minimal config
89
+ if not generator_conf:
90
+ generator_conf = {"engine_type": "sagellm", "backend_type": "auto"}
91
+ self.generator = _build_generator(generator_conf, engine_type=engine_type)
92
+ else:
93
+ self.generator = None
94
+
95
+ planner_config = PlannerConfig(**config.get("planner", {}))
96
+ runtime_config = RuntimeConfig(planner=planner_config)
97
+
98
+ # Create orchestrator
99
+ self.orchestrator = Orchestrator(config=runtime_config, planner=planner)
100
+
101
+ # Create adapter for easy use
102
+ self.adapter = BenchmarkAdapter(self.orchestrator)
103
+
104
+ def __call__(self, request: Any) -> Any:
105
+ """Execute planning.
106
+
107
+ Args:
108
+ request: Planning request
109
+
110
+ Returns:
111
+ Generated plan
112
+ """
113
+ return self.adapter.run_planning(request)
114
+
115
+ def execute(self, data: Any) -> Any:
116
+ """Execute map function interface."""
117
+ return self.__call__(data)
118
+
119
+ def get_metrics(self) -> dict[str, Any]:
120
+ """Get performance metrics.
121
+
122
+ Returns:
123
+ Dictionary of metrics
124
+ """
125
+ return self.adapter.get_metrics()
@@ -0,0 +1,132 @@
1
+ """
2
+ RefinedSearcherOperator - Search with optional context compression.
3
+
4
+ Uses isage-refiner for context compression if enabled.
5
+
6
+ Installation:
7
+ pip install isage-refiner # Optional, only if refiner is used
8
+ """
9
+
10
+ import logging
11
+ from typing import Any, AsyncGenerator, Optional
12
+
13
+ from sage_libs.sage_agentic.agents.bots.searcher_bot import SearcherBot
14
+
15
+ from sage.libs.foundation.tools.tool import BaseTool
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class RefinedSearcherOperator:
21
+ """
22
+ L4 Operator that wraps L3 SearcherBot and adds optional refiner capabilities.
23
+
24
+ Uses isage-refiner for context compression when refiner_config is provided.
25
+ """
26
+
27
+ name = "search_internet"
28
+ description = "Search the internet for information using multiple sources (Arxiv, etc)."
29
+ input_schema = {
30
+ "type": "object",
31
+ "properties": {"query": {"type": "string", "description": "The search query"}},
32
+ "required": ["query"],
33
+ }
34
+
35
+ input_types = {"query": "str - The search query"}
36
+
37
+ def __init__(
38
+ self, tools: list[BaseTool], refiner_config: Optional[dict[str, Any]] = None, **kwargs
39
+ ):
40
+ self.bot = SearcherBot(tools=tools, **kwargs)
41
+
42
+ self.compressor = None
43
+ if refiner_config:
44
+ try:
45
+ algorithm = refiner_config.get("algorithm", "long_refiner").lower()
46
+ self._init_compressor(algorithm, refiner_config)
47
+ logger.info(f"RefinedSearcherOperator: Initialized {algorithm} compressor")
48
+ except ImportError as e:
49
+ logger.warning(
50
+ f"RefinedSearcherOperator: isage-refiner not installed: {e}\n"
51
+ f"Install with: pip install isage-refiner"
52
+ )
53
+ except Exception as e:
54
+ logger.warning(f"RefinedSearcherOperator: Failed to init compressor: {e}")
55
+
56
+ def _init_compressor(self, algorithm: str, config: dict[str, Any]):
57
+ """Initialize compressor from isage-refiner."""
58
+ if algorithm == "long_refiner":
59
+ from sage_refiner import LongRefinerCompressor
60
+
61
+ self.compressor = LongRefinerCompressor(
62
+ base_model_path=config.get("base_model_path", "Qwen/Qwen2.5-3B-Instruct"),
63
+ score_model_path=config.get("score_model_path", "BAAI/bge-reranker-v2-m3"),
64
+ max_model_len=config.get("max_model_len", 25000),
65
+ gpu_memory_utilization=config.get("gpu_memory_utilization", 0.5),
66
+ )
67
+ elif algorithm == "reform":
68
+ from sage_refiner import REFORMCompressor
69
+
70
+ self.compressor = REFORMCompressor(**config.get("reform_config", {}))
71
+ elif algorithm == "provence":
72
+ from sage_refiner import ProvenceCompressor
73
+
74
+ self.compressor = ProvenceCompressor(**config.get("provence_config", {}))
75
+ else:
76
+ raise ValueError(f"Unsupported algorithm: {algorithm}")
77
+
78
+ self.budget = config.get("budget", 2048)
79
+
80
+ def call(self, arguments: dict) -> Any:
81
+ """MCP compatible call method"""
82
+ import asyncio
83
+
84
+ query = arguments.get("query")
85
+ try:
86
+ loop = asyncio.get_running_loop()
87
+ if loop.is_running():
88
+ return asyncio.run(self.execute(query))
89
+ except RuntimeError:
90
+ return asyncio.run(self.execute(query))
91
+
92
+ return asyncio.run(self.execute(query))
93
+
94
+ async def execute(self, query: str) -> dict[str, Any]:
95
+ """Execute search and optionally compress results."""
96
+ data = query
97
+ # 1. Execute L3 Bot
98
+ raw_result = await self.bot.execute(data)
99
+ results = raw_result.get("results", [])
100
+
101
+ # 2. Compress if enabled
102
+ if self.compressor and results:
103
+ query_str = data if isinstance(data, str) else data.get("query", "")
104
+ try:
105
+ logger.info(f"Compressing {len(results)} results for query: {query_str}")
106
+
107
+ # Normalize documents to isage-refiner format
108
+ documents = [
109
+ {"contents": r.get("contents") or r.get("text") or str(r)} for r in results
110
+ ]
111
+
112
+ compress_result = self.compressor.compress(
113
+ question=query_str,
114
+ document_list=documents,
115
+ budget=self.budget,
116
+ )
117
+
118
+ return {
119
+ "results": compress_result.get("compressed_context", ""),
120
+ "original_count": len(results),
121
+ "compressed": True,
122
+ }
123
+ except Exception as e:
124
+ logger.error(f"Compression failed: {e}")
125
+ return raw_result
126
+
127
+ return raw_result
128
+
129
+ async def execute_stream(self, data: Any) -> AsyncGenerator[dict[str, Any], None]:
130
+ """Stream execution. Compression is batch, so just stream search events."""
131
+ async for event in self.bot.execute_stream(data):
132
+ yield event