mcp-mesh 0.5.7__py3-none-any.whl → 0.6.0__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.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/base_injector.py +171 -0
- _mcp_mesh/engine/decorator_registry.py +136 -33
- _mcp_mesh/engine/dependency_injector.py +91 -18
- _mcp_mesh/engine/http_wrapper.py +5 -22
- _mcp_mesh/engine/llm_config.py +41 -0
- _mcp_mesh/engine/llm_errors.py +115 -0
- _mcp_mesh/engine/mesh_llm_agent.py +440 -0
- _mcp_mesh/engine/mesh_llm_agent_injector.py +487 -0
- _mcp_mesh/engine/response_parser.py +240 -0
- _mcp_mesh/engine/signature_analyzer.py +229 -99
- _mcp_mesh/engine/tool_executor.py +169 -0
- _mcp_mesh/engine/tool_schema_builder.py +125 -0
- _mcp_mesh/engine/unified_mcp_proxy.py +14 -12
- _mcp_mesh/generated/.openapi-generator/FILES +4 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +81 -44
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +72 -35
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +132 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +172 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +92 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +121 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +98 -51
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +93 -44
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +84 -41
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +9 -72
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +6 -3
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +222 -0
- _mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +7 -0
- _mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +65 -4
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +2 -2
- _mcp_mesh/shared/registry_client_wrapper.py +60 -4
- _mcp_mesh/utils/fastmcp_schema_extractor.py +476 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/RECORD +39 -25
- mesh/__init__.py +8 -4
- mesh/decorators.py +344 -2
- mesh/types.py +145 -94
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,7 +14,7 @@ import weakref
|
|
|
14
14
|
from collections.abc import Callable
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
-
from .signature_analyzer import
|
|
17
|
+
from .signature_analyzer import get_mesh_agent_positions, has_llm_agent_parameter
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
@@ -57,17 +57,17 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
|
|
|
57
57
|
logger.warning(
|
|
58
58
|
f"Single parameter '{param_name}' in function '{func_name}' found, "
|
|
59
59
|
f"injecting {dependencies[0] if dependencies else 'dependency'} proxy "
|
|
60
|
-
f"(consider typing as McpMeshAgent
|
|
60
|
+
f"(consider typing as McpMeshAgent for clarity)"
|
|
61
61
|
)
|
|
62
62
|
return [0] # Inject into the single parameter
|
|
63
63
|
|
|
64
|
-
# Multiple parameters rule: only inject into McpMeshAgent
|
|
64
|
+
# Multiple parameters rule: only inject into McpMeshAgent typed parameters
|
|
65
65
|
if param_count > 1:
|
|
66
66
|
if not mesh_positions:
|
|
67
67
|
logger.warning(
|
|
68
68
|
f"⚠️ Function '{func_name}' has {param_count} parameters but none are "
|
|
69
|
-
f"typed as McpMeshAgent
|
|
70
|
-
f"Consider typing dependency parameters as McpMeshAgent
|
|
69
|
+
f"typed as McpMeshAgent. Skipping injection of {len(dependencies)} dependencies. "
|
|
70
|
+
f"Consider typing dependency parameters as McpMeshAgent."
|
|
71
71
|
)
|
|
72
72
|
return []
|
|
73
73
|
|
|
@@ -77,7 +77,7 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
|
|
|
77
77
|
excess_deps = dependencies[len(mesh_positions) :]
|
|
78
78
|
logger.warning(
|
|
79
79
|
f"Function '{func_name}' has {len(dependencies)} dependencies "
|
|
80
|
-
f"but only {len(mesh_positions)} McpMeshAgent
|
|
80
|
+
f"but only {len(mesh_positions)} McpMeshAgent parameters. "
|
|
81
81
|
f"Dependencies {excess_deps} will not be injected."
|
|
82
82
|
)
|
|
83
83
|
else:
|
|
@@ -85,7 +85,7 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
|
|
|
85
85
|
params[pos].name for pos in mesh_positions[len(dependencies) :]
|
|
86
86
|
]
|
|
87
87
|
logger.warning(
|
|
88
|
-
f"Function '{func_name}' has {len(mesh_positions)} McpMeshAgent
|
|
88
|
+
f"Function '{func_name}' has {len(mesh_positions)} McpMeshAgent parameters "
|
|
89
89
|
f"but only {len(dependencies)} dependencies declared. "
|
|
90
90
|
f"Parameters {excess_params} will remain None."
|
|
91
91
|
)
|
|
@@ -101,10 +101,11 @@ class DependencyInjector:
|
|
|
101
101
|
Manages dynamic dependency injection for mesh agents.
|
|
102
102
|
|
|
103
103
|
This class:
|
|
104
|
-
1. Maintains a registry of available dependencies
|
|
105
|
-
2.
|
|
106
|
-
3.
|
|
107
|
-
4.
|
|
104
|
+
1. Maintains a registry of available dependencies (McpMeshAgent)
|
|
105
|
+
2. Coordinates with MeshLlmAgentInjector for LLM agent injection
|
|
106
|
+
3. Tracks which functions depend on which services
|
|
107
|
+
4. Updates function bindings when topology changes
|
|
108
|
+
5. Handles graceful degradation when dependencies unavailable
|
|
108
109
|
"""
|
|
109
110
|
|
|
110
111
|
def __init__(self):
|
|
@@ -117,6 +118,12 @@ class DependencyInjector:
|
|
|
117
118
|
) # dep_name -> set of function_ids
|
|
118
119
|
self._lock = asyncio.Lock()
|
|
119
120
|
|
|
121
|
+
# LLM agent injector for MeshLlmAgent parameters
|
|
122
|
+
from .mesh_llm_agent_injector import get_global_llm_injector
|
|
123
|
+
|
|
124
|
+
self._llm_injector = get_global_llm_injector()
|
|
125
|
+
logger.debug("🤖 DependencyInjector initialized with MeshLlmAgentInjector")
|
|
126
|
+
|
|
120
127
|
async def register_dependency(self, name: str, instance: Any) -> None:
|
|
121
128
|
"""Register a new dependency or update existing one.
|
|
122
129
|
|
|
@@ -282,6 +289,49 @@ class DependencyInjector:
|
|
|
282
289
|
)
|
|
283
290
|
return None
|
|
284
291
|
|
|
292
|
+
def process_llm_tools(self, llm_tools: dict[str, list[dict[str, Any]]]) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Process llm_tools from registry response and delegate to MeshLlmAgentInjector.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
llm_tools: Dict mapping function_id -> list of tool metadata
|
|
298
|
+
Format: {"function_id": [{"function_name": "...", "endpoint": {...}, ...}]}
|
|
299
|
+
"""
|
|
300
|
+
logger.info(
|
|
301
|
+
f"🤖 DependencyInjector processing llm_tools for {len(llm_tools)} functions"
|
|
302
|
+
)
|
|
303
|
+
self._llm_injector.process_llm_tools(llm_tools)
|
|
304
|
+
|
|
305
|
+
def update_llm_tools(self, llm_tools: dict[str, list[dict[str, Any]]]) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Update llm_tools when topology changes (heartbeat updates).
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
llm_tools: Updated llm_tools dict from registry
|
|
311
|
+
"""
|
|
312
|
+
logger.info(
|
|
313
|
+
f"🔄 DependencyInjector updating llm_tools for {len(llm_tools)} functions"
|
|
314
|
+
)
|
|
315
|
+
self._llm_injector.update_llm_tools(llm_tools)
|
|
316
|
+
|
|
317
|
+
def create_llm_injection_wrapper(
|
|
318
|
+
self, func: Callable, function_id: str
|
|
319
|
+
) -> Callable:
|
|
320
|
+
"""
|
|
321
|
+
Create wrapper for function with MeshLlmAgent parameter.
|
|
322
|
+
|
|
323
|
+
Delegates to MeshLlmAgentInjector.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
func: Function to wrap
|
|
327
|
+
function_id: Unique function ID from @mesh.llm decorator
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Wrapped function with MeshLlmAgent injection
|
|
331
|
+
"""
|
|
332
|
+
logger.debug(f"🤖 Creating LLM injection wrapper for {function_id}")
|
|
333
|
+
return self._llm_injector.create_injection_wrapper(func, function_id)
|
|
334
|
+
|
|
285
335
|
def create_injection_wrapper(
|
|
286
336
|
self, func: Callable, dependencies: list[str]
|
|
287
337
|
) -> Callable:
|
|
@@ -300,9 +350,6 @@ class DependencyInjector:
|
|
|
300
350
|
# Use new smart injection strategy
|
|
301
351
|
mesh_positions = analyze_injection_strategy(func, dependencies)
|
|
302
352
|
|
|
303
|
-
# Get parameter type information for proxy selection
|
|
304
|
-
parameter_types = get_agent_parameter_types(func)
|
|
305
|
-
|
|
306
353
|
# Track which dependencies this function needs (using composite keys)
|
|
307
354
|
for dep_index, dep in enumerate(dependencies):
|
|
308
355
|
dep_key = f"{func_id}:dep_{dep_index}"
|
|
@@ -365,7 +412,6 @@ class DependencyInjector:
|
|
|
365
412
|
minimal_wrapper._mesh_injected_deps = [None] * len(dependencies)
|
|
366
413
|
minimal_wrapper._mesh_dependencies = dependencies
|
|
367
414
|
minimal_wrapper._mesh_positions = mesh_positions
|
|
368
|
-
minimal_wrapper._mesh_parameter_types = get_agent_parameter_types(func)
|
|
369
415
|
minimal_wrapper._mesh_original_func = func
|
|
370
416
|
|
|
371
417
|
def update_dependency(dep_index: int, instance: Any | None) -> None:
|
|
@@ -463,6 +509,21 @@ class DependencyInjector:
|
|
|
463
509
|
f"🔧 DEPENDENCY_WRAPPER: final_kwargs={final_kwargs}"
|
|
464
510
|
)
|
|
465
511
|
|
|
512
|
+
# ===== INJECT LLM AGENT IF PRESENT (Option A) =====
|
|
513
|
+
# Check if this function has @mesh.llm metadata attached (on the original function)
|
|
514
|
+
if hasattr(func, "_mesh_llm_param_name"):
|
|
515
|
+
llm_param = func._mesh_llm_param_name
|
|
516
|
+
# Only inject if not already provided
|
|
517
|
+
if (
|
|
518
|
+
llm_param not in final_kwargs
|
|
519
|
+
or final_kwargs.get(llm_param) is None
|
|
520
|
+
):
|
|
521
|
+
llm_agent = getattr(func, "_mesh_llm_agent", None)
|
|
522
|
+
final_kwargs[llm_param] = llm_agent
|
|
523
|
+
wrapper_logger.debug(
|
|
524
|
+
f"🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
525
|
+
)
|
|
526
|
+
|
|
466
527
|
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
467
528
|
# Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
|
|
468
529
|
from ..tracing.execution_tracer import ExecutionTracer
|
|
@@ -540,6 +601,21 @@ class DependencyInjector:
|
|
|
540
601
|
final_kwargs[param_name] = dependency
|
|
541
602
|
injected_count += 1
|
|
542
603
|
|
|
604
|
+
# ===== INJECT LLM AGENT IF PRESENT (Option A) =====
|
|
605
|
+
# Check if this function has @mesh.llm metadata attached (on the original function)
|
|
606
|
+
if hasattr(func, "_mesh_llm_param_name"):
|
|
607
|
+
llm_param = func._mesh_llm_param_name
|
|
608
|
+
# Only inject if not already provided
|
|
609
|
+
if (
|
|
610
|
+
llm_param not in final_kwargs
|
|
611
|
+
or final_kwargs.get(llm_param) is None
|
|
612
|
+
):
|
|
613
|
+
llm_agent = getattr(func, "_mesh_llm_agent", None)
|
|
614
|
+
final_kwargs[llm_param] = llm_agent
|
|
615
|
+
wrapper_logger.debug(
|
|
616
|
+
f"🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
617
|
+
)
|
|
618
|
+
|
|
543
619
|
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
544
620
|
# Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
|
|
545
621
|
from ..tracing.execution_tracer import ExecutionTracer
|
|
@@ -587,9 +663,6 @@ class DependencyInjector:
|
|
|
587
663
|
dependency_wrapper._mesh_update_dependency = update_dependency
|
|
588
664
|
dependency_wrapper._mesh_dependencies = dependencies
|
|
589
665
|
dependency_wrapper._mesh_positions = mesh_positions
|
|
590
|
-
dependency_wrapper._mesh_parameter_types = (
|
|
591
|
-
parameter_types # Store for proxy selection
|
|
592
|
-
)
|
|
593
666
|
dependency_wrapper._mesh_original_func = func
|
|
594
667
|
|
|
595
668
|
# Register this wrapper for dependency updates
|
_mcp_mesh/engine/http_wrapper.py
CHANGED
|
@@ -170,32 +170,12 @@ class HttpMcpWrapper:
|
|
|
170
170
|
async def setup(self):
|
|
171
171
|
"""Set up FastMCP app for integration (no separate wrapper app)."""
|
|
172
172
|
|
|
173
|
-
# Debug the FastMCP server instance first
|
|
174
|
-
logger.debug(f"🔍 DEBUG: FastMCP server type: {type(self.mcp_server)}")
|
|
175
|
-
logger.debug(
|
|
176
|
-
f"🔍 DEBUG: FastMCP server module: {type(self.mcp_server).__module__}"
|
|
177
|
-
)
|
|
178
|
-
|
|
179
173
|
# Using FastMCP library (fastmcp>=2.8.0)
|
|
180
174
|
logger.info(
|
|
181
175
|
"🆕 HTTP Wrapper: Server instance is from FastMCP library (fastmcp)"
|
|
182
176
|
)
|
|
183
177
|
|
|
184
|
-
logger.debug(
|
|
185
|
-
f"🔍 DEBUG: FastMCP server dir: {[attr for attr in dir(self.mcp_server) if 'app' in attr.lower()]}"
|
|
186
|
-
)
|
|
187
|
-
logger.debug(f"🔍 DEBUG: Has http_app: {hasattr(self.mcp_server, 'http_app')}")
|
|
188
|
-
|
|
189
178
|
if self._mcp_app is not None:
|
|
190
|
-
logger.debug("🔍 DEBUG: FastMCP app prepared for integration")
|
|
191
|
-
logger.debug(f"🔍 DEBUG: FastMCP app type: {type(self._mcp_app)}")
|
|
192
|
-
|
|
193
|
-
# Debug: Check what routes the FastMCP app has
|
|
194
|
-
if hasattr(self._mcp_app, "routes"):
|
|
195
|
-
logger.debug(
|
|
196
|
-
f"🔍 DEBUG: FastMCP app routes: {[route.path for route in self._mcp_app.routes if hasattr(route, 'path')]}"
|
|
197
|
-
)
|
|
198
|
-
|
|
199
179
|
# Phase 5: Add session routing middleware to FastMCP app
|
|
200
180
|
self._add_session_routing_middleware()
|
|
201
181
|
|
|
@@ -396,10 +376,11 @@ class HttpMcpWrapper:
|
|
|
396
376
|
|
|
397
377
|
class MCPSessionRoutingMiddleware(BaseHTTPMiddleware):
|
|
398
378
|
"""Clean session routing middleware for MCP requests (v0.4.0 style).
|
|
399
|
-
|
|
379
|
+
|
|
400
380
|
Handles session affinity and basic trace context setup only.
|
|
401
381
|
Function execution tracing is handled by ExecutionTracer in DependencyInjector.
|
|
402
382
|
"""
|
|
383
|
+
|
|
403
384
|
def __init__(self, app, http_wrapper):
|
|
404
385
|
super().__init__(app)
|
|
405
386
|
self.http_wrapper = http_wrapper
|
|
@@ -455,7 +436,9 @@ class HttpMcpWrapper:
|
|
|
455
436
|
|
|
456
437
|
# Add the middleware to FastMCP app
|
|
457
438
|
self._mcp_app.add_middleware(MCPSessionRoutingMiddleware, http_wrapper=self)
|
|
458
|
-
logger.info(
|
|
439
|
+
logger.info(
|
|
440
|
+
"✅ Clean session routing middleware added to FastMCP app (v0.4.0 style)"
|
|
441
|
+
)
|
|
459
442
|
|
|
460
443
|
async def _extract_session_id(self, request) -> str:
|
|
461
444
|
"""Extract session ID from request headers or body."""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM configuration dataclass.
|
|
3
|
+
|
|
4
|
+
Consolidates LLM-related configuration into a single type-safe structure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class LLMConfig:
|
|
13
|
+
"""
|
|
14
|
+
Configuration for MeshLlmAgent.
|
|
15
|
+
|
|
16
|
+
Consolidates provider, model, and runtime settings into a single type-safe structure.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
provider: str = "claude"
|
|
20
|
+
"""LLM provider (e.g., 'claude', 'openai', 'gemini')"""
|
|
21
|
+
|
|
22
|
+
model: str = "claude-3-5-sonnet-20241022"
|
|
23
|
+
"""Model name for the provider"""
|
|
24
|
+
|
|
25
|
+
api_key: str = ""
|
|
26
|
+
"""API key for the provider (uses environment variable if empty)"""
|
|
27
|
+
|
|
28
|
+
max_iterations: int = 10
|
|
29
|
+
"""Maximum iterations for the agentic loop"""
|
|
30
|
+
|
|
31
|
+
system_prompt: Optional[str] = None
|
|
32
|
+
"""Optional system prompt to prepend to all interactions"""
|
|
33
|
+
|
|
34
|
+
def __post_init__(self):
|
|
35
|
+
"""Validate configuration after initialization."""
|
|
36
|
+
if self.max_iterations < 1:
|
|
37
|
+
raise ValueError("max_iterations must be >= 1")
|
|
38
|
+
if not self.provider:
|
|
39
|
+
raise ValueError("provider cannot be empty")
|
|
40
|
+
if not self.model:
|
|
41
|
+
raise ValueError("model cannot be empty")
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced error classes for LLM engine with structured context.
|
|
3
|
+
|
|
4
|
+
Provides rich debugging information for better troubleshooting and telemetry.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MaxIterationsError(Exception):
|
|
11
|
+
"""
|
|
12
|
+
Raised when max iterations are exceeded in agentic loop.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
iteration_count: Number of iterations that were attempted
|
|
16
|
+
max_allowed: Maximum iterations allowed
|
|
17
|
+
function_id: Optional function ID for debugging
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
iteration_count: int,
|
|
23
|
+
max_allowed: int,
|
|
24
|
+
function_id: Optional[str] = None,
|
|
25
|
+
):
|
|
26
|
+
self.iteration_count = iteration_count
|
|
27
|
+
self.max_allowed = max_allowed
|
|
28
|
+
self.function_id = function_id
|
|
29
|
+
|
|
30
|
+
message = (
|
|
31
|
+
f"Exceeded maximum {max_allowed} iterations without reaching final response"
|
|
32
|
+
)
|
|
33
|
+
if function_id:
|
|
34
|
+
message += f" (function_id={function_id})"
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LLMAPIError(Exception):
|
|
39
|
+
"""
|
|
40
|
+
Raised when LLM API call fails.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
provider: LLM provider name (e.g., 'claude', 'openai')
|
|
44
|
+
model: Model name
|
|
45
|
+
original_error: The underlying exception
|
|
46
|
+
status_code: HTTP status code if available
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
provider: str,
|
|
52
|
+
model: str,
|
|
53
|
+
original_error: Exception,
|
|
54
|
+
status_code: Optional[int] = None,
|
|
55
|
+
):
|
|
56
|
+
self.provider = provider
|
|
57
|
+
self.model = model
|
|
58
|
+
self.original_error = original_error
|
|
59
|
+
self.status_code = status_code
|
|
60
|
+
|
|
61
|
+
message = f"LLM API call failed: {original_error}"
|
|
62
|
+
if status_code:
|
|
63
|
+
message = f"LLM API call failed (HTTP {status_code}): {original_error}"
|
|
64
|
+
message += f" [provider={provider}, model={model}]"
|
|
65
|
+
super().__init__(message)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ToolExecutionError(Exception):
|
|
69
|
+
"""
|
|
70
|
+
Raised when a tool execution fails.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
tool_name: Name of the tool that failed
|
|
74
|
+
arguments: Arguments passed to the tool
|
|
75
|
+
original_error: The underlying exception
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
tool_name: str,
|
|
81
|
+
arguments: dict[str, Any],
|
|
82
|
+
original_error: Exception,
|
|
83
|
+
):
|
|
84
|
+
self.tool_name = tool_name
|
|
85
|
+
self.arguments = arguments
|
|
86
|
+
self.original_error = original_error
|
|
87
|
+
|
|
88
|
+
message = f"Tool '{tool_name}' execution failed: {original_error}"
|
|
89
|
+
super().__init__(message)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ResponseParseError(Exception):
|
|
93
|
+
"""
|
|
94
|
+
Raised when response parsing or validation fails.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
raw_content: Raw response content (truncated to 500 chars)
|
|
98
|
+
expected_schema: Expected Pydantic schema name
|
|
99
|
+
validation_errors: Pydantic validation errors if available
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
raw_content: str,
|
|
105
|
+
expected_schema: str,
|
|
106
|
+
validation_errors: Optional[str] = None,
|
|
107
|
+
):
|
|
108
|
+
self.raw_content = raw_content[:500] # Truncate for logging
|
|
109
|
+
self.expected_schema = expected_schema
|
|
110
|
+
self.validation_errors = validation_errors
|
|
111
|
+
|
|
112
|
+
message = f"Response validation failed for schema '{expected_schema}'"
|
|
113
|
+
if validation_errors:
|
|
114
|
+
message += f": {validation_errors}"
|
|
115
|
+
super().__init__(message)
|