mcp-mesh 0.7.12__py3-none-any.whl → 0.7.13__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/__init__.py +1 -22
- _mcp_mesh/engine/async_mcp_client.py +88 -25
- _mcp_mesh/engine/decorator_registry.py +10 -9
- _mcp_mesh/engine/dependency_injector.py +64 -53
- _mcp_mesh/engine/mesh_llm_agent.py +119 -5
- _mcp_mesh/engine/mesh_llm_agent_injector.py +30 -0
- _mcp_mesh/engine/session_aware_client.py +3 -3
- _mcp_mesh/engine/unified_mcp_proxy.py +82 -90
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -89
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +3 -3
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +30 -28
- _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +16 -18
- _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +5 -5
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +3 -3
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +6 -6
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +1 -1
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +15 -11
- _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +3 -3
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +37 -268
- _mcp_mesh/pipeline/mcp_startup/lifespan_factory.py +142 -0
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +57 -93
- _mcp_mesh/pipeline/shared/registry_connection.py +1 -1
- _mcp_mesh/shared/health_check_manager.py +313 -0
- _mcp_mesh/shared/logging_config.py +190 -7
- _mcp_mesh/shared/registry_client_wrapper.py +8 -8
- _mcp_mesh/shared/sse_parser.py +19 -17
- _mcp_mesh/tracing/execution_tracer.py +26 -1
- _mcp_mesh/tracing/fastapi_tracing_middleware.py +3 -4
- _mcp_mesh/tracing/trace_context_helper.py +25 -6
- {mcp_mesh-0.7.12.dist-info → mcp_mesh-0.7.13.dist-info}/METADATA +1 -1
- {mcp_mesh-0.7.12.dist-info → mcp_mesh-0.7.13.dist-info}/RECORD +38 -39
- mesh/__init__.py +3 -1
- mesh/decorators.py +81 -43
- mesh/helpers.py +72 -4
- mesh/types.py +48 -4
- _mcp_mesh/engine/full_mcp_proxy.py +0 -641
- _mcp_mesh/engine/mcp_client_proxy.py +0 -457
- _mcp_mesh/shared/health_check_cache.py +0 -246
- {mcp_mesh-0.7.12.dist-info → mcp_mesh-0.7.13.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.7.12.dist-info → mcp_mesh-0.7.13.dist-info}/licenses/LICENSE +0 -0
_mcp_mesh/__init__.py
CHANGED
_mcp_mesh/engine/__init__.py
CHANGED
|
@@ -17,13 +17,8 @@ __all__ = [
|
|
|
17
17
|
# Dependency injection
|
|
18
18
|
"DependencyInjector",
|
|
19
19
|
"get_global_injector",
|
|
20
|
-
# MCP client proxies
|
|
21
|
-
"MCPClientProxy",
|
|
22
|
-
"EnhancedMCPClientProxy",
|
|
23
|
-
"FullMCPProxy",
|
|
24
|
-
"EnhancedFullMCPProxy",
|
|
20
|
+
# MCP client proxies
|
|
25
21
|
"AsyncMCPClient",
|
|
26
|
-
# Unified MCP proxy (recommended)
|
|
27
22
|
"UnifiedMCPProxy",
|
|
28
23
|
"EnhancedUnifiedMCPProxy",
|
|
29
24
|
# Self-dependency proxy
|
|
@@ -59,22 +54,6 @@ def __getattr__(name):
|
|
|
59
54
|
|
|
60
55
|
return get_global_injector
|
|
61
56
|
# MCP client proxies
|
|
62
|
-
elif name == "MCPClientProxy":
|
|
63
|
-
from .mcp_client_proxy import MCPClientProxy
|
|
64
|
-
|
|
65
|
-
return MCPClientProxy
|
|
66
|
-
elif name == "EnhancedMCPClientProxy":
|
|
67
|
-
from .mcp_client_proxy import EnhancedMCPClientProxy
|
|
68
|
-
|
|
69
|
-
return EnhancedMCPClientProxy
|
|
70
|
-
elif name == "FullMCPProxy":
|
|
71
|
-
from .full_mcp_proxy import FullMCPProxy
|
|
72
|
-
|
|
73
|
-
return FullMCPProxy
|
|
74
|
-
elif name == "EnhancedFullMCPProxy":
|
|
75
|
-
from .full_mcp_proxy import EnhancedFullMCPProxy
|
|
76
|
-
|
|
77
|
-
return EnhancedFullMCPProxy
|
|
78
57
|
elif name == "AsyncMCPClient":
|
|
79
58
|
from .async_mcp_client import AsyncMCPClient
|
|
80
59
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Async HTTP client for MCP JSON-RPC protocol."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
import atexit
|
|
3
5
|
import json
|
|
4
6
|
import logging
|
|
5
7
|
import urllib.error
|
|
6
8
|
import urllib.request
|
|
7
|
-
from typing import Any
|
|
9
|
+
from typing import Any, ClassVar, Optional
|
|
8
10
|
|
|
9
11
|
from ..shared.sse_parser import SSEParser
|
|
10
12
|
|
|
@@ -12,13 +14,67 @@ logger = logging.getLogger(__name__)
|
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class AsyncMCPClient:
|
|
15
|
-
"""Async HTTP client for MCP JSON-RPC protocol.
|
|
17
|
+
"""Async HTTP client for MCP JSON-RPC protocol.
|
|
18
|
+
|
|
19
|
+
Uses connection pooling to reuse HTTP connections across requests,
|
|
20
|
+
reducing TCP/SSL handshake overhead and preventing port exhaustion.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Class-level connection pool (shared across instances for same endpoint)
|
|
24
|
+
_client_pool: ClassVar[dict[str, "httpx.AsyncClient"]] = {}
|
|
25
|
+
_pool_lock: ClassVar[Optional[asyncio.Lock]] = None
|
|
26
|
+
|
|
27
|
+
# Default connection limits for pooling
|
|
28
|
+
DEFAULT_MAX_CONNECTIONS = 100
|
|
29
|
+
DEFAULT_MAX_KEEPALIVE_CONNECTIONS = 20
|
|
16
30
|
|
|
17
31
|
def __init__(self, endpoint: str, timeout: float = 30.0):
|
|
18
32
|
self.endpoint = endpoint
|
|
19
33
|
self.timeout = timeout
|
|
20
34
|
self.logger = logger.getChild(f"client.{endpoint}")
|
|
21
35
|
|
|
36
|
+
@classmethod
|
|
37
|
+
def _get_lock(cls) -> asyncio.Lock:
|
|
38
|
+
"""Get or create the class-level lock (handles event loop creation)."""
|
|
39
|
+
if cls._pool_lock is None:
|
|
40
|
+
cls._pool_lock = asyncio.Lock()
|
|
41
|
+
return cls._pool_lock
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
async def _get_pooled_client(
|
|
45
|
+
cls, endpoint: str, timeout: float
|
|
46
|
+
) -> "httpx.AsyncClient":
|
|
47
|
+
"""Get or create a pooled httpx client for the endpoint.
|
|
48
|
+
|
|
49
|
+
This enables connection reuse across multiple requests to the same endpoint,
|
|
50
|
+
significantly reducing overhead from TCP connection establishment and SSL handshakes.
|
|
51
|
+
"""
|
|
52
|
+
import httpx
|
|
53
|
+
|
|
54
|
+
async with cls._get_lock():
|
|
55
|
+
if endpoint not in cls._client_pool:
|
|
56
|
+
cls._client_pool[endpoint] = httpx.AsyncClient(
|
|
57
|
+
timeout=timeout,
|
|
58
|
+
limits=httpx.Limits(
|
|
59
|
+
max_connections=cls.DEFAULT_MAX_CONNECTIONS,
|
|
60
|
+
max_keepalive_connections=cls.DEFAULT_MAX_KEEPALIVE_CONNECTIONS,
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
logger.debug(f"Created pooled client for endpoint: {endpoint}")
|
|
64
|
+
return cls._client_pool[endpoint]
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
async def close_all_clients(cls) -> None:
|
|
68
|
+
"""Close all pooled clients. Call during application shutdown."""
|
|
69
|
+
async with cls._get_lock():
|
|
70
|
+
for endpoint, client in list(cls._client_pool.items()):
|
|
71
|
+
try:
|
|
72
|
+
await client.aclose()
|
|
73
|
+
logger.debug(f"Closed pooled client for: {endpoint}")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.warning(f"Error closing client for {endpoint}: {e}")
|
|
76
|
+
cls._client_pool.clear()
|
|
77
|
+
|
|
22
78
|
async def call_tool(self, tool_name: str, arguments: dict) -> Any:
|
|
23
79
|
"""Call remote tool using MCP JSON-RPC protocol."""
|
|
24
80
|
payload = {
|
|
@@ -38,37 +94,39 @@ class AsyncMCPClient:
|
|
|
38
94
|
raise
|
|
39
95
|
|
|
40
96
|
async def _make_request(self, payload: dict) -> dict:
|
|
41
|
-
"""Make async HTTP request to MCP endpoint."""
|
|
97
|
+
"""Make async HTTP request to MCP endpoint using pooled connections."""
|
|
42
98
|
url = f"{self.endpoint}/mcp"
|
|
43
99
|
|
|
44
100
|
try:
|
|
45
|
-
# Use httpx
|
|
101
|
+
# Use httpx with connection pooling for better resource management
|
|
46
102
|
import httpx
|
|
47
103
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
url,
|
|
51
|
-
json=payload,
|
|
52
|
-
headers={
|
|
53
|
-
"Content-Type": "application/json",
|
|
54
|
-
"Accept": "application/json, text/event-stream",
|
|
55
|
-
},
|
|
56
|
-
)
|
|
104
|
+
# Get pooled client (reuses connections across requests)
|
|
105
|
+
client = await self._get_pooled_client(self.endpoint, self.timeout)
|
|
57
106
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
107
|
+
response = await client.post(
|
|
108
|
+
url,
|
|
109
|
+
json=payload,
|
|
110
|
+
headers={
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
"Accept": "application/json, text/event-stream",
|
|
113
|
+
},
|
|
114
|
+
)
|
|
64
115
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
116
|
+
if response.status_code == 404:
|
|
117
|
+
raise RuntimeError(f"MCP endpoint not found at {url}")
|
|
118
|
+
elif response.status_code >= 400:
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"HTTP error {response.status_code}: {response.reason_phrase}"
|
|
70
121
|
)
|
|
71
122
|
|
|
123
|
+
response_text = response.text
|
|
124
|
+
|
|
125
|
+
# Use shared SSE parser
|
|
126
|
+
data = SSEParser.parse_sse_response(
|
|
127
|
+
response_text, f"AsyncMCPClient.{self.endpoint}"
|
|
128
|
+
)
|
|
129
|
+
|
|
72
130
|
# Check for JSON-RPC error
|
|
73
131
|
if "error" in data:
|
|
74
132
|
error = data["error"]
|
|
@@ -169,5 +227,10 @@ class AsyncMCPClient:
|
|
|
169
227
|
return result
|
|
170
228
|
|
|
171
229
|
async def close(self):
|
|
172
|
-
"""Close client (no
|
|
230
|
+
"""Close client instance (no-op as connections are pooled at class level).
|
|
231
|
+
|
|
232
|
+
Connection pooling is managed at the class level for efficiency.
|
|
233
|
+
To close all pooled connections during shutdown, use:
|
|
234
|
+
await AsyncMCPClient.close_all_clients()
|
|
235
|
+
"""
|
|
173
236
|
pass
|
|
@@ -664,27 +664,28 @@ class DecoratorRegistry:
|
|
|
664
664
|
cls._immediate_uvicorn_server = None
|
|
665
665
|
logger.debug("🔄 REGISTRY: Cleared immediate uvicorn server reference")
|
|
666
666
|
|
|
667
|
-
# Health check result storage
|
|
668
|
-
_health_check_result: dict | None = None
|
|
667
|
+
# Health check result storage (delegated to health_check_manager)
|
|
669
668
|
|
|
670
669
|
@classmethod
|
|
671
670
|
def store_health_check_result(cls, result: dict) -> None:
|
|
672
671
|
"""Store health check result for /health endpoint."""
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
)
|
|
672
|
+
from ..shared.health_check_manager import store_health_check_result
|
|
673
|
+
|
|
674
|
+
store_health_check_result(result)
|
|
677
675
|
|
|
678
676
|
@classmethod
|
|
679
677
|
def get_health_check_result(cls) -> dict | None:
|
|
680
678
|
"""Get stored health check result."""
|
|
681
|
-
|
|
679
|
+
from ..shared.health_check_manager import get_health_check_result
|
|
680
|
+
|
|
681
|
+
return get_health_check_result()
|
|
682
682
|
|
|
683
683
|
@classmethod
|
|
684
684
|
def clear_health_check_result(cls) -> None:
|
|
685
685
|
"""Clear stored health check result."""
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
from ..shared.health_check_manager import clear_health_check_result
|
|
687
|
+
|
|
688
|
+
clear_health_check_result()
|
|
688
689
|
|
|
689
690
|
@classmethod
|
|
690
691
|
def store_fastmcp_lifespan(cls, lifespan: Any) -> None:
|
|
@@ -14,6 +14,11 @@ import weakref
|
|
|
14
14
|
from collections.abc import Callable
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
+
from ..shared.logging_config import (
|
|
18
|
+
format_log_value,
|
|
19
|
+
format_result_summary,
|
|
20
|
+
get_trace_prefix,
|
|
21
|
+
)
|
|
17
22
|
from .signature_analyzer import get_mesh_agent_positions, has_llm_agent_parameter
|
|
18
23
|
|
|
19
24
|
logger = logging.getLogger(__name__)
|
|
@@ -448,17 +453,17 @@ class DependencyInjector:
|
|
|
448
453
|
|
|
449
454
|
@functools.wraps(func)
|
|
450
455
|
async def dependency_wrapper(*args, **kwargs):
|
|
456
|
+
# Get trace prefix if available
|
|
457
|
+
tp = get_trace_prefix()
|
|
458
|
+
|
|
459
|
+
# Log tool invocation - summary line
|
|
460
|
+
arg_keys = list(kwargs.keys()) if kwargs else []
|
|
451
461
|
wrapper_logger.debug(
|
|
452
|
-
f"🔧
|
|
453
|
-
)
|
|
454
|
-
wrapper_logger.debug(
|
|
455
|
-
f"🔧 DEPENDENCY_WRAPPER: args={args}, kwargs={kwargs}"
|
|
456
|
-
)
|
|
457
|
-
wrapper_logger.debug(
|
|
458
|
-
f"🔧 DEPENDENCY_WRAPPER: mesh_positions={mesh_positions}"
|
|
462
|
+
f"{tp}🔧 Tool '{func.__name__}' called with kwargs={arg_keys}"
|
|
459
463
|
)
|
|
464
|
+
# Log full args (will be TRACE later)
|
|
460
465
|
wrapper_logger.debug(
|
|
461
|
-
f"🔧
|
|
466
|
+
f"{tp}🔧 Tool '{func.__name__}' args: {format_log_value(kwargs)}"
|
|
462
467
|
)
|
|
463
468
|
|
|
464
469
|
# We know mesh_positions is not empty since we checked above
|
|
@@ -468,20 +473,14 @@ class DependencyInjector:
|
|
|
468
473
|
params = list(sig.parameters.keys())
|
|
469
474
|
final_kwargs = kwargs.copy()
|
|
470
475
|
|
|
471
|
-
wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: params={params}")
|
|
472
|
-
wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: original kwargs={kwargs}")
|
|
473
|
-
|
|
474
476
|
# Inject dependencies as kwargs (using array-based lookup)
|
|
475
477
|
injected_count = 0
|
|
478
|
+
injected_deps = [] # Track what was injected for logging
|
|
476
479
|
for dep_index, param_position in enumerate(mesh_positions):
|
|
477
480
|
if dep_index < len(dependencies):
|
|
478
481
|
dep_name = dependencies[dep_index]
|
|
479
482
|
param_name = params[param_position]
|
|
480
483
|
|
|
481
|
-
wrapper_logger.debug(
|
|
482
|
-
f"🔧 DEPENDENCY_WRAPPER: Processing dep {dep_index}: {dep_name} -> {param_name}"
|
|
483
|
-
)
|
|
484
|
-
|
|
485
484
|
# Only inject if the parameter wasn't explicitly provided
|
|
486
485
|
if (
|
|
487
486
|
param_name not in final_kwargs
|
|
@@ -493,34 +492,25 @@ class DependencyInjector:
|
|
|
493
492
|
dependency = dependency_wrapper._mesh_injected_deps[
|
|
494
493
|
dep_index
|
|
495
494
|
]
|
|
496
|
-
wrapper_logger.debug(
|
|
497
|
-
f"🔧 DEPENDENCY_WRAPPER: From wrapper storage[{dep_index}]: {dependency}"
|
|
498
|
-
)
|
|
499
495
|
|
|
500
496
|
if dependency is None:
|
|
501
497
|
# Fallback to global storage with composite key
|
|
502
498
|
dep_key = f"{func.__module__}.{func.__qualname__}:dep_{dep_index}"
|
|
503
499
|
dependency = self.get_dependency(dep_key)
|
|
504
|
-
wrapper_logger.debug(
|
|
505
|
-
f"🔧 DEPENDENCY_WRAPPER: From global storage[{dep_key}]: {dependency}"
|
|
506
|
-
)
|
|
507
500
|
|
|
508
501
|
final_kwargs[param_name] = dependency
|
|
509
502
|
injected_count += 1
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
else:
|
|
514
|
-
wrapper_logger.debug(
|
|
515
|
-
f"🔧 DEPENDENCY_WRAPPER: Skipping {param_name} - already provided"
|
|
503
|
+
# Track for consolidated logging
|
|
504
|
+
proxy_type = (
|
|
505
|
+
type(dependency).__name__ if dependency else "None"
|
|
516
506
|
)
|
|
507
|
+
injected_deps.append(f"{dep_name} → {proxy_type}")
|
|
517
508
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
)
|
|
509
|
+
# Log consolidated dependency injection summary
|
|
510
|
+
if injected_count > 0:
|
|
511
|
+
wrapper_logger.debug(
|
|
512
|
+
f"{tp}🔧 Injected {injected_count} dependencies: {', '.join(injected_deps)}"
|
|
513
|
+
)
|
|
524
514
|
|
|
525
515
|
# ===== INJECT LLM AGENT IF PRESENT (Option A) =====
|
|
526
516
|
# Check if this function has @mesh.llm metadata attached (on the original function)
|
|
@@ -534,7 +524,7 @@ class DependencyInjector:
|
|
|
534
524
|
llm_agent = getattr(func, "_mesh_llm_agent", None)
|
|
535
525
|
final_kwargs[llm_param] = llm_agent
|
|
536
526
|
wrapper_logger.debug(
|
|
537
|
-
f"🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
527
|
+
f"{tp}🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
538
528
|
)
|
|
539
529
|
|
|
540
530
|
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
@@ -543,10 +533,6 @@ class DependencyInjector:
|
|
|
543
533
|
|
|
544
534
|
original_func = func._mesh_original_func
|
|
545
535
|
|
|
546
|
-
wrapper_logger.debug(
|
|
547
|
-
f"🔧 DI: Executing async function {original_func.__name__} with {injected_count} injected dependencies"
|
|
548
|
-
)
|
|
549
|
-
|
|
550
536
|
# Use ExecutionTracer's async method for clean tracing
|
|
551
537
|
result = await ExecutionTracer.trace_function_execution_async(
|
|
552
538
|
original_func,
|
|
@@ -558,26 +544,32 @@ class DependencyInjector:
|
|
|
558
544
|
wrapper_logger,
|
|
559
545
|
)
|
|
560
546
|
|
|
547
|
+
# Log result - summary line
|
|
561
548
|
wrapper_logger.debug(
|
|
562
|
-
f"🔧
|
|
549
|
+
f"{tp}🔧 Tool '{func.__name__}' returned: {format_result_summary(result)}"
|
|
563
550
|
)
|
|
551
|
+
# Log full result (will be TRACE later)
|
|
552
|
+
wrapper_logger.debug(
|
|
553
|
+
f"{tp}🔧 Tool '{func.__name__}' result: {format_log_value(result)}"
|
|
554
|
+
)
|
|
555
|
+
|
|
564
556
|
return result
|
|
565
557
|
|
|
566
558
|
else:
|
|
567
559
|
# Create sync wrapper for sync functions without dependencies
|
|
568
560
|
@functools.wraps(func)
|
|
569
561
|
def dependency_wrapper(*args, **kwargs):
|
|
562
|
+
# Get trace prefix if available
|
|
563
|
+
tp = get_trace_prefix()
|
|
564
|
+
|
|
565
|
+
# Log tool invocation - summary line
|
|
566
|
+
arg_keys = list(kwargs.keys()) if kwargs else []
|
|
570
567
|
wrapper_logger.debug(
|
|
571
|
-
f"🔧
|
|
572
|
-
)
|
|
573
|
-
wrapper_logger.debug(
|
|
574
|
-
f"🔧 DEPENDENCY_WRAPPER: args={args}, kwargs={kwargs}"
|
|
575
|
-
)
|
|
576
|
-
wrapper_logger.debug(
|
|
577
|
-
f"🔧 DEPENDENCY_WRAPPER: mesh_positions={mesh_positions}"
|
|
568
|
+
f"{tp}🔧 Tool '{func.__name__}' called with kwargs={arg_keys}"
|
|
578
569
|
)
|
|
570
|
+
# Log full args (will be TRACE later)
|
|
579
571
|
wrapper_logger.debug(
|
|
580
|
-
f"🔧
|
|
572
|
+
f"{tp}🔧 Tool '{func.__name__}' args: {format_log_value(kwargs)}"
|
|
581
573
|
)
|
|
582
574
|
|
|
583
575
|
# We know mesh_positions is not empty since we checked above
|
|
@@ -589,6 +581,7 @@ class DependencyInjector:
|
|
|
589
581
|
|
|
590
582
|
# Inject dependencies as kwargs (using array-based lookup)
|
|
591
583
|
injected_count = 0
|
|
584
|
+
injected_deps = [] # Track what was injected for logging
|
|
592
585
|
for dep_index, param_position in enumerate(mesh_positions):
|
|
593
586
|
if dep_index < len(dependencies):
|
|
594
587
|
dep_name = dependencies[dep_index]
|
|
@@ -613,6 +606,17 @@ class DependencyInjector:
|
|
|
613
606
|
|
|
614
607
|
final_kwargs[param_name] = dependency
|
|
615
608
|
injected_count += 1
|
|
609
|
+
# Track for consolidated logging
|
|
610
|
+
proxy_type = (
|
|
611
|
+
type(dependency).__name__ if dependency else "None"
|
|
612
|
+
)
|
|
613
|
+
injected_deps.append(f"{dep_name} → {proxy_type}")
|
|
614
|
+
|
|
615
|
+
# Log consolidated dependency injection summary
|
|
616
|
+
if injected_count > 0:
|
|
617
|
+
wrapper_logger.debug(
|
|
618
|
+
f"{tp}🔧 Injected {injected_count} dependencies: {', '.join(injected_deps)}"
|
|
619
|
+
)
|
|
616
620
|
|
|
617
621
|
# ===== INJECT LLM AGENT IF PRESENT (Option A) =====
|
|
618
622
|
# Check if this function has @mesh.llm metadata attached (on the original function)
|
|
@@ -626,19 +630,15 @@ class DependencyInjector:
|
|
|
626
630
|
llm_agent = getattr(func, "_mesh_llm_agent", None)
|
|
627
631
|
final_kwargs[llm_param] = llm_agent
|
|
628
632
|
wrapper_logger.debug(
|
|
629
|
-
f"🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
633
|
+
f"{tp}🤖 LLM_INJECTION: Injected {llm_param}={llm_agent}"
|
|
630
634
|
)
|
|
631
635
|
|
|
632
636
|
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
633
637
|
# Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
|
|
634
638
|
from ..tracing.execution_tracer import ExecutionTracer
|
|
635
639
|
|
|
636
|
-
wrapper_logger.debug(
|
|
637
|
-
f"🔧 DI: Executing sync function {func._mesh_original_func.__name__} with {injected_count} injected dependencies"
|
|
638
|
-
)
|
|
639
|
-
|
|
640
640
|
# Use ExecutionTracer for clean execution tracing
|
|
641
|
-
|
|
641
|
+
result = ExecutionTracer.trace_function_execution(
|
|
642
642
|
func._mesh_original_func,
|
|
643
643
|
args,
|
|
644
644
|
final_kwargs,
|
|
@@ -648,6 +648,17 @@ class DependencyInjector:
|
|
|
648
648
|
wrapper_logger,
|
|
649
649
|
)
|
|
650
650
|
|
|
651
|
+
# Log result - summary line
|
|
652
|
+
wrapper_logger.debug(
|
|
653
|
+
f"{tp}🔧 Tool '{func.__name__}' returned: {format_result_summary(result)}"
|
|
654
|
+
)
|
|
655
|
+
# Log full result (will be TRACE later)
|
|
656
|
+
wrapper_logger.debug(
|
|
657
|
+
f"{tp}🔧 Tool '{func.__name__}' result: {format_log_value(result)}"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
return result
|
|
661
|
+
|
|
651
662
|
# Store dependency state on wrapper as array (indexed by position)
|
|
652
663
|
dependency_wrapper._mesh_injected_deps = [None] * len(dependencies)
|
|
653
664
|
|