glaip-sdk 0.6.5b5__py3-none-any.whl → 0.6.5b9__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.
- glaip_sdk/agents/base.py +43 -7
- glaip_sdk/registry/tool.py +4 -11
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +597 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/runtime_config.py +116 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- {glaip_sdk-0.6.5b5.dist-info → glaip_sdk-0.6.5b9.dist-info}/METADATA +6 -2
- {glaip_sdk-0.6.5b5.dist-info → glaip_sdk-0.6.5b9.dist-info}/RECORD +21 -7
- {glaip_sdk-0.6.5b5.dist-info → glaip_sdk-0.6.5b9.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.5b5.dist-info → glaip_sdk-0.6.5b9.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"""LangGraph-based runner for local agent execution.
|
|
2
|
+
|
|
3
|
+
This module provides the LangGraphRunner which executes glaip-sdk agents
|
|
4
|
+
locally via the aip-agents LangGraphReactAgent, without requiring the AIP server.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from glaip_sdk.runner import LangGraphRunner
|
|
11
|
+
>>> from glaip_sdk.agents import Agent
|
|
12
|
+
>>>
|
|
13
|
+
>>> runner = LangGraphRunner()
|
|
14
|
+
>>> agent = Agent(name="my-agent", instruction="You are helpful.")
|
|
15
|
+
>>> result = runner.run(agent, "Hello, world!")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
from glaip_sdk.runner.base import BaseRunner
|
|
25
|
+
from glaip_sdk.runner.deps import (
|
|
26
|
+
check_local_runtime_available,
|
|
27
|
+
get_local_runtime_missing_message,
|
|
28
|
+
)
|
|
29
|
+
from glaip_sdk.utils.a2a import A2AEventStreamProcessor
|
|
30
|
+
from gllm_core.utils import LoggerManager
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from glaip_sdk.agents.base import Agent
|
|
34
|
+
|
|
35
|
+
logger = LoggerManager().get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
# Default A2A event processor
|
|
38
|
+
_event_processor = A2AEventStreamProcessor()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class LangGraphRunner(BaseRunner):
|
|
43
|
+
"""Runner implementation using aip-agents LangGraphReactAgent.
|
|
44
|
+
|
|
45
|
+
MVP scope:
|
|
46
|
+
- Execute via `LangGraphReactAgent.arun_a2a_stream()`
|
|
47
|
+
- Extract and return final text from the emitted `final_response` event
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
default_model: Model name to use when agent.model is not set.
|
|
51
|
+
Defaults to "gpt-4o-mini".
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
default_model: str = "openai/gpt-4o-mini"
|
|
55
|
+
|
|
56
|
+
def run(
|
|
57
|
+
self,
|
|
58
|
+
agent: Agent,
|
|
59
|
+
message: str,
|
|
60
|
+
verbose: bool = False,
|
|
61
|
+
runtime_config: dict[str, Any] | None = None, # noqa: ARG002 - Used in PR-04+
|
|
62
|
+
chat_history: (list[dict[str, str]] | None) = None, # noqa: ARG002 - Used in PR-03
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Execute agent synchronously and return final response text.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
agent: The glaip_sdk Agent to execute.
|
|
69
|
+
message: The user message to send to the agent.
|
|
70
|
+
verbose: If True, emit debug trace output during execution.
|
|
71
|
+
Defaults to False.
|
|
72
|
+
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
73
|
+
Defaults to None. (Implemented in PR-04+)
|
|
74
|
+
chat_history: Optional list of prior conversation messages.
|
|
75
|
+
Defaults to None. (Implemented in PR-03)
|
|
76
|
+
**kwargs: Additional keyword arguments passed to the backend.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The final response text from the agent.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
RuntimeError: If the local runtime dependencies are not available.
|
|
83
|
+
RuntimeError: If no final response is received from the agent.
|
|
84
|
+
"""
|
|
85
|
+
if not check_local_runtime_available():
|
|
86
|
+
raise RuntimeError(get_local_runtime_missing_message())
|
|
87
|
+
|
|
88
|
+
return asyncio.run(
|
|
89
|
+
self._arun_internal(
|
|
90
|
+
agent=agent,
|
|
91
|
+
message=message,
|
|
92
|
+
verbose=verbose,
|
|
93
|
+
runtime_config=runtime_config,
|
|
94
|
+
**kwargs,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def arun(
|
|
99
|
+
self,
|
|
100
|
+
agent: Agent,
|
|
101
|
+
message: str,
|
|
102
|
+
verbose: bool = False,
|
|
103
|
+
runtime_config: dict[str, Any] | None = None,
|
|
104
|
+
chat_history: (list[dict[str, str]] | None) = None, # noqa: ARG002 - Used in PR-03
|
|
105
|
+
**kwargs: Any,
|
|
106
|
+
) -> str:
|
|
107
|
+
"""Execute agent asynchronously and return final response text.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
agent: The glaip_sdk Agent to execute.
|
|
111
|
+
message: The user message to send to the agent.
|
|
112
|
+
verbose: If True, emit debug trace output during execution.
|
|
113
|
+
Defaults to False.
|
|
114
|
+
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
115
|
+
Defaults to None. (Implemented in PR-04+)
|
|
116
|
+
chat_history: Optional list of prior conversation messages.
|
|
117
|
+
Defaults to None. (Implemented in PR-03)
|
|
118
|
+
**kwargs: Additional keyword arguments passed to the backend.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The final response text from the agent.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
RuntimeError: If no final response is received from the agent.
|
|
125
|
+
"""
|
|
126
|
+
return await self._arun_internal(
|
|
127
|
+
agent=agent,
|
|
128
|
+
message=message,
|
|
129
|
+
verbose=verbose,
|
|
130
|
+
runtime_config=runtime_config,
|
|
131
|
+
**kwargs,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def _arun_internal(
|
|
135
|
+
self,
|
|
136
|
+
agent: Agent,
|
|
137
|
+
message: str,
|
|
138
|
+
verbose: bool = False,
|
|
139
|
+
runtime_config: dict[str, Any] | None = None,
|
|
140
|
+
**kwargs: Any,
|
|
141
|
+
) -> str:
|
|
142
|
+
"""Internal async implementation of agent execution.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
agent: The glaip_sdk Agent to execute.
|
|
146
|
+
message: The user message to send to the agent.
|
|
147
|
+
verbose: If True, emit debug trace output during execution.
|
|
148
|
+
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
149
|
+
**kwargs: Additional keyword arguments passed to the backend.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The final response text from the agent.
|
|
153
|
+
"""
|
|
154
|
+
# Build the local LangGraphReactAgent from the glaip_sdk Agent
|
|
155
|
+
local_agent = self.build_langgraph_agent(agent, runtime_config=runtime_config)
|
|
156
|
+
|
|
157
|
+
# Collect A2AEvents from the stream and extract final response
|
|
158
|
+
events: list[dict[str, Any]] = []
|
|
159
|
+
|
|
160
|
+
async for event in local_agent.arun_a2a_stream(message, **kwargs):
|
|
161
|
+
if verbose:
|
|
162
|
+
self._log_event(event)
|
|
163
|
+
events.append(event)
|
|
164
|
+
|
|
165
|
+
return _event_processor.extract_final_response(events)
|
|
166
|
+
|
|
167
|
+
def build_langgraph_agent(
|
|
168
|
+
self,
|
|
169
|
+
agent: Agent,
|
|
170
|
+
runtime_config: dict[str, Any] | None = None,
|
|
171
|
+
) -> Any:
|
|
172
|
+
"""Build a LangGraphReactAgent from a glaip_sdk Agent definition.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
agent: The glaip_sdk Agent to convert.
|
|
176
|
+
runtime_config: Optional runtime configuration with tool_configs,
|
|
177
|
+
mcp_configs, agent_config, and agent-specific overrides.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A configured LangGraphReactAgent instance.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ImportError: If aip-agents is not installed.
|
|
184
|
+
ValueError: If agent has unsupported tools, MCPs, or sub-agents for local mode.
|
|
185
|
+
"""
|
|
186
|
+
from aip_agents.agent import LangGraphReactAgent # noqa: PLC0415
|
|
187
|
+
from glaip_sdk.runner.tool_adapter import LangChainToolAdapter # noqa: PLC0415
|
|
188
|
+
|
|
189
|
+
# Adapt tools for local execution
|
|
190
|
+
langchain_tools: list[Any] = []
|
|
191
|
+
if agent.tools:
|
|
192
|
+
adapter = LangChainToolAdapter()
|
|
193
|
+
langchain_tools = adapter.adapt_tools(agent.tools)
|
|
194
|
+
|
|
195
|
+
# Build sub-agents recursively
|
|
196
|
+
sub_agent_instances = self._build_sub_agents(agent.agents, runtime_config)
|
|
197
|
+
|
|
198
|
+
# Normalize runtime config: merge global and agent-specific configs
|
|
199
|
+
normalized_config = self._normalize_runtime_config(runtime_config, agent)
|
|
200
|
+
|
|
201
|
+
# Merge tool_configs: agent definition < runtime config
|
|
202
|
+
tool_configs = self._merge_tool_configs(agent, normalized_config)
|
|
203
|
+
|
|
204
|
+
# Merge mcp_configs: agent definition < runtime config
|
|
205
|
+
mcp_configs = self._merge_mcp_configs(agent, normalized_config)
|
|
206
|
+
|
|
207
|
+
# Merge agent_config: agent definition < runtime config
|
|
208
|
+
merged_agent_config = self._merge_agent_config(agent, normalized_config)
|
|
209
|
+
agent_config_params, agent_config_kwargs = self._apply_agent_config(merged_agent_config)
|
|
210
|
+
|
|
211
|
+
# Build the LangGraphReactAgent with tools, sub-agents, and configs
|
|
212
|
+
local_agent = LangGraphReactAgent(
|
|
213
|
+
name=agent.name,
|
|
214
|
+
instruction=agent.instruction,
|
|
215
|
+
description=agent.description,
|
|
216
|
+
model=agent.model or self.default_model,
|
|
217
|
+
tools=langchain_tools,
|
|
218
|
+
agents=sub_agent_instances if sub_agent_instances else None,
|
|
219
|
+
tool_configs=tool_configs if tool_configs else None,
|
|
220
|
+
**agent_config_params,
|
|
221
|
+
**agent_config_kwargs,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Add MCP servers if configured
|
|
225
|
+
self._add_mcp_servers(local_agent, agent, mcp_configs)
|
|
226
|
+
|
|
227
|
+
logger.debug(
|
|
228
|
+
"Built local LangGraphReactAgent for agent '%s' with %d tools, %d sub-agents, and %d MCPs",
|
|
229
|
+
agent.name,
|
|
230
|
+
len(langchain_tools),
|
|
231
|
+
len(sub_agent_instances),
|
|
232
|
+
len(agent.mcps) if agent.mcps else 0,
|
|
233
|
+
)
|
|
234
|
+
return local_agent
|
|
235
|
+
|
|
236
|
+
def _build_sub_agents(
|
|
237
|
+
self,
|
|
238
|
+
sub_agents: list[Any] | None,
|
|
239
|
+
runtime_config: dict[str, Any] | None,
|
|
240
|
+
) -> list[Any]:
|
|
241
|
+
"""Build sub-agent instances recursively.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
sub_agents: List of sub-agent definitions.
|
|
245
|
+
runtime_config: Runtime config to pass to sub-agents.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
List of built sub-agent instances.
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
ValueError: If sub-agent is platform-only.
|
|
252
|
+
"""
|
|
253
|
+
if not sub_agents:
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
sub_agent_instances = []
|
|
257
|
+
for sub_agent in sub_agents:
|
|
258
|
+
if getattr(sub_agent, "_lookup_only", False):
|
|
259
|
+
agent_name = getattr(sub_agent, "name", "<unknown>")
|
|
260
|
+
raise ValueError(
|
|
261
|
+
f"Sub-agent '{agent_name}' is not supported in local mode. "
|
|
262
|
+
"Platform agents (from_id, from_native) cannot be used as "
|
|
263
|
+
"sub-agents in local execution. "
|
|
264
|
+
"Define the sub-agent locally with Agent(name=..., instruction=...) instead."
|
|
265
|
+
)
|
|
266
|
+
sub_agent_instances.append(self.build_langgraph_agent(sub_agent, runtime_config))
|
|
267
|
+
return sub_agent_instances
|
|
268
|
+
|
|
269
|
+
def _add_mcp_servers(
|
|
270
|
+
self,
|
|
271
|
+
local_agent: Any,
|
|
272
|
+
agent: Agent,
|
|
273
|
+
merged_mcp_configs: dict[str, Any],
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Add MCP servers to a built agent.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
local_agent: The LangGraphReactAgent to add MCPs to.
|
|
279
|
+
agent: The glaip_sdk Agent with MCP definitions.
|
|
280
|
+
merged_mcp_configs: Merged mcp_configs (agent definition + runtime).
|
|
281
|
+
"""
|
|
282
|
+
if not agent.mcps:
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
from glaip_sdk.runner.mcp_adapter import LangChainMCPAdapter # noqa: PLC0415
|
|
286
|
+
|
|
287
|
+
mcp_adapter = LangChainMCPAdapter()
|
|
288
|
+
base_mcp_configs = mcp_adapter.adapt_mcps(agent.mcps)
|
|
289
|
+
logger.debug("Base MCP configs from adapter: %s", base_mcp_configs)
|
|
290
|
+
|
|
291
|
+
# Apply merged mcp_configs overrides (agent definition + runtime)
|
|
292
|
+
logger.debug("Merged mcp_configs to apply: %s", merged_mcp_configs)
|
|
293
|
+
if merged_mcp_configs:
|
|
294
|
+
base_mcp_configs = self._apply_runtime_mcp_configs(base_mcp_configs, merged_mcp_configs)
|
|
295
|
+
logger.debug("MCP configs after override: %s", base_mcp_configs)
|
|
296
|
+
|
|
297
|
+
if base_mcp_configs:
|
|
298
|
+
logger.info("MCP configs being sent to aip-agents: %s", base_mcp_configs)
|
|
299
|
+
local_agent.add_mcp_server(base_mcp_configs)
|
|
300
|
+
logger.debug(
|
|
301
|
+
"Registered %d MCP server(s) for agent '%s'",
|
|
302
|
+
len(base_mcp_configs),
|
|
303
|
+
agent.name,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def _normalize_runtime_config(
|
|
307
|
+
self,
|
|
308
|
+
runtime_config: dict[str, Any] | None,
|
|
309
|
+
agent: Agent,
|
|
310
|
+
) -> dict[str, Any]:
|
|
311
|
+
"""Normalize runtime_config for local execution.
|
|
312
|
+
|
|
313
|
+
Merges global and agent-specific configs with proper priority.
|
|
314
|
+
Keys are resolved from instances/classes to string names.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
runtime_config: Raw runtime config from user.
|
|
318
|
+
agent: The agent being built (for resolving agent-specific overrides).
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Normalized config with string keys and merged priorities.
|
|
322
|
+
"""
|
|
323
|
+
from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
|
|
324
|
+
merge_configs,
|
|
325
|
+
normalize_local_config_keys,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if not runtime_config:
|
|
329
|
+
return {}
|
|
330
|
+
|
|
331
|
+
# 1. Extract global configs and normalize keys
|
|
332
|
+
global_tool_configs = normalize_local_config_keys(runtime_config.get("tool_configs", {}))
|
|
333
|
+
global_mcp_configs = normalize_local_config_keys(runtime_config.get("mcp_configs", {}))
|
|
334
|
+
global_agent_config = runtime_config.get("agent_config", {})
|
|
335
|
+
|
|
336
|
+
# 2. Extract agent-specific overrides (highest priority)
|
|
337
|
+
agent_specific = self._get_agent_specific_config(runtime_config, agent)
|
|
338
|
+
agent_tool_configs = normalize_local_config_keys(agent_specific.get("tool_configs", {}))
|
|
339
|
+
agent_mcp_configs = normalize_local_config_keys(agent_specific.get("mcp_configs", {}))
|
|
340
|
+
agent_config_override = agent_specific.get("agent_config", {})
|
|
341
|
+
|
|
342
|
+
# 3. Merge with priority: global < agent-specific
|
|
343
|
+
merged_result = {
|
|
344
|
+
"tool_configs": merge_configs(global_tool_configs, agent_tool_configs),
|
|
345
|
+
"mcp_configs": merge_configs(global_mcp_configs, agent_mcp_configs),
|
|
346
|
+
"agent_config": merge_configs(global_agent_config, agent_config_override),
|
|
347
|
+
}
|
|
348
|
+
return merged_result
|
|
349
|
+
|
|
350
|
+
def _get_agent_specific_config(
|
|
351
|
+
self,
|
|
352
|
+
runtime_config: dict[str, Any],
|
|
353
|
+
agent: Agent,
|
|
354
|
+
) -> dict[str, Any]:
|
|
355
|
+
"""Extract agent-specific config from runtime_config.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
runtime_config: Runtime config that may contain agent-specific overrides.
|
|
359
|
+
agent: The agent to find config for.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Agent-specific config dict, or empty dict if not found.
|
|
363
|
+
"""
|
|
364
|
+
from glaip_sdk.utils.runtime_config import get_name_from_key # noqa: PLC0415
|
|
365
|
+
|
|
366
|
+
# Reserved keys at the top level
|
|
367
|
+
reserved_keys = {"tool_configs", "mcp_configs", "agent_config"}
|
|
368
|
+
|
|
369
|
+
# Try finding agent by instance, class, or name
|
|
370
|
+
for key, value in runtime_config.items():
|
|
371
|
+
if key in reserved_keys:
|
|
372
|
+
continue # Skip global configs
|
|
373
|
+
|
|
374
|
+
# Check if this key matches the agent
|
|
375
|
+
try:
|
|
376
|
+
key_name = get_name_from_key(key)
|
|
377
|
+
except ValueError:
|
|
378
|
+
continue # Skip invalid keys
|
|
379
|
+
|
|
380
|
+
if key_name and key_name == agent.name:
|
|
381
|
+
return value if isinstance(value, dict) else {}
|
|
382
|
+
|
|
383
|
+
return {}
|
|
384
|
+
|
|
385
|
+
def _merge_tool_configs(
|
|
386
|
+
self,
|
|
387
|
+
agent: Agent,
|
|
388
|
+
normalized_config: dict[str, Any],
|
|
389
|
+
) -> dict[str, Any]:
|
|
390
|
+
"""Merge agent.tool_configs with runtime tool_configs.
|
|
391
|
+
|
|
392
|
+
Priority (lowest to highest):
|
|
393
|
+
1. Agent definition (agent.tool_configs)
|
|
394
|
+
2. Runtime config (normalized_config["tool_configs"])
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
agent: The agent with optional tool_configs property.
|
|
398
|
+
normalized_config: Normalized runtime config.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Merged tool_configs dict.
|
|
402
|
+
"""
|
|
403
|
+
from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
|
|
404
|
+
merge_configs,
|
|
405
|
+
normalize_local_config_keys,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
# Get agent's tool_configs if defined
|
|
409
|
+
agent_tool_configs = {}
|
|
410
|
+
if hasattr(agent, "tool_configs") and agent.tool_configs:
|
|
411
|
+
agent_tool_configs = normalize_local_config_keys(agent.tool_configs)
|
|
412
|
+
|
|
413
|
+
# Get runtime tool_configs
|
|
414
|
+
runtime_tool_configs = normalized_config.get("tool_configs", {})
|
|
415
|
+
|
|
416
|
+
# Merge: agent definition < runtime config
|
|
417
|
+
return merge_configs(agent_tool_configs, runtime_tool_configs)
|
|
418
|
+
|
|
419
|
+
def _merge_mcp_configs(
|
|
420
|
+
self,
|
|
421
|
+
agent: Agent,
|
|
422
|
+
normalized_config: dict[str, Any],
|
|
423
|
+
) -> dict[str, Any]:
|
|
424
|
+
"""Merge agent.mcp_configs with runtime mcp_configs.
|
|
425
|
+
|
|
426
|
+
Priority (lowest to highest):
|
|
427
|
+
1. Agent definition (agent.mcp_configs)
|
|
428
|
+
2. Runtime config (normalized_config["mcp_configs"])
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
agent: The agent with optional mcp_configs property.
|
|
432
|
+
normalized_config: Normalized runtime config.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Merged mcp_configs dict.
|
|
436
|
+
"""
|
|
437
|
+
from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
|
|
438
|
+
merge_configs,
|
|
439
|
+
normalize_local_config_keys,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Get agent's mcp_configs if defined
|
|
443
|
+
agent_mcp_configs = {}
|
|
444
|
+
if hasattr(agent, "mcp_configs") and agent.mcp_configs:
|
|
445
|
+
agent_mcp_configs = normalize_local_config_keys(agent.mcp_configs)
|
|
446
|
+
|
|
447
|
+
# Get runtime mcp_configs
|
|
448
|
+
runtime_mcp_configs = normalized_config.get("mcp_configs", {})
|
|
449
|
+
|
|
450
|
+
# Merge: agent definition < runtime config
|
|
451
|
+
return merge_configs(agent_mcp_configs, runtime_mcp_configs)
|
|
452
|
+
|
|
453
|
+
def _merge_agent_config(
|
|
454
|
+
self,
|
|
455
|
+
agent: Agent,
|
|
456
|
+
normalized_config: dict[str, Any],
|
|
457
|
+
) -> dict[str, Any]:
|
|
458
|
+
"""Merge agent.agent_config with runtime agent_config.
|
|
459
|
+
|
|
460
|
+
Priority (lowest to highest):
|
|
461
|
+
1. Agent definition (agent.agent_config)
|
|
462
|
+
2. Runtime config (normalized_config["agent_config"])
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
agent: The agent with optional agent_config property.
|
|
466
|
+
normalized_config: Normalized runtime config.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Merged agent_config dict.
|
|
470
|
+
"""
|
|
471
|
+
from glaip_sdk.utils.runtime_config import merge_configs # noqa: PLC0415
|
|
472
|
+
|
|
473
|
+
# Get agent's agent_config if defined
|
|
474
|
+
agent_agent_config = {}
|
|
475
|
+
if hasattr(agent, "agent_config") and agent.agent_config:
|
|
476
|
+
agent_agent_config = agent.agent_config
|
|
477
|
+
|
|
478
|
+
# Get runtime agent_config
|
|
479
|
+
runtime_agent_config = normalized_config.get("agent_config", {})
|
|
480
|
+
|
|
481
|
+
# Merge: agent definition < runtime config
|
|
482
|
+
return merge_configs(agent_agent_config, runtime_agent_config)
|
|
483
|
+
|
|
484
|
+
def _apply_agent_config(
|
|
485
|
+
self,
|
|
486
|
+
agent_config: dict[str, Any],
|
|
487
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
488
|
+
"""Extract and separate agent_config into direct params and kwargs.
|
|
489
|
+
|
|
490
|
+
Separates agent_config into parameters that go directly to LangGraphReactAgent
|
|
491
|
+
constructor vs those that go through **kwargs.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
agent_config: Runtime agent configuration dict.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
Tuple of (direct_params, kwargs_params):
|
|
498
|
+
- direct_params: Parameters passed directly to LangGraphReactAgent.__init__()
|
|
499
|
+
- kwargs_params: Parameters passed via **kwargs to BaseAgent
|
|
500
|
+
"""
|
|
501
|
+
direct_params = {}
|
|
502
|
+
kwargs_params = {}
|
|
503
|
+
|
|
504
|
+
# Direct constructor parameters
|
|
505
|
+
if "planning" in agent_config:
|
|
506
|
+
direct_params["planning"] = agent_config["planning"]
|
|
507
|
+
|
|
508
|
+
# Kwargs parameters (passed through **kwargs to BaseAgent)
|
|
509
|
+
if "memory" in agent_config:
|
|
510
|
+
# Map "memory" to "memory_backend" for aip-agents compatibility
|
|
511
|
+
kwargs_params["memory_backend"] = agent_config["memory"]
|
|
512
|
+
|
|
513
|
+
# Additional memory-related settings
|
|
514
|
+
memory_settings = ["agent_id", "memory_namespace", "save_interaction_to_memory"]
|
|
515
|
+
for key in memory_settings:
|
|
516
|
+
if key in agent_config:
|
|
517
|
+
kwargs_params[key] = agent_config[key]
|
|
518
|
+
|
|
519
|
+
return direct_params, kwargs_params
|
|
520
|
+
|
|
521
|
+
def _apply_runtime_mcp_configs(
|
|
522
|
+
self,
|
|
523
|
+
base_configs: dict[str, Any],
|
|
524
|
+
runtime_overrides: dict[str, Any],
|
|
525
|
+
) -> dict[str, Any]:
|
|
526
|
+
"""Apply runtime mcp_configs overrides to base MCP configurations.
|
|
527
|
+
|
|
528
|
+
Merges runtime overrides into the base configs, handling authentication
|
|
529
|
+
conversion to headers using MCPConfigBuilder.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
base_configs: Base MCP configs from adapter (server_name -> config).
|
|
533
|
+
runtime_overrides: Runtime mcp_configs overrides (server_name -> config).
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
Merged MCP configs with authentication converted to headers.
|
|
537
|
+
"""
|
|
538
|
+
return {
|
|
539
|
+
server_name: self._merge_single_mcp_config(server_name, base_config, runtime_overrides.get(server_name))
|
|
540
|
+
for server_name, base_config in base_configs.items()
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
def _merge_single_mcp_config(
|
|
544
|
+
self,
|
|
545
|
+
server_name: str,
|
|
546
|
+
base_config: dict[str, Any],
|
|
547
|
+
override: dict[str, Any] | None,
|
|
548
|
+
) -> dict[str, Any]:
|
|
549
|
+
"""Merge a single MCP config with runtime override.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
server_name: Name of the MCP server.
|
|
553
|
+
base_config: Base config from adapter.
|
|
554
|
+
override: Optional runtime override config.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
Merged config dict.
|
|
558
|
+
"""
|
|
559
|
+
merged = base_config.copy()
|
|
560
|
+
|
|
561
|
+
if not override:
|
|
562
|
+
return merged
|
|
563
|
+
|
|
564
|
+
from glaip_sdk.runner.mcp_adapter.mcp_config_builder import ( # noqa: PLC0415
|
|
565
|
+
MCPConfigBuilder,
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
# Handle authentication override
|
|
569
|
+
if "authentication" in override:
|
|
570
|
+
headers = MCPConfigBuilder.build_headers_from_auth(override["authentication"])
|
|
571
|
+
if headers:
|
|
572
|
+
merged["headers"] = headers
|
|
573
|
+
logger.debug("Applied runtime authentication headers for MCP '%s'", server_name)
|
|
574
|
+
|
|
575
|
+
# Merge other config keys (excluding authentication since we converted it)
|
|
576
|
+
for key, value in override.items():
|
|
577
|
+
if key != "authentication":
|
|
578
|
+
merged[key] = value
|
|
579
|
+
|
|
580
|
+
return merged
|
|
581
|
+
|
|
582
|
+
def _log_event(self, event: dict[str, Any]) -> None:
|
|
583
|
+
"""Log an A2AEvent for verbose debug output.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
event: The A2AEvent dictionary to log.
|
|
587
|
+
"""
|
|
588
|
+
event_type = event.get("event_type", "unknown")
|
|
589
|
+
content = event.get("content", "")
|
|
590
|
+
is_final = event.get("is_final", False)
|
|
591
|
+
|
|
592
|
+
# Truncate long content for readability
|
|
593
|
+
content_str = str(content) if content else ""
|
|
594
|
+
content_preview = content_str[:100] + "..." if len(content_str) > 100 else content_str
|
|
595
|
+
|
|
596
|
+
final_marker = "(final)" if is_final else ""
|
|
597
|
+
logger.info("[%s] %s %s", event_type, final_marker, content_preview)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""MCP adapter module for local agent runtime.
|
|
2
|
+
|
|
3
|
+
This module provides MCP adapters for converting glaip-sdk MCP references
|
|
4
|
+
to backend-specific MCP configuration formats.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from glaip_sdk.runner.mcp_adapter.base_mcp_adapter import BaseMCPAdapter
|
|
11
|
+
from glaip_sdk.runner.mcp_adapter.langchain_mcp_adapter import LangChainMCPAdapter
|
|
12
|
+
|
|
13
|
+
__all__ = ["BaseMCPAdapter", "LangChainMCPAdapter"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Base MCP adapter for local agent runtime.
|
|
2
|
+
|
|
3
|
+
This module defines the abstract base class for MCP adapters.
|
|
4
|
+
Different backends (LangGraph, Google ADK) implement their own adapters.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseMCPAdapter(ABC):
|
|
17
|
+
"""Abstract base class for MCP adapters.
|
|
18
|
+
|
|
19
|
+
One Interface, Multiple Implementations:
|
|
20
|
+
- LangChainMCPAdapter: Adapts to aip-agents mcp_config format
|
|
21
|
+
- GoogleADKMCPAdapter: Adapts to Google ADK format (future)
|
|
22
|
+
|
|
23
|
+
Each backend implements this interface to adapt glaip-sdk MCPs
|
|
24
|
+
to their specific MCP configuration format.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def adapt_mcps(self, mcp_refs: list[Any]) -> dict[str, Any]:
|
|
29
|
+
"""Adapt glaip-sdk MCP references to backend-specific format.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
mcp_refs: List of MCP references from Agent definition.
|
|
33
|
+
Can be: MCP class instances, MCP.from_native() refs, etc.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Backend-specific MCP configuration.
|
|
37
|
+
For LangChain/aip-agents: dict[str, dict[str, Any]] mapping server names to config.
|
|
38
|
+
For Google ADK: dict in Google ADK MCP format.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If MCP is not supported by this backend.
|
|
42
|
+
"""
|
|
43
|
+
...
|