tooluniverse 1.0.8__py3-none-any.whl → 1.0.9__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 tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +8 -0
- tooluniverse/admetai_tool.py +8 -1
- tooluniverse/compose_scripts/output_summarizer.py +87 -33
- tooluniverse/compose_tool.py +2 -2
- tooluniverse/data/adverse_event_tools.json +97 -98
- tooluniverse/data/agentic_tools.json +81 -162
- tooluniverse/data/compose_tools.json +0 -54
- tooluniverse/data/drug_discovery_agents.json +10 -20
- tooluniverse/data/literature_search_tools.json +15 -35
- tooluniverse/data/monarch_tools.json +1 -2
- tooluniverse/data/opentarget_tools.json +8 -16
- tooluniverse/data/output_summarization_tools.json +23 -20
- tooluniverse/data/packages/bioinformatics_core_tools.json +2 -2
- tooluniverse/data/packages/cheminformatics_tools.json +1 -1
- tooluniverse/data/packages/genomics_tools.json +1 -1
- tooluniverse/data/packages/single_cell_tools.json +1 -1
- tooluniverse/data/packages/structural_biology_tools.json +1 -1
- tooluniverse/data/tool_composition_tools.json +2 -4
- tooluniverse/execute_function.py +39 -1
- tooluniverse/logging_config.py +64 -2
- tooluniverse/molecule_2d_tool.py +9 -3
- tooluniverse/molecule_3d_tool.py +9 -3
- tooluniverse/output_hook.py +217 -150
- tooluniverse/smcp.py +8 -1
- tooluniverse/smcp_server.py +89 -199
- tooluniverse/tools/__init__.py +1 -3
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/METADATA +2 -1
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/RECORD +32 -33
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/entry_points.txt +0 -3
- tooluniverse/tools/MultiAgentLiteratureSearch.py +0 -59
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.dist-info}/top_level.txt +0 -0
tooluniverse/output_hook.py
CHANGED
|
@@ -16,8 +16,12 @@ leveraging AgenticTool and ComposeTool for intelligent output processing.
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
|
+
from dataclasses import dataclass
|
|
19
20
|
from typing import Dict, Any, List, Optional
|
|
20
21
|
from pathlib import Path
|
|
22
|
+
from tooluniverse.logging_config import get_logger
|
|
23
|
+
|
|
24
|
+
_logger = get_logger(__name__)
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
class HookRule:
|
|
@@ -164,9 +168,9 @@ class OutputHook:
|
|
|
164
168
|
def process(
|
|
165
169
|
self,
|
|
166
170
|
result: Any,
|
|
167
|
-
tool_name: str,
|
|
168
|
-
arguments: Dict[str, Any],
|
|
169
|
-
context: Dict[str, Any],
|
|
171
|
+
tool_name: str | None = None,
|
|
172
|
+
arguments: Dict[str, Any] | None = None,
|
|
173
|
+
context: Dict[str, Any] | None = None,
|
|
170
174
|
) -> Any:
|
|
171
175
|
"""
|
|
172
176
|
Process the tool output.
|
|
@@ -189,6 +193,32 @@ class OutputHook:
|
|
|
189
193
|
raise NotImplementedError("Subclasses must implement process method")
|
|
190
194
|
|
|
191
195
|
|
|
196
|
+
@dataclass
|
|
197
|
+
class SummarizationHookConfig:
|
|
198
|
+
composer_tool: str = "OutputSummarizationComposer"
|
|
199
|
+
chunk_size: int = (
|
|
200
|
+
30000 # Increased to 30000 to minimize chunk count and improve success rate
|
|
201
|
+
)
|
|
202
|
+
focus_areas: str = "key_findings_and_results"
|
|
203
|
+
max_summary_length: int = 3000
|
|
204
|
+
composer_timeout_sec: int = 60
|
|
205
|
+
|
|
206
|
+
def validate(self) -> "SummarizationHookConfig":
|
|
207
|
+
# Validate numeric fields; clamp to sensible defaults if invalid
|
|
208
|
+
if not isinstance(self.chunk_size, int) or self.chunk_size <= 0:
|
|
209
|
+
self.chunk_size = 30000
|
|
210
|
+
if not isinstance(self.max_summary_length, int) or self.max_summary_length <= 0:
|
|
211
|
+
self.max_summary_length = 3000
|
|
212
|
+
if (
|
|
213
|
+
not isinstance(self.composer_timeout_sec, int)
|
|
214
|
+
or self.composer_timeout_sec <= 0
|
|
215
|
+
):
|
|
216
|
+
self.composer_timeout_sec = 60
|
|
217
|
+
if not isinstance(self.composer_tool, str) or not self.composer_tool:
|
|
218
|
+
self.composer_tool = "OutputSummarizationComposer"
|
|
219
|
+
return self
|
|
220
|
+
|
|
221
|
+
|
|
192
222
|
class SummarizationHook(OutputHook):
|
|
193
223
|
"""
|
|
194
224
|
Hook for intelligent output summarization using AI.
|
|
@@ -203,13 +233,13 @@ class SummarizationHook(OutputHook):
|
|
|
203
233
|
|
|
204
234
|
Attributes:
|
|
205
235
|
tooluniverse: ToolUniverse instance for tool execution
|
|
206
|
-
|
|
236
|
+
composer_tool (str): Name of the ComposeTool for summarization
|
|
207
237
|
chunk_size (int): Size of chunks for processing large outputs
|
|
208
238
|
focus_areas (str): Areas to focus on during summarization
|
|
209
239
|
max_summary_length (int): Maximum length of final summary
|
|
210
240
|
"""
|
|
211
241
|
|
|
212
|
-
def __init__(self, config: Dict[str, Any], tooluniverse):
|
|
242
|
+
def __init__(self, config: Dict[str, Any] | SummarizationHookConfig, tooluniverse):
|
|
213
243
|
"""
|
|
214
244
|
Initialize the summarization hook.
|
|
215
245
|
|
|
@@ -217,25 +247,34 @@ class SummarizationHook(OutputHook):
|
|
|
217
247
|
config (Dict[str, Any]): Hook configuration
|
|
218
248
|
tooluniverse: ToolUniverse instance for executing summarization tools
|
|
219
249
|
"""
|
|
220
|
-
super().__init__(config)
|
|
250
|
+
super().__init__(config if isinstance(config, dict) else {"hook_config": {}})
|
|
221
251
|
self.tooluniverse = tooluniverse
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
252
|
+
# Normalize input to config dataclass
|
|
253
|
+
if isinstance(config, SummarizationHookConfig):
|
|
254
|
+
cfg = config
|
|
255
|
+
else:
|
|
256
|
+
raw = config.get("hook_config", {}) if isinstance(config, dict) else {}
|
|
257
|
+
# Breaking change: only support composer_tool going forward
|
|
258
|
+
cfg = SummarizationHookConfig(
|
|
259
|
+
composer_tool=raw.get("composer_tool", "OutputSummarizationComposer"),
|
|
260
|
+
chunk_size=raw.get("chunk_size", 2000),
|
|
261
|
+
focus_areas=raw.get("focus_areas", "key_findings_and_results"),
|
|
262
|
+
max_summary_length=raw.get("max_summary_length", 3000),
|
|
263
|
+
composer_timeout_sec=raw.get("composer_timeout_sec", 60),
|
|
264
|
+
)
|
|
265
|
+
self.config_obj = cfg.validate()
|
|
266
|
+
self.composer_tool = self.config_obj.composer_tool
|
|
267
|
+
self.chunk_size = self.config_obj.chunk_size
|
|
268
|
+
self.focus_areas = self.config_obj.focus_areas
|
|
269
|
+
self.max_summary_length = self.config_obj.max_summary_length
|
|
270
|
+
self.composer_timeout_sec = self.config_obj.composer_timeout_sec
|
|
232
271
|
|
|
233
272
|
def process(
|
|
234
273
|
self,
|
|
235
274
|
result: Any,
|
|
236
|
-
tool_name: str,
|
|
237
|
-
arguments: Dict[str, Any],
|
|
238
|
-
context: Dict[str, Any],
|
|
275
|
+
tool_name: Optional[str] = None,
|
|
276
|
+
arguments: Optional[Dict[str, Any]] = None,
|
|
277
|
+
context: Optional[Dict[str, Any]] = None,
|
|
239
278
|
) -> Any:
|
|
240
279
|
"""
|
|
241
280
|
Execute summarization processing using Compose Summarizer Tool.
|
|
@@ -255,31 +294,39 @@ class SummarizationHook(OutputHook):
|
|
|
255
294
|
Any: The summarized output, or original output if summarization fails
|
|
256
295
|
"""
|
|
257
296
|
try:
|
|
297
|
+
# Backward-compat: allow calling process(result) only
|
|
298
|
+
if tool_name is None:
|
|
299
|
+
tool_name = "unknown_tool"
|
|
300
|
+
if arguments is None:
|
|
301
|
+
arguments = {}
|
|
302
|
+
if context is None:
|
|
303
|
+
context = {}
|
|
304
|
+
# Explicitly preserve None and empty string semantics
|
|
305
|
+
if result is None:
|
|
306
|
+
return None
|
|
307
|
+
if isinstance(result, str) and result == "":
|
|
308
|
+
return ""
|
|
258
309
|
# Debug: basic context
|
|
259
310
|
try:
|
|
260
311
|
_len = len(str(result))
|
|
261
312
|
except Exception:
|
|
262
313
|
_len = -1
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
flush=True,
|
|
314
|
+
_logger.debug(
|
|
315
|
+
"SummarizationHook process: tool=%s, result_len=%s, chunk_size=%s, max_summary_length=%s",
|
|
316
|
+
tool_name,
|
|
317
|
+
_len,
|
|
318
|
+
self.chunk_size,
|
|
319
|
+
self.max_summary_length,
|
|
270
320
|
)
|
|
271
321
|
# Check if the required tools are available
|
|
272
322
|
if (
|
|
273
|
-
self.
|
|
274
|
-
and self.
|
|
323
|
+
self.composer_tool not in self.tooluniverse.callable_functions
|
|
324
|
+
and self.composer_tool not in self.tooluniverse.all_tool_dict
|
|
275
325
|
):
|
|
276
|
-
|
|
277
|
-
|
|
326
|
+
_logger.warning(
|
|
327
|
+
"Summarization tool '%s' not available; returning original output",
|
|
328
|
+
self.composer_tool,
|
|
278
329
|
)
|
|
279
|
-
print(
|
|
280
|
-
" This usually means the output_summarization tools are not loaded."
|
|
281
|
-
)
|
|
282
|
-
print(" Returning original output without summarization.")
|
|
283
330
|
return result
|
|
284
331
|
|
|
285
332
|
# Prepare parameters for Compose Summarizer Tool
|
|
@@ -293,10 +340,10 @@ class SummarizationHook(OutputHook):
|
|
|
293
340
|
}
|
|
294
341
|
|
|
295
342
|
# Call Compose Summarizer Tool through ToolUniverse
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
343
|
+
_logger.debug(
|
|
344
|
+
"Calling composer tool '%s' (timeout=%ss)",
|
|
345
|
+
self.composer_tool,
|
|
346
|
+
self.composer_timeout_sec,
|
|
300
347
|
)
|
|
301
348
|
# Run composer with timeout to avoid hangs
|
|
302
349
|
try:
|
|
@@ -306,7 +353,7 @@ class SummarizationHook(OutputHook):
|
|
|
306
353
|
|
|
307
354
|
def _call_composer():
|
|
308
355
|
return self.tooluniverse.run_one_function(
|
|
309
|
-
{"name": self.
|
|
356
|
+
{"name": self.composer_tool, "arguments": composer_args}
|
|
310
357
|
)
|
|
311
358
|
|
|
312
359
|
with ThreadPoolExecutor(max_workers=1) as _pool:
|
|
@@ -314,28 +361,20 @@ class SummarizationHook(OutputHook):
|
|
|
314
361
|
composer_result = _future.result(timeout=self.composer_timeout_sec)
|
|
315
362
|
except Exception as _e_timeout:
|
|
316
363
|
# Timeout or execution error; log and fall back to original output
|
|
317
|
-
|
|
318
|
-
f"[SummarizationHook] composer execution failed/timeout: {_e_timeout}",
|
|
319
|
-
file=_sys.stderr,
|
|
320
|
-
flush=True,
|
|
321
|
-
)
|
|
364
|
+
_logger.warning("Composer execution failed/timeout: %s", _e_timeout)
|
|
322
365
|
return result
|
|
323
366
|
# Debug: show composer result meta
|
|
324
367
|
try:
|
|
325
368
|
if isinstance(composer_result, dict):
|
|
326
369
|
success = composer_result.get("success", False)
|
|
327
370
|
summary_len = len(composer_result.get("summary", ""))
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
371
|
+
_logger.debug(
|
|
372
|
+
"Composer result: success=%s summary_len=%s",
|
|
373
|
+
success,
|
|
374
|
+
summary_len,
|
|
332
375
|
)
|
|
333
376
|
except Exception as _e_dbg:
|
|
334
|
-
|
|
335
|
-
f"[SummarizationHook] debug error inspecting composer_result: {_e_dbg}",
|
|
336
|
-
file=_sys.stderr,
|
|
337
|
-
flush=True,
|
|
338
|
-
)
|
|
377
|
+
_logger.debug("Debug error inspecting composer_result: %s", _e_dbg)
|
|
339
378
|
|
|
340
379
|
# Process Compose Tool result
|
|
341
380
|
if isinstance(composer_result, dict) and composer_result.get("success"):
|
|
@@ -343,28 +382,20 @@ class SummarizationHook(OutputHook):
|
|
|
343
382
|
elif isinstance(composer_result, str):
|
|
344
383
|
return composer_result
|
|
345
384
|
else:
|
|
346
|
-
|
|
347
|
-
|
|
385
|
+
_logger.warning(
|
|
386
|
+
"Compose Summarizer Tool returned unexpected result: %s",
|
|
387
|
+
composer_result,
|
|
348
388
|
)
|
|
349
389
|
return result
|
|
350
390
|
|
|
351
391
|
except Exception as e:
|
|
352
392
|
error_msg = str(e)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
print(
|
|
356
|
-
f"Error in summarization hook: {error_msg}",
|
|
357
|
-
file=_sys.stderr,
|
|
358
|
-
flush=True,
|
|
359
|
-
)
|
|
360
|
-
|
|
393
|
+
_logger.error("Error in summarization hook: %s", error_msg)
|
|
361
394
|
# Check if the error is due to missing tools
|
|
362
395
|
if "not found" in error_msg.lower() or "ToolOutputSummarizer" in error_msg:
|
|
363
|
-
|
|
364
|
-
"
|
|
396
|
+
_logger.error(
|
|
397
|
+
"Required summarization tools are not available. Please ensure the SMCP server is started with hooks enabled."
|
|
365
398
|
)
|
|
366
|
-
print(" Please ensure the SMCP server is started with hooks enabled.")
|
|
367
|
-
|
|
368
399
|
return result
|
|
369
400
|
|
|
370
401
|
def _extract_query_context(self, context: Dict[str, Any]) -> str:
|
|
@@ -424,20 +455,49 @@ class HookManager:
|
|
|
424
455
|
self.tooluniverse = tooluniverse
|
|
425
456
|
self.hooks: List[OutputHook] = []
|
|
426
457
|
self.enabled = True
|
|
458
|
+
# Alias for tests that expect hooks_enabled flag
|
|
459
|
+
self.hooks_enabled = self.enabled
|
|
427
460
|
self.config_path = config.get("config_path", "template/hook_config.json")
|
|
428
461
|
self._pending_tools_to_load: List[str] = []
|
|
429
462
|
self._load_hook_config()
|
|
430
463
|
|
|
431
464
|
# Validate LLM API keys before loading hooks
|
|
432
465
|
if not self._validate_llm_api_keys():
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
"
|
|
466
|
+
_logger.warning("LLM API keys not available. Hooks will be disabled.")
|
|
467
|
+
_logger.info(
|
|
468
|
+
"To enable hooks, please set LLM API keys environment variable."
|
|
436
469
|
)
|
|
470
|
+
# Disable hook processing but still ensure required tools are available
|
|
471
|
+
# Tests expect hook-related tools (e.g., ToolOutputSummarizer) to be registered
|
|
472
|
+
# in ToolUniverse.callable_functions even when hooks are disabled.
|
|
437
473
|
self.enabled = False
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
# Proactively load tools required by summarization hooks so they are discoverable
|
|
477
|
+
all_hook_configs = []
|
|
478
|
+
global_hooks = (
|
|
479
|
+
self.config.get("hooks", [])
|
|
480
|
+
if isinstance(self.config, dict)
|
|
481
|
+
else []
|
|
482
|
+
)
|
|
483
|
+
for hook_cfg in global_hooks:
|
|
484
|
+
all_hook_configs.append(hook_cfg)
|
|
485
|
+
|
|
486
|
+
# Attempt auto-load based on config; if config is empty, fall back to ensuring summarization tools
|
|
487
|
+
self._auto_load_hook_tools(all_hook_configs)
|
|
488
|
+
# Ensure tools are pre-instantiated into callable_functions if possible
|
|
489
|
+
self._ensure_hook_tools_loaded()
|
|
490
|
+
except Exception as _e:
|
|
491
|
+
# Non-fatal: we still proceed with disabled hooks
|
|
492
|
+
_logger.warning("Failed to preload hook tools without API keys: %s", _e)
|
|
493
|
+
|
|
494
|
+
# Do not proceed to create hook instances when disabled
|
|
495
|
+
# Keep hooks list empty and reflect flags
|
|
496
|
+
self.hooks_enabled = self.enabled
|
|
438
497
|
return
|
|
439
498
|
|
|
440
499
|
self._load_hooks()
|
|
500
|
+
self.hooks_enabled = self.enabled
|
|
441
501
|
|
|
442
502
|
def apply_hooks(
|
|
443
503
|
self,
|
|
@@ -482,7 +542,9 @@ class HookManager:
|
|
|
482
542
|
# Check if hook is applicable to current tool
|
|
483
543
|
if self._is_hook_applicable(hook, tool_name, context):
|
|
484
544
|
if hook.should_trigger(result, tool_name, arguments, context):
|
|
485
|
-
|
|
545
|
+
_logger.debug(
|
|
546
|
+
"Applying hook: %s for tool: %s", hook.name, tool_name
|
|
547
|
+
)
|
|
486
548
|
result = hook.process(result, tool_name, arguments, context)
|
|
487
549
|
|
|
488
550
|
return result
|
|
@@ -497,11 +559,11 @@ class HookManager:
|
|
|
497
559
|
from .agentic_tool import AgenticTool
|
|
498
560
|
|
|
499
561
|
if AgenticTool.has_any_api_keys():
|
|
500
|
-
|
|
562
|
+
_logger.debug("LLM API keys validated successfully")
|
|
501
563
|
return True
|
|
502
564
|
else:
|
|
503
|
-
|
|
504
|
-
|
|
565
|
+
_logger.error("LLM API key validation failed: No API keys available")
|
|
566
|
+
_logger.info("To enable hooks, please set API key environment variables.")
|
|
505
567
|
return False
|
|
506
568
|
|
|
507
569
|
def enable_hook(self, hook_name: str):
|
|
@@ -514,9 +576,9 @@ class HookManager:
|
|
|
514
576
|
hook = self.get_hook(hook_name)
|
|
515
577
|
if hook:
|
|
516
578
|
hook.enabled = True
|
|
517
|
-
|
|
579
|
+
_logger.info("Enabled hook: %s", hook_name)
|
|
518
580
|
else:
|
|
519
|
-
|
|
581
|
+
_logger.error("Hook not found: %s", hook_name)
|
|
520
582
|
|
|
521
583
|
def disable_hook(self, hook_name: str):
|
|
522
584
|
"""
|
|
@@ -528,9 +590,9 @@ class HookManager:
|
|
|
528
590
|
hook = self.get_hook(hook_name)
|
|
529
591
|
if hook:
|
|
530
592
|
hook.enabled = False
|
|
531
|
-
|
|
593
|
+
_logger.info("Disabled hook: %s", hook_name)
|
|
532
594
|
else:
|
|
533
|
-
|
|
595
|
+
_logger.error("Hook not found: %s", hook_name)
|
|
534
596
|
|
|
535
597
|
def toggle_hooks(self, enabled: bool):
|
|
536
598
|
"""
|
|
@@ -540,8 +602,23 @@ class HookManager:
|
|
|
540
602
|
enabled (bool): True to enable all hooks, False to disable
|
|
541
603
|
"""
|
|
542
604
|
self.enabled = enabled
|
|
605
|
+
self.hooks_enabled = enabled
|
|
543
606
|
status = "enabled" if enabled else "disabled"
|
|
544
|
-
|
|
607
|
+
_logger.info("Hooks %s", status)
|
|
608
|
+
|
|
609
|
+
# Backward-compat: tests reference enable_hooks/disable_hooks APIs
|
|
610
|
+
def enable_hooks(self):
|
|
611
|
+
"""Enable hooks and (re)load configurations and required tools."""
|
|
612
|
+
self.toggle_hooks(True)
|
|
613
|
+
# Ensure tools and hooks are ready
|
|
614
|
+
self._ensure_hook_tools_loaded()
|
|
615
|
+
self._load_hooks()
|
|
616
|
+
|
|
617
|
+
def disable_hooks(self):
|
|
618
|
+
"""Disable hooks and clear in-memory hook instances."""
|
|
619
|
+
self.toggle_hooks(False)
|
|
620
|
+
# Do not destroy config; just clear active hooks
|
|
621
|
+
self.hooks = []
|
|
545
622
|
|
|
546
623
|
def reload_config(self, config_path: Optional[str] = None):
|
|
547
624
|
"""
|
|
@@ -555,7 +632,7 @@ class HookManager:
|
|
|
555
632
|
self.config_path = config_path
|
|
556
633
|
self._load_hook_config()
|
|
557
634
|
self._load_hooks()
|
|
558
|
-
|
|
635
|
+
_logger.info("Reloaded hook configuration")
|
|
559
636
|
|
|
560
637
|
def get_hook(self, hook_name: str) -> Optional[OutputHook]:
|
|
561
638
|
"""
|
|
@@ -664,8 +741,8 @@ class HookManager:
|
|
|
664
741
|
# Auto-load required tools for hooks
|
|
665
742
|
self._auto_load_hook_tools(all_hook_configs)
|
|
666
743
|
|
|
667
|
-
#
|
|
668
|
-
|
|
744
|
+
# Note: Hook tools will be pre-loaded when ToolUniverse.load_tools() is called
|
|
745
|
+
# This is handled in the _load_pending_tools method
|
|
669
746
|
|
|
670
747
|
# Create hook instances
|
|
671
748
|
for hook_config in all_hook_configs:
|
|
@@ -742,23 +819,25 @@ class HookManager:
|
|
|
742
819
|
missing_tools.append(tool)
|
|
743
820
|
|
|
744
821
|
if missing_tools:
|
|
745
|
-
|
|
746
|
-
|
|
822
|
+
_logger.warning(
|
|
823
|
+
"Some hook tools could not be loaded: %s", missing_tools
|
|
747
824
|
)
|
|
748
|
-
|
|
825
|
+
_logger.info("This may cause summarization hooks to fail.")
|
|
749
826
|
else:
|
|
750
|
-
|
|
751
|
-
|
|
827
|
+
_logger.info(
|
|
828
|
+
"Auto-loaded hook tools: %s",
|
|
829
|
+
", ".join(tools_to_load),
|
|
752
830
|
)
|
|
753
831
|
else:
|
|
754
832
|
# Store tools to load later when ToolUniverse is ready
|
|
755
833
|
self._pending_tools_to_load = tools_to_load
|
|
756
|
-
|
|
757
|
-
|
|
834
|
+
_logger.info(
|
|
835
|
+
"Hook tools queued for loading: %s",
|
|
836
|
+
", ".join(tools_to_load),
|
|
758
837
|
)
|
|
759
838
|
except Exception as e:
|
|
760
|
-
|
|
761
|
-
|
|
839
|
+
_logger.warning("Could not auto-load hook tools: %s", e)
|
|
840
|
+
_logger.info("This will cause summarization hooks to fail.")
|
|
762
841
|
|
|
763
842
|
def _ensure_hook_tools_loaded(self):
|
|
764
843
|
"""
|
|
@@ -779,34 +858,49 @@ class HookManager:
|
|
|
779
858
|
not hasattr(self.tooluniverse, "tool_category_dicts")
|
|
780
859
|
or "output_summarization" not in self.tooluniverse.tool_category_dicts
|
|
781
860
|
):
|
|
782
|
-
|
|
861
|
+
_logger.info("Loading output_summarization tools for hooks")
|
|
783
862
|
self.tooluniverse.load_tools(["output_summarization"])
|
|
784
863
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
864
|
+
# Pre-instantiate hook tools to ensure they're available in callable_functions
|
|
865
|
+
required_tools = ["ToolOutputSummarizer", "OutputSummarizationComposer"]
|
|
866
|
+
for tool_name in required_tools:
|
|
867
|
+
if (
|
|
868
|
+
tool_name in self.tooluniverse.all_tool_dict
|
|
869
|
+
and tool_name not in self.tooluniverse.callable_functions
|
|
870
|
+
):
|
|
871
|
+
try:
|
|
872
|
+
self.tooluniverse.init_tool(
|
|
873
|
+
self.tooluniverse.all_tool_dict[tool_name],
|
|
874
|
+
add_to_cache=True,
|
|
875
|
+
)
|
|
876
|
+
_logger.debug("Pre-loaded hook tool: %s", tool_name)
|
|
877
|
+
except Exception as e:
|
|
878
|
+
_logger.warning(
|
|
879
|
+
"Failed to pre-load hook tool %s: %s", tool_name, e
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Verify the tools were loaded
|
|
883
|
+
missing_tools = []
|
|
884
|
+
for tool in required_tools:
|
|
885
|
+
if (
|
|
886
|
+
hasattr(self.tooluniverse, "callable_functions")
|
|
887
|
+
and tool not in self.tooluniverse.callable_functions
|
|
888
|
+
and hasattr(self.tooluniverse, "all_tool_dict")
|
|
889
|
+
and tool not in self.tooluniverse.all_tool_dict
|
|
890
|
+
):
|
|
891
|
+
missing_tools.append(tool)
|
|
892
|
+
|
|
893
|
+
if missing_tools:
|
|
894
|
+
_logger.warning(
|
|
895
|
+
"Some hook tools could not be loaded: %s", missing_tools
|
|
896
|
+
)
|
|
897
|
+
_logger.info("This may cause summarization hooks to fail")
|
|
804
898
|
else:
|
|
805
|
-
|
|
899
|
+
_logger.info("Hook tools loaded successfully: %s", required_tools)
|
|
806
900
|
|
|
807
901
|
except Exception as e:
|
|
808
|
-
|
|
809
|
-
|
|
902
|
+
_logger.error("Error loading hook tools: %s", e)
|
|
903
|
+
_logger.info("This will cause summarization hooks to fail")
|
|
810
904
|
|
|
811
905
|
def _load_pending_tools(self):
|
|
812
906
|
"""
|
|
@@ -819,12 +913,16 @@ class HookManager:
|
|
|
819
913
|
if self._pending_tools_to_load and hasattr(self.tooluniverse, "all_tools"):
|
|
820
914
|
try:
|
|
821
915
|
self.tooluniverse.load_tools(self._pending_tools_to_load)
|
|
822
|
-
|
|
823
|
-
|
|
916
|
+
_logger.info(
|
|
917
|
+
"Loaded pending hook tools: %s",
|
|
918
|
+
", ".join(self._pending_tools_to_load),
|
|
824
919
|
)
|
|
825
920
|
self._pending_tools_to_load = [] # Clear the pending list
|
|
826
921
|
except Exception as e:
|
|
827
|
-
|
|
922
|
+
_logger.warning("Could not load pending hook tools: %s", e)
|
|
923
|
+
|
|
924
|
+
# Pre-load hook tools if they're available but not instantiated
|
|
925
|
+
self._ensure_hook_tools_loaded()
|
|
828
926
|
|
|
829
927
|
def _is_hook_tool(self, tool_name: str) -> bool:
|
|
830
928
|
"""
|
|
@@ -874,7 +972,7 @@ class HookManager:
|
|
|
874
972
|
file_save_config.update(enhanced_config.get("hook_config", {}))
|
|
875
973
|
return FileSaveHook(file_save_config)
|
|
876
974
|
else:
|
|
877
|
-
|
|
975
|
+
_logger.error("Unknown hook type: %s", hook_type)
|
|
878
976
|
return None
|
|
879
977
|
|
|
880
978
|
def _apply_hook_type_defaults(self, hook_config: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -918,38 +1016,7 @@ class HookManager:
|
|
|
918
1016
|
"default_max_summary_length", 3000
|
|
919
1017
|
),
|
|
920
1018
|
}
|
|
921
|
-
|
|
922
|
-
defaults = {
|
|
923
|
-
"replacement_text": hook_type_defaults.get(
|
|
924
|
-
"default_replacement_text", "[REDACTED]"
|
|
925
|
-
),
|
|
926
|
-
"preserve_structure": hook_type_defaults.get(
|
|
927
|
-
"default_preserve_structure", True
|
|
928
|
-
),
|
|
929
|
-
"log_filtered_items": hook_type_defaults.get(
|
|
930
|
-
"default_log_filtered_items", False
|
|
931
|
-
),
|
|
932
|
-
}
|
|
933
|
-
elif hook_type == "FormattingHook":
|
|
934
|
-
defaults = {
|
|
935
|
-
"indent_size": hook_type_defaults.get("default_indent_size", 2),
|
|
936
|
-
"sort_keys": hook_type_defaults.get("default_sort_keys", True),
|
|
937
|
-
"pretty_print": hook_type_defaults.get("default_pretty_print", True),
|
|
938
|
-
"max_line_length": hook_type_defaults.get(
|
|
939
|
-
"default_max_line_length", 100
|
|
940
|
-
),
|
|
941
|
-
}
|
|
942
|
-
elif hook_type == "ValidationHook":
|
|
943
|
-
defaults = {
|
|
944
|
-
"strict_mode": hook_type_defaults.get("default_strict_mode", False),
|
|
945
|
-
"error_action": hook_type_defaults.get("default_error_action", "warn"),
|
|
946
|
-
}
|
|
947
|
-
elif hook_type == "LoggingHook":
|
|
948
|
-
defaults = {
|
|
949
|
-
"log_level": hook_type_defaults.get("default_log_level", "INFO"),
|
|
950
|
-
"log_format": hook_type_defaults.get("default_log_format", "simple"),
|
|
951
|
-
"max_log_size": hook_type_defaults.get("default_max_log_size", 1000),
|
|
952
|
-
}
|
|
1019
|
+
# Removed unsupported hook types to avoid confusion
|
|
953
1020
|
elif hook_type == "FileSaveHook":
|
|
954
1021
|
defaults = {
|
|
955
1022
|
"temp_dir": hook_type_defaults.get("default_temp_dir", None),
|
tooluniverse/smcp.py
CHANGED
|
@@ -1842,7 +1842,14 @@ class SMCP(FastMCP):
|
|
|
1842
1842
|
│ │
|
|
1843
1843
|
╰────────────────────────────────────────────────────────────────────────────╯
|
|
1844
1844
|
"""
|
|
1845
|
-
|
|
1845
|
+
# In stdio mode, ensure the banner goes to stderr to avoid polluting stdout
|
|
1846
|
+
# which must exclusively carry JSON-RPC messages.
|
|
1847
|
+
import sys as _sys
|
|
1848
|
+
|
|
1849
|
+
if getattr(self, "_transport_type", None) == "stdio":
|
|
1850
|
+
print(banner, file=_sys.stderr)
|
|
1851
|
+
else:
|
|
1852
|
+
print(banner)
|
|
1846
1853
|
|
|
1847
1854
|
def run(self, *args, **kwargs):
|
|
1848
1855
|
"""
|