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
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MeshLlmAgent dependency injection system.
|
|
3
|
+
|
|
4
|
+
Handles injection of MeshLlmAgent proxies into function parameters.
|
|
5
|
+
Similar to DependencyInjector but specialized for LLM agent injection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
import logging
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .base_injector import BaseInjector
|
|
14
|
+
from .decorator_registry import DecoratorRegistry
|
|
15
|
+
from .llm_config import LLMConfig
|
|
16
|
+
from .mesh_llm_agent import MeshLlmAgent
|
|
17
|
+
from .unified_mcp_proxy import UnifiedMCPProxy
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MeshLlmAgentInjector(BaseInjector):
|
|
23
|
+
"""
|
|
24
|
+
Manages dynamic injection of MeshLlmAgent proxies.
|
|
25
|
+
|
|
26
|
+
This class:
|
|
27
|
+
1. Consumes llm_tools from registry response
|
|
28
|
+
2. Creates UnifiedMCPProxy instances for each tool
|
|
29
|
+
3. Creates MeshLlmAgent instances with config + proxies + output_type
|
|
30
|
+
4. Injects MeshLlmAgent into function parameters
|
|
31
|
+
5. Handles topology updates when tools join/leave
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
"""Initialize the LLM agent injector."""
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._llm_agents: dict[str, dict[str, Any]] = {}
|
|
38
|
+
|
|
39
|
+
def _build_function_name_to_id_mapping(self) -> dict[str, str]:
|
|
40
|
+
"""
|
|
41
|
+
Build mapping from function_name to function_id.
|
|
42
|
+
|
|
43
|
+
Registry returns llm_tools keyed by function_name (e.g., "chat"),
|
|
44
|
+
but DecoratorRegistry stores LLM agents keyed by function_id (e.g., "chat_ac4ed56b").
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dict mapping function_name -> function_id
|
|
48
|
+
"""
|
|
49
|
+
llm_agents = DecoratorRegistry.get_mesh_llm_agents()
|
|
50
|
+
return {
|
|
51
|
+
metadata.function.__name__: function_id
|
|
52
|
+
for function_id, metadata in llm_agents.items()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def process_llm_tools(self, llm_tools: dict[str, list[dict[str, Any]]]) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Process llm_tools from registry response.
|
|
58
|
+
|
|
59
|
+
Creates UnifiedMCPProxy instances and MeshLlmAgent instances
|
|
60
|
+
for each function_id.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
llm_tools: Dict mapping function_name -> list of tool metadata
|
|
64
|
+
Format: {"function_name": [{"function_name": "...", "endpoint": {...}, ...}]}
|
|
65
|
+
Note: Registry uses function_name as key, but we need to map to function_id
|
|
66
|
+
"""
|
|
67
|
+
logger.info(f"🔧 Processing llm_tools for {len(llm_tools)} functions")
|
|
68
|
+
|
|
69
|
+
# Build mapping from function_name to function_id
|
|
70
|
+
function_name_to_id = self._build_function_name_to_id_mapping()
|
|
71
|
+
|
|
72
|
+
for function_name, tools in llm_tools.items():
|
|
73
|
+
try:
|
|
74
|
+
# Map function_name to function_id
|
|
75
|
+
if function_name not in function_name_to_id:
|
|
76
|
+
logger.warning(
|
|
77
|
+
f"⚠️ Function name '{function_name}' not found in DecoratorRegistry, skipping"
|
|
78
|
+
)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
function_id = function_name_to_id[function_name]
|
|
82
|
+
self._process_function_tools(function_id, tools)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(
|
|
85
|
+
f"❌ Error processing llm_tools for {function_name}: {e}",
|
|
86
|
+
exc_info=True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _process_function_tools(
|
|
90
|
+
self, function_id: str, tools: list[dict[str, Any]]
|
|
91
|
+
) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Process tools for a single function.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
function_id: Unique function ID from @mesh.llm decorator
|
|
97
|
+
tools: List of tool metadata from registry
|
|
98
|
+
"""
|
|
99
|
+
# Get LLM agent metadata from DecoratorRegistry
|
|
100
|
+
llm_agents = DecoratorRegistry.get_mesh_llm_agents()
|
|
101
|
+
|
|
102
|
+
if function_id not in llm_agents:
|
|
103
|
+
logger.warning(
|
|
104
|
+
f"⚠️ Function '{function_id}' not found in DecoratorRegistry, skipping"
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
llm_metadata = llm_agents[function_id]
|
|
109
|
+
|
|
110
|
+
# Create UnifiedMCPProxy instances for each tool and build proxy map
|
|
111
|
+
tool_proxies_map = {} # Map function_name -> proxy
|
|
112
|
+
for tool in tools:
|
|
113
|
+
try:
|
|
114
|
+
proxy = self._create_tool_proxy(tool)
|
|
115
|
+
# OpenAPI spec uses "name" (camelCase) - enforce strict contract
|
|
116
|
+
function_name = tool.get("name")
|
|
117
|
+
if function_name:
|
|
118
|
+
tool_proxies_map[function_name] = proxy
|
|
119
|
+
else:
|
|
120
|
+
logger.error(
|
|
121
|
+
f"❌ Tool missing 'name' field (OpenAPI contract): {tool}"
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
# Get tool name for error message
|
|
125
|
+
tool_name = tool.get("name", "unknown")
|
|
126
|
+
logger.error(f"❌ Error creating proxy for tool {tool_name}: {e}")
|
|
127
|
+
# Continue processing other tools
|
|
128
|
+
|
|
129
|
+
# Store LLM agent data with both metadata and proxies
|
|
130
|
+
# Keep original tool metadata for schema building
|
|
131
|
+
self._llm_agents[function_id] = {
|
|
132
|
+
"config": llm_metadata.config,
|
|
133
|
+
"output_type": llm_metadata.output_type,
|
|
134
|
+
"param_name": llm_metadata.param_name,
|
|
135
|
+
"tools_metadata": tools, # Original metadata for schema building
|
|
136
|
+
"tools_proxies": tool_proxies_map, # Proxies for execution
|
|
137
|
+
"function": llm_metadata.function,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
logger.info(
|
|
141
|
+
f"✅ Processed {len(tool_proxies_map)} tools for LLM function '{function_id}'"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Update wrapper with MeshLlmAgent (two-phase pattern - Phase 2)
|
|
145
|
+
# Option A: Decorator stores function in DecoratorRegistry (not _function_registry)
|
|
146
|
+
# Get the function from DecoratorRegistry by matching function_id
|
|
147
|
+
llm_agents = DecoratorRegistry.get_mesh_llm_agents()
|
|
148
|
+
wrapper = None
|
|
149
|
+
for agent_func_id, metadata in llm_agents.items():
|
|
150
|
+
if metadata.function_id == function_id:
|
|
151
|
+
wrapper = metadata.function
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if wrapper and hasattr(wrapper, "_mesh_update_llm_agent"):
|
|
155
|
+
llm_agent = self._create_llm_agent(function_id)
|
|
156
|
+
wrapper._mesh_update_llm_agent(llm_agent)
|
|
157
|
+
logger.info(f"🔄 Updated wrapper with MeshLlmAgent for '{function_id}'")
|
|
158
|
+
elif wrapper:
|
|
159
|
+
logger.warning(
|
|
160
|
+
f"⚠️ Wrapper for '{function_id}' found but has no _mesh_update_llm_agent method"
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
logger.warning(
|
|
164
|
+
f"⚠️ No wrapper found for '{function_id}' - MeshLlmAgent not injected (decorator should have created it)"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def _create_tool_proxy(self, tool: dict[str, Any]) -> UnifiedMCPProxy:
|
|
168
|
+
"""
|
|
169
|
+
Create UnifiedMCPProxy for a tool.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
tool: Tool metadata from registry (must match OpenAPI spec field names)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
UnifiedMCPProxy instance
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
ValueError: If endpoint is missing or invalid
|
|
179
|
+
"""
|
|
180
|
+
# OpenAPI spec uses "name" (camelCase) - enforce strict contract
|
|
181
|
+
function_name = tool.get("name")
|
|
182
|
+
if not function_name:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"Tool missing required 'name' field (OpenAPI contract): {tool}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
endpoint = tool.get("endpoint")
|
|
188
|
+
if not endpoint:
|
|
189
|
+
raise ValueError(f"Tool {function_name} missing endpoint")
|
|
190
|
+
|
|
191
|
+
# Registry returns endpoint as a string URL (e.g., "http://localhost:9091")
|
|
192
|
+
# Use it directly instead of parsing host/port
|
|
193
|
+
if not isinstance(endpoint, str):
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Tool {function_name} has invalid endpoint (expected string): {endpoint}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Create proxy with endpoint URL
|
|
199
|
+
proxy = UnifiedMCPProxy(
|
|
200
|
+
endpoint=endpoint,
|
|
201
|
+
function_name=function_name,
|
|
202
|
+
kwargs_config={
|
|
203
|
+
"capability": tool.get("capability"),
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
logger.debug(f"🔧 Created proxy for {function_name} at {endpoint}")
|
|
208
|
+
|
|
209
|
+
return proxy
|
|
210
|
+
|
|
211
|
+
def create_injection_wrapper(self, func: Callable, function_id: str) -> Callable:
|
|
212
|
+
"""
|
|
213
|
+
Create wrapper that injects MeshLlmAgent into function parameters.
|
|
214
|
+
|
|
215
|
+
Like McpMeshAgent injection, this creates a wrapper at decorator time with llm_agent=None,
|
|
216
|
+
which gets updated during heartbeat when tools arrive from registry.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
func: Original function to wrap
|
|
220
|
+
function_id: Unique function ID
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Wrapped function with MeshLlmAgent injection
|
|
224
|
+
"""
|
|
225
|
+
# Get LLM metadata from DecoratorRegistry (registered at decorator time)
|
|
226
|
+
llm_agents = DecoratorRegistry.get_mesh_llm_agents()
|
|
227
|
+
|
|
228
|
+
if function_id not in llm_agents:
|
|
229
|
+
logger.warning(
|
|
230
|
+
f"⚠️ Function '{function_id}' not found in DecoratorRegistry, creating wrapper anyway"
|
|
231
|
+
)
|
|
232
|
+
# Get param_name from stored data if available, otherwise raise
|
|
233
|
+
if function_id in self._llm_agents:
|
|
234
|
+
param_name = self._llm_agents[function_id]["param_name"]
|
|
235
|
+
else:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Function '{function_id}' not found in LLM agent registry"
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
llm_metadata = llm_agents[function_id]
|
|
241
|
+
param_name = llm_metadata.param_name
|
|
242
|
+
|
|
243
|
+
# Validate parameter exists
|
|
244
|
+
sig = inspect.signature(func)
|
|
245
|
+
if param_name not in sig.parameters:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
f"Function '{func.__name__}' missing MeshLlmAgent parameter '{param_name}'"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Initialize with None (will be updated during heartbeat)
|
|
251
|
+
llm_agent = None
|
|
252
|
+
|
|
253
|
+
# Create injection logic closure
|
|
254
|
+
def inject_llm_agent(func: Callable, args: tuple, kwargs: dict) -> tuple:
|
|
255
|
+
"""Inject LLM agent into kwargs if not provided."""
|
|
256
|
+
if param_name not in kwargs or kwargs.get(param_name) is None:
|
|
257
|
+
# Phase 4: Check if templates are enabled
|
|
258
|
+
if function_id in self._llm_agents:
|
|
259
|
+
agent_data = self._llm_agents[function_id]
|
|
260
|
+
config_dict = agent_data["config"]
|
|
261
|
+
is_template = config_dict.get("is_template", False)
|
|
262
|
+
|
|
263
|
+
if is_template:
|
|
264
|
+
# Templates enabled - create per-call agent with context
|
|
265
|
+
# Import signature analyzer for context detection
|
|
266
|
+
from .signature_analyzer import get_context_parameter_name
|
|
267
|
+
|
|
268
|
+
# Detect context parameter
|
|
269
|
+
context_param_name = config_dict.get("context_param")
|
|
270
|
+
context_info = get_context_parameter_name(
|
|
271
|
+
func, explicit_name=context_param_name
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Extract context value from call
|
|
275
|
+
context_value = None
|
|
276
|
+
if context_info is not None:
|
|
277
|
+
ctx_name, ctx_index = context_info
|
|
278
|
+
|
|
279
|
+
# Try kwargs first
|
|
280
|
+
if ctx_name in kwargs:
|
|
281
|
+
context_value = kwargs[ctx_name]
|
|
282
|
+
# Then try positional args
|
|
283
|
+
elif ctx_index < len(args):
|
|
284
|
+
context_value = args[ctx_index]
|
|
285
|
+
|
|
286
|
+
# Create agent with context for this call
|
|
287
|
+
current_agent = self._create_llm_agent(
|
|
288
|
+
function_id, context_value=context_value
|
|
289
|
+
)
|
|
290
|
+
logger.debug(
|
|
291
|
+
f"🤖 Created MeshLlmAgent with context for {func.__name__}.{param_name}"
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
# No template - use cached agent (existing behavior)
|
|
295
|
+
current_agent = wrapper._mesh_llm_agent
|
|
296
|
+
if current_agent is not None:
|
|
297
|
+
logger.debug(
|
|
298
|
+
f"🤖 Injected MeshLlmAgent into {func.__name__}.{param_name}"
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
logger.warning(
|
|
302
|
+
f"⚠️ MeshLlmAgent for {func.__name__}.{param_name} is None (tools not yet received from registry)"
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
# No agent data - use cached (backward compatibility)
|
|
306
|
+
current_agent = wrapper._mesh_llm_agent
|
|
307
|
+
if current_agent is None:
|
|
308
|
+
logger.warning(
|
|
309
|
+
f"⚠️ MeshLlmAgent for {func.__name__}.{param_name} is None (tools not yet received from registry)"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
kwargs[param_name] = current_agent
|
|
313
|
+
return args, kwargs
|
|
314
|
+
|
|
315
|
+
# Create update method closure
|
|
316
|
+
def update_llm_agent(agent: MeshLlmAgent | None) -> None:
|
|
317
|
+
wrapper._mesh_llm_agent = agent
|
|
318
|
+
logger.info(
|
|
319
|
+
f"🔄 Updated MeshLlmAgent for {func.__name__} (function_id={function_id})"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Prepare metadata
|
|
323
|
+
metadata = {
|
|
324
|
+
"_mesh_llm_agent": llm_agent,
|
|
325
|
+
"_mesh_param_name": param_name,
|
|
326
|
+
"_mesh_update_llm_agent": update_llm_agent,
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# Use base class to create wrapper (handles async/sync automatically)
|
|
330
|
+
wrapper = self.create_wrapper_with_injection(
|
|
331
|
+
func, function_id, inject_llm_agent, metadata, register=True
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
logger.debug(
|
|
335
|
+
f"✅ Created LLM injection wrapper for {func.__name__} (agent=None, will be updated during heartbeat)"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return wrapper
|
|
339
|
+
|
|
340
|
+
def _create_llm_agent(
|
|
341
|
+
self, function_id: str, context_value: Any = None
|
|
342
|
+
) -> MeshLlmAgent:
|
|
343
|
+
"""
|
|
344
|
+
Create MeshLlmAgent instance for a function.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
function_id: Unique function ID
|
|
348
|
+
context_value: Optional context for template rendering (Phase 4)
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
MeshLlmAgent instance configured with tools and config
|
|
352
|
+
"""
|
|
353
|
+
llm_agent_data = self._llm_agents[function_id]
|
|
354
|
+
config_dict = llm_agent_data["config"]
|
|
355
|
+
|
|
356
|
+
# Create LLMConfig from dict
|
|
357
|
+
llm_config = LLMConfig(
|
|
358
|
+
provider=config_dict.get("provider", "claude"),
|
|
359
|
+
model=config_dict.get("model", "claude-3-5-sonnet-20241022"),
|
|
360
|
+
api_key=config_dict.get("api_key", ""), # Will use ENV if empty
|
|
361
|
+
max_iterations=config_dict.get("max_iterations", 10),
|
|
362
|
+
system_prompt=config_dict.get("system_prompt"),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Phase 4: Template support - extract template metadata
|
|
366
|
+
is_template = config_dict.get("is_template", False)
|
|
367
|
+
template_path = config_dict.get("template_path")
|
|
368
|
+
|
|
369
|
+
# Create MeshLlmAgent with both metadata and proxies
|
|
370
|
+
llm_agent = MeshLlmAgent(
|
|
371
|
+
config=llm_config,
|
|
372
|
+
filtered_tools=llm_agent_data[
|
|
373
|
+
"tools_metadata"
|
|
374
|
+
], # Metadata for schema building
|
|
375
|
+
output_type=llm_agent_data["output_type"],
|
|
376
|
+
tool_proxies=llm_agent_data["tools_proxies"], # Proxies for execution
|
|
377
|
+
template_path=template_path if is_template else None,
|
|
378
|
+
context_value=context_value if is_template else None,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
logger.debug(
|
|
382
|
+
f"🤖 Created MeshLlmAgent for {function_id} with {len(llm_agent_data['tools_metadata'])} tools"
|
|
383
|
+
+ (f", template={template_path}" if is_template else "")
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
return llm_agent
|
|
387
|
+
|
|
388
|
+
def update_llm_tools(self, llm_tools: dict[str, list[dict[str, Any]]]) -> None:
|
|
389
|
+
"""
|
|
390
|
+
Update LLM tools when topology changes.
|
|
391
|
+
|
|
392
|
+
Handles:
|
|
393
|
+
- New tools being added
|
|
394
|
+
- Existing tools being removed
|
|
395
|
+
- Entire functions being removed
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
llm_tools: Updated llm_tools dict from registry (keyed by function_name)
|
|
399
|
+
"""
|
|
400
|
+
logger.info(f"🔄 Updating llm_tools for {len(llm_tools)} functions")
|
|
401
|
+
|
|
402
|
+
# Build mapping from function_name to function_id
|
|
403
|
+
function_name_to_id = self._build_function_name_to_id_mapping()
|
|
404
|
+
|
|
405
|
+
# Map function_names from registry to function_ids
|
|
406
|
+
current_function_ids = set()
|
|
407
|
+
for function_name in llm_tools.keys():
|
|
408
|
+
if function_name in function_name_to_id:
|
|
409
|
+
current_function_ids.add(function_name_to_id[function_name])
|
|
410
|
+
|
|
411
|
+
# Track which functions are still active
|
|
412
|
+
previous_functions = set(self._llm_agents.keys())
|
|
413
|
+
|
|
414
|
+
# Remove functions that are no longer in the topology
|
|
415
|
+
removed_functions = previous_functions - current_function_ids
|
|
416
|
+
for function_id in removed_functions:
|
|
417
|
+
logger.info(
|
|
418
|
+
f"🗑️ Removing LLM function {function_id} (no longer in topology)"
|
|
419
|
+
)
|
|
420
|
+
del self._llm_agents[function_id]
|
|
421
|
+
# Also remove from function registry if present
|
|
422
|
+
if function_id in self._function_registry:
|
|
423
|
+
del self._function_registry[function_id]
|
|
424
|
+
|
|
425
|
+
# Update or add functions
|
|
426
|
+
for function_name, tools in llm_tools.items():
|
|
427
|
+
try:
|
|
428
|
+
# Map function_name to function_id
|
|
429
|
+
if function_name not in function_name_to_id:
|
|
430
|
+
logger.warning(
|
|
431
|
+
f"⚠️ Function name '{function_name}' not found in DecoratorRegistry during update, skipping"
|
|
432
|
+
)
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
function_id = function_name_to_id[function_name]
|
|
436
|
+
|
|
437
|
+
# Reprocess tools (will update existing or create new)
|
|
438
|
+
self._process_function_tools(function_id, tools)
|
|
439
|
+
|
|
440
|
+
# Update existing wrappers if they exist
|
|
441
|
+
if function_id in self._function_registry:
|
|
442
|
+
wrapper = self._function_registry[function_id]
|
|
443
|
+
# Recreate LLM agent with updated tools
|
|
444
|
+
new_llm_agent = self._create_llm_agent(function_id)
|
|
445
|
+
wrapper._mesh_llm_agent = new_llm_agent
|
|
446
|
+
logger.debug(
|
|
447
|
+
f"🔄 Updated MeshLlmAgent for existing wrapper: {function_id}"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logger.error(
|
|
452
|
+
f"❌ Error updating llm_tools for {function_name}: {e}",
|
|
453
|
+
exc_info=True,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
logger.info(
|
|
457
|
+
f"✅ LLM tools update complete: {len(self._llm_agents)} functions active"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
def get_llm_agent_data(self, function_id: str) -> dict[str, Any] | None:
|
|
461
|
+
"""
|
|
462
|
+
Get LLM agent data for a function.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
function_id: Unique function ID
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
LLM agent data dict or None if not found
|
|
469
|
+
"""
|
|
470
|
+
return self._llm_agents.get(function_id)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# Global injector instance
|
|
474
|
+
_global_llm_injector: MeshLlmAgentInjector | None = None
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def get_global_llm_injector() -> MeshLlmAgentInjector:
|
|
478
|
+
"""
|
|
479
|
+
Get or create the global MeshLlmAgentInjector instance.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Global MeshLlmAgentInjector instance
|
|
483
|
+
"""
|
|
484
|
+
global _global_llm_injector
|
|
485
|
+
if _global_llm_injector is None:
|
|
486
|
+
_global_llm_injector = MeshLlmAgentInjector()
|
|
487
|
+
return _global_llm_injector
|