glaip-sdk 0.6.19__py3-none-any.whl → 0.7.27__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 +283 -30
- glaip_sdk/agents/component.py +233 -0
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +1 -1
- glaip_sdk/cli/commands/configure.py +1 -2
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +112 -35
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +3 -1
- glaip_sdk/cli/slash/agent_session.py +1 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +343 -20
- glaip_sdk/cli/slash/tui/__init__.py +29 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
- glaip_sdk/cli/slash/tui/clipboard.py +316 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +1 -1
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +293 -17
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -5
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +109 -30
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +91 -0
- glaip_sdk/hitl/__init__.py +35 -2
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +1 -31
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +47 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/ptc.py +145 -0
- glaip_sdk/registry/tool.py +270 -57
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +4 -1
- glaip_sdk/runner/langgraph.py +251 -27
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
- glaip_sdk/runner/ptc_adapter.py +98 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +427 -49
- glaip_sdk/utils/runtime_config.py +3 -2
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
- glaip_sdk-0.7.27.dist-info/RECORD +227 -0
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
- glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.19.dist-info/RECORD +0 -163
- glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
glaip_sdk/runner/langgraph.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=duplicate-code
|
|
1
2
|
"""LangGraph-based runner for local agent execution.
|
|
2
3
|
|
|
3
4
|
This module provides the LangGraphRunner which executes glaip-sdk agents
|
|
@@ -23,16 +24,20 @@ import logging
|
|
|
23
24
|
from dataclasses import dataclass
|
|
24
25
|
from typing import TYPE_CHECKING, Any
|
|
25
26
|
|
|
26
|
-
from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
|
|
27
27
|
from gllm_core.utils import LoggerManager
|
|
28
28
|
|
|
29
29
|
from glaip_sdk.client.run_rendering import AgentRunRenderingManager
|
|
30
|
-
from glaip_sdk.hitl import
|
|
30
|
+
from glaip_sdk.hitl import PauseResumeCallback
|
|
31
|
+
from glaip_sdk.models import DEFAULT_MODEL
|
|
31
32
|
from glaip_sdk.runner.base import BaseRunner
|
|
32
33
|
from glaip_sdk.runner.deps import (
|
|
33
34
|
check_local_runtime_available,
|
|
34
35
|
get_local_runtime_missing_message,
|
|
35
36
|
)
|
|
37
|
+
from glaip_sdk.runner.ptc_adapter import (
|
|
38
|
+
normalize_ptc_for_aip_agents,
|
|
39
|
+
validate_ptc_for_local_run,
|
|
40
|
+
)
|
|
36
41
|
from glaip_sdk.utils.tool_storage_provider import build_tool_output_manager
|
|
37
42
|
|
|
38
43
|
if TYPE_CHECKING:
|
|
@@ -71,6 +76,10 @@ def _swallow_aip_logs(level: int = logging.ERROR) -> None:
|
|
|
71
76
|
logger = LoggerManager().get_logger(__name__)
|
|
72
77
|
|
|
73
78
|
|
|
79
|
+
# Constants for MCP configuration validation
|
|
80
|
+
_MCP_TRANSPORT_KEYS = {"url", "command", "args", "env", "timeout", "headers"}
|
|
81
|
+
|
|
82
|
+
|
|
74
83
|
def _convert_chat_history_to_messages(
|
|
75
84
|
chat_history: list[dict[str, str]] | None,
|
|
76
85
|
) -> list[BaseMessage]:
|
|
@@ -86,7 +95,11 @@ def _convert_chat_history_to_messages(
|
|
|
86
95
|
if not chat_history:
|
|
87
96
|
return []
|
|
88
97
|
|
|
89
|
-
from langchain_core.messages import
|
|
98
|
+
from langchain_core.messages import ( # noqa: PLC0415
|
|
99
|
+
AIMessage,
|
|
100
|
+
HumanMessage,
|
|
101
|
+
SystemMessage,
|
|
102
|
+
)
|
|
90
103
|
|
|
91
104
|
messages: list[BaseMessage] = []
|
|
92
105
|
for msg in chat_history:
|
|
@@ -121,7 +134,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
121
134
|
Defaults to "gpt-4o-mini".
|
|
122
135
|
"""
|
|
123
136
|
|
|
124
|
-
default_model: str =
|
|
137
|
+
default_model: str = DEFAULT_MODEL
|
|
125
138
|
|
|
126
139
|
def run(
|
|
127
140
|
self,
|
|
@@ -259,7 +272,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
259
272
|
|
|
260
273
|
# Build the local LangGraphReactAgent from the glaip_sdk Agent
|
|
261
274
|
local_agent = self.build_langgraph_agent(
|
|
262
|
-
agent,
|
|
275
|
+
agent,
|
|
276
|
+
runtime_config=runtime_config,
|
|
277
|
+
pause_resume_callback=pause_resume_callback,
|
|
263
278
|
)
|
|
264
279
|
|
|
265
280
|
# Convert chat history to LangChain messages for the agent
|
|
@@ -305,12 +320,26 @@ class LangGraphRunner(BaseRunner):
|
|
|
305
320
|
renderer.close()
|
|
306
321
|
finally:
|
|
307
322
|
raise
|
|
323
|
+
finally:
|
|
324
|
+
# Cleanup PTC sandbox and MCP sessions
|
|
325
|
+
# Isolated cleanup steps so one failure doesn't skip the other
|
|
326
|
+
try:
|
|
327
|
+
await local_agent.cleanup()
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.warning("Failed to cleanup agent resources: %s", e)
|
|
308
330
|
|
|
309
331
|
# Use shared finalizer to avoid code duplication
|
|
310
|
-
from glaip_sdk.client.run_rendering import
|
|
332
|
+
from glaip_sdk.client.run_rendering import ( # noqa: PLC0415
|
|
333
|
+
finalize_render_manager,
|
|
334
|
+
)
|
|
311
335
|
|
|
312
336
|
return finalize_render_manager(
|
|
313
|
-
render_manager,
|
|
337
|
+
render_manager,
|
|
338
|
+
renderer,
|
|
339
|
+
final_text,
|
|
340
|
+
stats_usage,
|
|
341
|
+
started_monotonic,
|
|
342
|
+
finished_monotonic,
|
|
314
343
|
)
|
|
315
344
|
|
|
316
345
|
def build_langgraph_agent(
|
|
@@ -365,6 +394,17 @@ class LangGraphRunner(BaseRunner):
|
|
|
365
394
|
merged_agent_config = self._merge_agent_config(agent, normalized_config)
|
|
366
395
|
agent_config_params, agent_config_kwargs = self._apply_agent_config(merged_agent_config)
|
|
367
396
|
|
|
397
|
+
# Validate and normalize PTC configuration for local runs
|
|
398
|
+
ptc_config = validate_ptc_for_local_run(
|
|
399
|
+
agent_ptc=agent.ptc if hasattr(agent, "ptc") else None,
|
|
400
|
+
agent_config_ptc=None, # Already validated in _merge_agent_config
|
|
401
|
+
runtime_config_ptc=None, # Already validated in _normalize_runtime_config
|
|
402
|
+
)
|
|
403
|
+
normalized_ptc = normalize_ptc_for_aip_agents(ptc_config)
|
|
404
|
+
|
|
405
|
+
# Resolve model and merge its configuration into agent kwargs
|
|
406
|
+
model_string = self._resolve_local_model(agent, agent_config_kwargs)
|
|
407
|
+
|
|
368
408
|
tool_output_manager = self._resolve_tool_output_manager(
|
|
369
409
|
agent,
|
|
370
410
|
merged_agent_config,
|
|
@@ -378,16 +418,18 @@ class LangGraphRunner(BaseRunner):
|
|
|
378
418
|
shared_tool_output_manager=tool_output_manager,
|
|
379
419
|
)
|
|
380
420
|
|
|
381
|
-
# Build the LangGraphReactAgent with tools, sub-agents, and
|
|
421
|
+
# Build the LangGraphReactAgent with tools, sub-agents, configs, and PTC
|
|
382
422
|
local_agent = LangGraphReactAgent(
|
|
383
423
|
name=agent.name,
|
|
384
424
|
instruction=agent.instruction,
|
|
385
425
|
description=agent.description,
|
|
386
|
-
model=
|
|
426
|
+
model=model_string,
|
|
387
427
|
tools=langchain_tools,
|
|
388
428
|
agents=sub_agent_instances if sub_agent_instances else None,
|
|
389
429
|
tool_configs=tool_configs if tool_configs else None,
|
|
390
430
|
tool_output_manager=tool_output_manager,
|
|
431
|
+
guardrail=agent.guardrail,
|
|
432
|
+
ptc_config=normalized_ptc,
|
|
391
433
|
**agent_config_params,
|
|
392
434
|
**agent_config_kwargs,
|
|
393
435
|
)
|
|
@@ -434,13 +476,22 @@ class LangGraphRunner(BaseRunner):
|
|
|
434
476
|
hitl_enabled = merged_agent_config.get("hitl_enabled", False)
|
|
435
477
|
if hitl_enabled:
|
|
436
478
|
try:
|
|
479
|
+
from aip_agents.agent.hitl.manager import ( # noqa: PLC0415
|
|
480
|
+
ApprovalManager,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
from glaip_sdk.hitl import LocalPromptHandler # noqa: PLC0415
|
|
484
|
+
|
|
437
485
|
local_agent.hitl_manager = ApprovalManager(
|
|
438
486
|
prompt_handler=LocalPromptHandler(pause_resume_callback=pause_resume_callback)
|
|
439
487
|
)
|
|
440
488
|
# Store callback reference for setting renderer later
|
|
441
489
|
if pause_resume_callback:
|
|
442
490
|
local_agent._pause_resume_callback = pause_resume_callback
|
|
443
|
-
logger.debug(
|
|
491
|
+
logger.debug(
|
|
492
|
+
"HITL manager injected for agent '%s' (hitl_enabled=True)",
|
|
493
|
+
agent_name,
|
|
494
|
+
)
|
|
444
495
|
except ImportError as e:
|
|
445
496
|
# Missing dependencies - fail fast
|
|
446
497
|
raise ImportError("Local HITL requires aip_agents. Install with: pip install 'glaip-sdk[local]'") from e
|
|
@@ -448,7 +499,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
448
499
|
# Other errors during HITL setup - fail fast
|
|
449
500
|
raise RuntimeError(f"Failed to initialize HITL manager for agent '{agent_name}'") from e
|
|
450
501
|
else:
|
|
451
|
-
logger.debug(
|
|
502
|
+
logger.debug(
|
|
503
|
+
"HITL manager not injected for agent '%s' (hitl_enabled=False)",
|
|
504
|
+
agent_name,
|
|
505
|
+
)
|
|
452
506
|
|
|
453
507
|
def _build_sub_agents(
|
|
454
508
|
self,
|
|
@@ -543,6 +597,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
543
597
|
if not runtime_config:
|
|
544
598
|
return {}
|
|
545
599
|
|
|
600
|
+
# Check for unsupported runtime_config.ptc (v1 constraint)
|
|
601
|
+
if "ptc" in runtime_config:
|
|
602
|
+
validate_ptc_for_local_run(
|
|
603
|
+
agent_ptc=None,
|
|
604
|
+
agent_config_ptc=None,
|
|
605
|
+
runtime_config_ptc=runtime_config["ptc"],
|
|
606
|
+
)
|
|
607
|
+
|
|
546
608
|
# 1. Extract global configs and normalize keys
|
|
547
609
|
global_tool_configs = normalize_local_config_keys(runtime_config.get("tool_configs", {}))
|
|
548
610
|
global_mcp_configs = normalize_local_config_keys(runtime_config.get("mcp_configs", {}))
|
|
@@ -702,6 +764,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
702
764
|
# Get runtime agent_config
|
|
703
765
|
runtime_agent_config = normalized_config.get("agent_config", {})
|
|
704
766
|
|
|
767
|
+
# Check for unsupported agent_config.ptc (local runs constraint)
|
|
768
|
+
if "ptc" in agent_agent_config or "ptc" in runtime_agent_config:
|
|
769
|
+
validate_ptc_for_local_run(
|
|
770
|
+
agent_ptc=None,
|
|
771
|
+
agent_config_ptc=agent_agent_config.get("ptc") or runtime_agent_config.get("ptc"),
|
|
772
|
+
runtime_config_ptc=None,
|
|
773
|
+
)
|
|
774
|
+
|
|
705
775
|
# Merge: agent definition < runtime config
|
|
706
776
|
return merge_configs(agent_agent_config, runtime_agent_config)
|
|
707
777
|
|
|
@@ -724,6 +794,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
724
794
|
"""
|
|
725
795
|
direct_params = {}
|
|
726
796
|
kwargs_params = {}
|
|
797
|
+
config_dict = {}
|
|
727
798
|
|
|
728
799
|
# Direct constructor parameters
|
|
729
800
|
if "planning" in agent_config:
|
|
@@ -735,6 +806,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
735
806
|
# Kwargs parameters (passed through **kwargs to BaseAgent)
|
|
736
807
|
if "enable_pii" in agent_config:
|
|
737
808
|
kwargs_params["enable_pii"] = agent_config["enable_pii"]
|
|
809
|
+
config_dict["enable_pii"] = agent_config["enable_pii"]
|
|
738
810
|
|
|
739
811
|
if "memory" in agent_config:
|
|
740
812
|
# Map "memory" to "memory_backend" for aip-agents compatibility
|
|
@@ -746,8 +818,73 @@ class LangGraphRunner(BaseRunner):
|
|
|
746
818
|
if key in agent_config:
|
|
747
819
|
kwargs_params[key] = agent_config[key]
|
|
748
820
|
|
|
821
|
+
# Ensure we pass a config dictionary to BaseAgent, which uses it for
|
|
822
|
+
# LM configuration (api keys, etc.). Memory settings are passed only
|
|
823
|
+
# via kwargs to avoid leaking into LM invoker config.
|
|
824
|
+
if config_dict:
|
|
825
|
+
kwargs_params["config"] = config_dict
|
|
826
|
+
|
|
749
827
|
return direct_params, kwargs_params
|
|
750
828
|
|
|
829
|
+
def _convert_model_for_local(self, model: Any) -> tuple[str, dict[str, Any]]:
|
|
830
|
+
"""Convert model to aip_agents format for local execution.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
model: Model object or string identifier.
|
|
834
|
+
|
|
835
|
+
Returns:
|
|
836
|
+
Tuple of (model_string, config_dict).
|
|
837
|
+
"""
|
|
838
|
+
from glaip_sdk.models._validation import ( # noqa: PLC0415
|
|
839
|
+
convert_model_for_local_execution,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
return convert_model_for_local_execution(model)
|
|
843
|
+
|
|
844
|
+
def _resolve_local_model(self, agent: Agent, agent_config_kwargs: dict[str, Any]) -> str:
|
|
845
|
+
"""Resolve model string and merge its configuration into agent kwargs.
|
|
846
|
+
|
|
847
|
+
This method extracts model-specific credentials and hyperparameters from a Model
|
|
848
|
+
object and merges them into the 'config' dictionary within agent_config_kwargs.
|
|
849
|
+
This is required because BaseAgent expects LM settings (api keys, etc.) to be
|
|
850
|
+
inside the 'config' parameter, not top-level kwargs.
|
|
851
|
+
|
|
852
|
+
Example:
|
|
853
|
+
If agent has:
|
|
854
|
+
- model = Model(id="deepinfra/model", credentials="key-123")
|
|
855
|
+
- agent_config_kwargs = {"enable_pii": True, "config": {"enable_pii": True}}
|
|
856
|
+
|
|
857
|
+
_resolve_local_model will:
|
|
858
|
+
1. Resolve model_string to "openai-compatible/model"
|
|
859
|
+
2. Extract model_config as {"lm_api_key": "key-123"}
|
|
860
|
+
3. Update agent_config_kwargs["config"] to:
|
|
861
|
+
{"enable_pii": True, "lm_api_key": "key-123"}
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
agent: The glaip_sdk Agent.
|
|
865
|
+
agent_config_kwargs: Agent config kwargs to update (modified in-place).
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
The model identifier string for local execution.
|
|
869
|
+
"""
|
|
870
|
+
model_to_use = agent.model or self.default_model
|
|
871
|
+
model_string, model_config = self._convert_model_for_local(model_to_use)
|
|
872
|
+
|
|
873
|
+
if model_config:
|
|
874
|
+
# Normalize config to a dict early to simplify merging
|
|
875
|
+
config_val = agent_config_kwargs.get("config", {})
|
|
876
|
+
if hasattr(config_val, "model_dump"):
|
|
877
|
+
config_val = config_val.model_dump()
|
|
878
|
+
|
|
879
|
+
if not isinstance(config_val, dict):
|
|
880
|
+
config_val = {}
|
|
881
|
+
|
|
882
|
+
# Use a single merge path for model configuration
|
|
883
|
+
config_val.update(model_config)
|
|
884
|
+
agent_config_kwargs["config"] = config_val
|
|
885
|
+
|
|
886
|
+
return model_string
|
|
887
|
+
|
|
751
888
|
def _apply_runtime_mcp_configs(
|
|
752
889
|
self,
|
|
753
890
|
base_configs: dict[str, Any],
|
|
@@ -776,39 +913,126 @@ class LangGraphRunner(BaseRunner):
|
|
|
776
913
|
base_config: dict[str, Any],
|
|
777
914
|
override: dict[str, Any] | None,
|
|
778
915
|
) -> dict[str, Any]:
|
|
779
|
-
"""Merge a single MCP config with runtime override.
|
|
916
|
+
"""Merge a single MCP config with a runtime override, handling normalization and parity fixes.
|
|
917
|
+
|
|
918
|
+
This method orchestrates the merging of base MCP settings (from the object definition)
|
|
919
|
+
with runtime overrides. It enforces Platform parity by prioritizing the nested 'config'
|
|
920
|
+
block while maintaining robustness for local development by auto-fixing flat transport keys.
|
|
921
|
+
|
|
922
|
+
The merge follows these priority rules (highest to lowest):
|
|
923
|
+
1. Misplaced flat keys in the override (e.g., 'url' at top level) - Auto-fixed with warning.
|
|
924
|
+
2. Nested 'config' block in the override (Matches Platform/Constructor schema).
|
|
925
|
+
3. Authentication objects in the override (Converted to HTTP headers).
|
|
926
|
+
4. Structural settings in the override (e.g., 'allowed_tools').
|
|
927
|
+
5. Base configuration from the MCP object definition.
|
|
928
|
+
|
|
929
|
+
Examples:
|
|
930
|
+
>>> # 1. Strict Nested Style (Recommended)
|
|
931
|
+
>>> override = {"config": {"url": "https://new.api"}, "allowed_tools": ["t1"]}
|
|
932
|
+
>>> self._merge_single_mcp_config("mcp", base, override)
|
|
933
|
+
>>> # Result: {"url": "https://new.api", "allowed_tools": ["t1"], ...}
|
|
934
|
+
|
|
935
|
+
>>> # 2. Flat Legacy Style (Auto-fixed with warning)
|
|
936
|
+
>>> override = {"url": "https://new.api"}
|
|
937
|
+
>>> self._merge_single_mcp_config("mcp", base, override)
|
|
938
|
+
>>> # Result: {"url": "https://new.api", ...}
|
|
939
|
+
|
|
940
|
+
>>> # 3. Header Merging (Preserves Auth)
|
|
941
|
+
>>> base = {"headers": {"Authorization": "Bearer token"}}
|
|
942
|
+
>>> override = {"headers": {"X-Custom": "val"}}
|
|
943
|
+
>>> self._merge_single_mcp_config("mcp", base, override)
|
|
944
|
+
>>> # Result: {"headers": {"Authorization": "Bearer token", "X-Custom": "val"}, ...}
|
|
780
945
|
|
|
781
946
|
Args:
|
|
782
|
-
server_name: Name of the MCP server.
|
|
783
|
-
base_config: Base
|
|
784
|
-
override: Optional runtime
|
|
947
|
+
server_name: Name of the MCP server being configured.
|
|
948
|
+
base_config: Base configuration dictionary derived from the MCP object.
|
|
949
|
+
override: Optional dictionary of runtime overrides.
|
|
785
950
|
|
|
786
951
|
Returns:
|
|
787
|
-
|
|
952
|
+
A fully merged and normalized configuration dictionary ready for the local runner.
|
|
788
953
|
"""
|
|
789
954
|
merged = base_config.copy()
|
|
790
955
|
|
|
791
956
|
if not override:
|
|
792
957
|
return merged
|
|
793
958
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
959
|
+
# 1. Check for misplaced keys and warn (DX/Parity guidance)
|
|
960
|
+
self._warn_if_mcp_override_misplaced(server_name, override)
|
|
961
|
+
|
|
962
|
+
# 2. Apply Authentication (Converted to headers)
|
|
963
|
+
self._apply_mcp_auth_override(server_name, merged, override)
|
|
964
|
+
|
|
965
|
+
# 3. Apply Transport Settings (Nested 'config')
|
|
966
|
+
if "config" in override and isinstance(override["config"], dict):
|
|
967
|
+
merged.update(override["config"])
|
|
797
968
|
|
|
798
|
-
#
|
|
799
|
-
if "
|
|
800
|
-
|
|
801
|
-
if headers:
|
|
802
|
-
merged["headers"] = headers
|
|
803
|
-
logger.debug("Applied runtime authentication headers for MCP '%s'", server_name)
|
|
969
|
+
# 4. Apply Structural Settings (e.g., allowed_tools)
|
|
970
|
+
if "allowed_tools" in override:
|
|
971
|
+
merged["allowed_tools"] = override["allowed_tools"]
|
|
804
972
|
|
|
805
|
-
#
|
|
973
|
+
# 5. Preserve unknown top-level keys (backward compatibility)
|
|
974
|
+
known_keys = _MCP_TRANSPORT_KEYS | {"config", "authentication", "allowed_tools"}
|
|
806
975
|
for key, value in override.items():
|
|
807
|
-
if key
|
|
976
|
+
if key not in known_keys:
|
|
808
977
|
merged[key] = value
|
|
809
978
|
|
|
979
|
+
# 6. Apply Auto-fix for misplaced keys (Local Success)
|
|
980
|
+
for key in [k for k in override if k in _MCP_TRANSPORT_KEYS]:
|
|
981
|
+
val = override[key]
|
|
982
|
+
# Special case: Merge headers instead of overwriting to preserve auth
|
|
983
|
+
if key == "headers" and isinstance(val, dict) and isinstance(merged.get("headers"), dict):
|
|
984
|
+
merged["headers"].update(val)
|
|
985
|
+
else:
|
|
986
|
+
merged[key] = val
|
|
987
|
+
|
|
810
988
|
return merged
|
|
811
989
|
|
|
990
|
+
def _warn_if_mcp_override_misplaced(self, server_name: str, override: dict[str, Any]) -> None:
|
|
991
|
+
"""Log a warning if transport keys are found at the top level of an override.
|
|
992
|
+
|
|
993
|
+
Args:
|
|
994
|
+
server_name: Name of the MCP server.
|
|
995
|
+
override: The raw override dictionary.
|
|
996
|
+
"""
|
|
997
|
+
misplaced = [k for k in override if k in _MCP_TRANSPORT_KEYS]
|
|
998
|
+
if misplaced:
|
|
999
|
+
logger.warning(
|
|
1000
|
+
"MCP '%s' override contains transport keys at the top level: %s. "
|
|
1001
|
+
"This structure is inconsistent with the Platform and MCP constructor. "
|
|
1002
|
+
"Transport settings should be nested within a 'config' dictionary. "
|
|
1003
|
+
"Example: mcp_configs={'%s': {'config': {'%s': '...'}}}. "
|
|
1004
|
+
"Automatically merging top-level keys for local execution parity.",
|
|
1005
|
+
server_name,
|
|
1006
|
+
misplaced,
|
|
1007
|
+
server_name,
|
|
1008
|
+
misplaced[0],
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
def _apply_mcp_auth_override(
|
|
1012
|
+
self,
|
|
1013
|
+
server_name: str,
|
|
1014
|
+
merged_config: dict[str, Any],
|
|
1015
|
+
override: dict[str, Any],
|
|
1016
|
+
) -> None:
|
|
1017
|
+
"""Convert authentication override to headers and apply to config.
|
|
1018
|
+
|
|
1019
|
+
Args:
|
|
1020
|
+
server_name: Name of the MCP server.
|
|
1021
|
+
merged_config: The configuration being built (mutated in place).
|
|
1022
|
+
override: The raw override dictionary.
|
|
1023
|
+
"""
|
|
1024
|
+
if "authentication" not in override:
|
|
1025
|
+
return
|
|
1026
|
+
|
|
1027
|
+
from glaip_sdk.runner.mcp_adapter.mcp_config_builder import ( # noqa: PLC0415
|
|
1028
|
+
MCPConfigBuilder,
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
headers = MCPConfigBuilder.build_headers_from_auth(override["authentication"])
|
|
1032
|
+
if headers:
|
|
1033
|
+
merged_config["headers"] = headers
|
|
1034
|
+
logger.debug("Applied runtime authentication headers for MCP '%s'", server_name)
|
|
1035
|
+
|
|
812
1036
|
def _validate_sub_agent_for_local_mode(self, sub_agent: Any) -> None:
|
|
813
1037
|
"""Validate that a sub-agent reference is supported for local execution.
|
|
814
1038
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Logging configuration for CLI to suppress noisy dependency warnings.
|
|
2
|
+
|
|
3
|
+
This module provides centralized logging suppression for optional dependencies
|
|
4
|
+
that emit noisy warnings during CLI usage. Warnings are suppressed by default
|
|
5
|
+
but can be shown using GLAIP_LOG_LEVEL=DEBUG.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
NOISY_LOGGERS = ["transformers", "gllm_privacy", "google.cloud.aiplatform"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NameFilter(logging.Filter):
|
|
19
|
+
"""Filter logs by logger name prefix."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, prefixes: list[str]) -> None:
|
|
22
|
+
"""Initialize filter with logger name prefixes to suppress.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
prefixes: List of logger name prefixes to filter out.
|
|
26
|
+
"""
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.prefixes = prefixes
|
|
29
|
+
|
|
30
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
31
|
+
"""Filter log records by name prefix.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
record: Log record to filter.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
False if record should be suppressed, True otherwise.
|
|
38
|
+
"""
|
|
39
|
+
return not any(record.name.startswith(p) for p in self.prefixes)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def setup_cli_logging() -> None:
|
|
43
|
+
"""Suppress INFO from noisy third-party libraries.
|
|
44
|
+
|
|
45
|
+
Use GLAIP_LOG_LEVEL=DEBUG to see all warnings.
|
|
46
|
+
This function is idempotent - calling it multiple times is safe.
|
|
47
|
+
"""
|
|
48
|
+
# Check env level FIRST before any suppression
|
|
49
|
+
env_level = os.getenv("GLAIP_LOG_LEVEL", "").upper()
|
|
50
|
+
is_debug = env_level == "DEBUG"
|
|
51
|
+
|
|
52
|
+
if is_debug:
|
|
53
|
+
# Debug mode: show everything, no suppression
|
|
54
|
+
if env_level and hasattr(logging, env_level):
|
|
55
|
+
logging.basicConfig(level=getattr(logging, env_level))
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Default mode: suppress noisy warnings
|
|
59
|
+
if env_level and hasattr(logging, env_level):
|
|
60
|
+
logging.basicConfig(level=getattr(logging, env_level))
|
|
61
|
+
|
|
62
|
+
# Add handler filter to suppress by name prefix (handles child loggers)
|
|
63
|
+
# Check if filter already exists to ensure idempotency
|
|
64
|
+
root_logger = logging.getLogger()
|
|
65
|
+
has_name_filter = any(isinstance(f, NameFilter) for h in root_logger.handlers for f in h.filters)
|
|
66
|
+
|
|
67
|
+
if not has_name_filter:
|
|
68
|
+
handler = logging.StreamHandler()
|
|
69
|
+
handler.addFilter(NameFilter(NOISY_LOGGERS))
|
|
70
|
+
root_logger.addHandler(handler)
|
|
71
|
+
|
|
72
|
+
# Suppress FutureWarning for GCS (idempotent - multiple calls are safe)
|
|
73
|
+
warnings.filterwarnings(
|
|
74
|
+
"ignore",
|
|
75
|
+
category=FutureWarning,
|
|
76
|
+
message=r".*google-cloud-storage.*",
|
|
77
|
+
)
|
|
@@ -60,19 +60,42 @@ class MCPConfigBuilder:
|
|
|
60
60
|
def _handle_custom_header(authentication: dict[str, Any]) -> dict[str, str] | None:
|
|
61
61
|
"""Handle custom-header auth type."""
|
|
62
62
|
headers = authentication.get("headers")
|
|
63
|
-
if isinstance(headers, dict)
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if isinstance(headers, dict):
|
|
64
|
+
cleaned_headers = MCPConfigBuilder._clean_headers(headers)
|
|
65
|
+
if cleaned_headers:
|
|
66
|
+
return cleaned_headers
|
|
67
|
+
|
|
68
|
+
logger.warning("custom-header auth requires 'headers' dict with at least one non-null key/value")
|
|
66
69
|
return None
|
|
67
70
|
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _clean_headers(headers: dict[str, Any]) -> dict[str, str] | None:
|
|
73
|
+
"""Clean header dict by filtering None keys/values and stringifying values.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
headers: Raw headers dict potentially containing None or non-string values.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Cleaned headers dict with string keys/values, or None if empty after cleaning.
|
|
80
|
+
"""
|
|
81
|
+
cleaned: dict[str, str] = {}
|
|
82
|
+
for key, value in headers.items():
|
|
83
|
+
if key is None:
|
|
84
|
+
logger.warning("Dropping header with null key")
|
|
85
|
+
continue
|
|
86
|
+
if value is None:
|
|
87
|
+
logger.warning("Dropping header '%s' with null value", key)
|
|
88
|
+
continue
|
|
89
|
+
cleaned[str(key)] = str(value)
|
|
90
|
+
|
|
91
|
+
return cleaned if cleaned else None
|
|
92
|
+
|
|
68
93
|
@staticmethod
|
|
69
94
|
def _handle_bearer_token(authentication: dict[str, Any]) -> dict[str, str] | None:
|
|
70
95
|
"""Handle bearer-token auth type."""
|
|
71
|
-
# Check if headers provided directly
|
|
72
96
|
headers = authentication.get("headers")
|
|
73
97
|
if isinstance(headers, dict):
|
|
74
|
-
return headers
|
|
75
|
-
# Otherwise build from token
|
|
98
|
+
return MCPConfigBuilder._clean_headers(headers)
|
|
76
99
|
token = authentication.get("token")
|
|
77
100
|
if token:
|
|
78
101
|
return {"Authorization": f"Bearer {token}"}
|
|
@@ -82,11 +105,9 @@ class MCPConfigBuilder:
|
|
|
82
105
|
@staticmethod
|
|
83
106
|
def _handle_api_key(authentication: dict[str, Any]) -> dict[str, str] | None:
|
|
84
107
|
"""Handle api-key auth type."""
|
|
85
|
-
# Check if headers provided directly
|
|
86
108
|
headers = authentication.get("headers")
|
|
87
109
|
if isinstance(headers, dict):
|
|
88
|
-
return headers
|
|
89
|
-
# Otherwise build from key/value
|
|
110
|
+
return MCPConfigBuilder._clean_headers(headers)
|
|
90
111
|
key = authentication.get("key")
|
|
91
112
|
value = authentication.get("value")
|
|
92
113
|
if key and value:
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""PTC adapter for local runner integration.
|
|
2
|
+
|
|
3
|
+
This module provides validation and normalization of PTC configuration
|
|
4
|
+
for use in the local LangGraph runner. It ensures PTC is configured
|
|
5
|
+
correctly and rejects unsupported configuration sources.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.exceptions import ValidationError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from glaip_sdk.ptc import PTC
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def validate_ptc_for_local_run(
|
|
22
|
+
agent_ptc: PTC | None,
|
|
23
|
+
agent_config_ptc: Any | None,
|
|
24
|
+
runtime_config_ptc: Any | None,
|
|
25
|
+
) -> PTC | None:
|
|
26
|
+
"""Validate PTC configuration for local runs.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
agent_ptc: PTC object from Agent.ptc parameter.
|
|
30
|
+
agent_config_ptc: PTC from agent_config (should be None for local).
|
|
31
|
+
runtime_config_ptc: PTC from runtime_config (should be None for v1).
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Validated PTC object if enabled, None otherwise.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValidationError: If agent_config.ptc or runtime_config.ptc are provided,
|
|
38
|
+
or if agent_ptc is not a PTC instance when provided.
|
|
39
|
+
"""
|
|
40
|
+
if agent_config_ptc is not None:
|
|
41
|
+
msg = (
|
|
42
|
+
"PTC configuration via agent_config.ptc is not supported for local runs. "
|
|
43
|
+
"Please configure PTC using the Agent.ptc parameter instead: "
|
|
44
|
+
"Agent(name='...', ptc=PTC(enabled=True), ...)"
|
|
45
|
+
)
|
|
46
|
+
raise ValidationError(msg)
|
|
47
|
+
|
|
48
|
+
if runtime_config_ptc is not None:
|
|
49
|
+
msg = (
|
|
50
|
+
"PTC configuration via runtime_config.ptc is not supported in v1. "
|
|
51
|
+
"PTC configuration must be set at Agent initialization time using "
|
|
52
|
+
"the Agent.ptc parameter and cannot be overridden at runtime. "
|
|
53
|
+
"This preserves local/remote parity and prevents ambiguous precedence."
|
|
54
|
+
)
|
|
55
|
+
raise ValidationError(msg)
|
|
56
|
+
|
|
57
|
+
if agent_ptc is None:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
from glaip_sdk.ptc import PTC # noqa: PLC0415
|
|
61
|
+
|
|
62
|
+
if not isinstance(agent_ptc, PTC):
|
|
63
|
+
msg = (
|
|
64
|
+
f"Agent.ptc must be a PTC instance, got {type(agent_ptc).__name__}. "
|
|
65
|
+
"Example: Agent(name='...', ptc=PTC(enabled=True), ...)"
|
|
66
|
+
)
|
|
67
|
+
raise ValidationError(msg)
|
|
68
|
+
|
|
69
|
+
if not agent_ptc.enabled:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
return agent_ptc
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def normalize_ptc_for_aip_agents(ptc: PTC | None) -> Any:
|
|
76
|
+
"""Normalize PTC config for aip-agents LangGraphReactAgent.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
ptc: Validated PTC object or None.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
PTCSandboxConfig for aip-agents or None if PTC disabled.
|
|
83
|
+
"""
|
|
84
|
+
if ptc is None or not ptc.enabled:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
from aip_agents.ptc import PromptConfig, PTCSandboxConfig # noqa: PLC0415
|
|
88
|
+
|
|
89
|
+
# Build PromptConfig if prompt dict is provided.
|
|
90
|
+
prompt_config = None
|
|
91
|
+
if ptc.prompt is not None:
|
|
92
|
+
prompt_config = PromptConfig(**ptc.prompt)
|
|
93
|
+
|
|
94
|
+
return PTCSandboxConfig(
|
|
95
|
+
enabled=ptc.enabled,
|
|
96
|
+
sandbox_timeout=ptc.sandbox_timeout,
|
|
97
|
+
prompt=prompt_config,
|
|
98
|
+
)
|