glaip-sdk 0.6.5b6__py3-none-any.whl → 0.7.12__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/__init__.py +42 -5
- glaip_sdk/agents/base.py +217 -42
- 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 +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -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 +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- 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/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +369 -23
- glaip_sdk/cli/slash/tui/__init__.py +26 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
- glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +87 -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 +160 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
- 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 +374 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- 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 +50 -8
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -1
- 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} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +17 -0
- glaip_sdk/models/agent_runs.py +2 -1
- 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/registry/tool.py +273 -59
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +5 -8
- glaip_sdk/runner/langgraph.py +318 -42
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
- 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/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +15 -12
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
- glaip_sdk-0.7.12.dist-info/RECORD +219 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.12.dist-info/top_level.txt +1 -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.5b6.dist-info/RECORD +0 -159
- glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
glaip_sdk/runner/__init__.py
CHANGED
|
@@ -19,18 +19,19 @@ Example:
|
|
|
19
19
|
>>> result = runner.run(agent, "Hello!")
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
22
24
|
from glaip_sdk.runner.deps import (
|
|
23
25
|
LOCAL_RUNTIME_AVAILABLE,
|
|
24
26
|
check_local_runtime_available,
|
|
25
27
|
get_local_runtime_missing_message,
|
|
26
28
|
)
|
|
27
|
-
from glaip_sdk.runner.langgraph import LangGraphRunner
|
|
28
29
|
|
|
29
30
|
# Default runner instance
|
|
30
|
-
_default_runner:
|
|
31
|
+
_default_runner: Any | None = None
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def get_default_runner() ->
|
|
34
|
+
def get_default_runner() -> Any:
|
|
34
35
|
"""Get the default runner instance for local agent execution.
|
|
35
36
|
|
|
36
37
|
Returns:
|
|
@@ -45,11 +46,17 @@ def get_default_runner() -> LangGraphRunner:
|
|
|
45
46
|
raise RuntimeError(get_local_runtime_missing_message())
|
|
46
47
|
|
|
47
48
|
if _default_runner is None:
|
|
49
|
+
# Lazy import to avoid requiring aip-agents when runner is not used
|
|
50
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner # noqa: PLC0415
|
|
51
|
+
|
|
48
52
|
_default_runner = LangGraphRunner()
|
|
49
53
|
|
|
50
54
|
return _default_runner
|
|
51
55
|
|
|
52
56
|
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner
|
|
59
|
+
|
|
53
60
|
__all__ = [
|
|
54
61
|
"LOCAL_RUNTIME_AVAILABLE",
|
|
55
62
|
"LangGraphRunner",
|
|
@@ -57,3 +64,13 @@ __all__ = [
|
|
|
57
64
|
"get_default_runner",
|
|
58
65
|
"get_local_runtime_missing_message",
|
|
59
66
|
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def __getattr__(name: str) -> Any:
|
|
70
|
+
"""Lazy import for LangGraphRunner to avoid requiring aip-agents when not used."""
|
|
71
|
+
if name == "LangGraphRunner":
|
|
72
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner # noqa: PLC0415
|
|
73
|
+
|
|
74
|
+
globals()["LangGraphRunner"] = LangGraphRunner
|
|
75
|
+
return LangGraphRunner
|
|
76
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
glaip_sdk/runner/deps.py
CHANGED
|
@@ -15,6 +15,8 @@ Example:
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import importlib.util
|
|
19
|
+
|
|
18
20
|
from gllm_core.utils import LoggerManager
|
|
19
21
|
|
|
20
22
|
logger = LoggerManager().get_logger(__name__)
|
|
@@ -24,17 +26,12 @@ _local_runtime_available: bool | None = None
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def _probe_aip_agents_import() -> bool:
|
|
27
|
-
"""
|
|
29
|
+
"""Check if aip_agents is available without importing it.
|
|
28
30
|
|
|
29
31
|
Returns:
|
|
30
|
-
True if aip_agents
|
|
32
|
+
True if aip_agents appears importable, False otherwise.
|
|
31
33
|
"""
|
|
32
|
-
|
|
33
|
-
import aip_agents # noqa: F401, PLC0415
|
|
34
|
-
|
|
35
|
-
return True
|
|
36
|
-
except ImportError:
|
|
37
|
-
return False
|
|
34
|
+
return importlib.util.find_spec("aip_agents") is not None
|
|
38
35
|
|
|
39
36
|
|
|
40
37
|
def check_local_runtime_available() -> bool:
|
glaip_sdk/runner/langgraph.py
CHANGED
|
@@ -18,33 +18,102 @@ Example:
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import asyncio
|
|
21
|
+
import inspect
|
|
22
|
+
import logging
|
|
21
23
|
from dataclasses import dataclass
|
|
22
24
|
from typing import TYPE_CHECKING, Any
|
|
23
25
|
|
|
26
|
+
from gllm_core.utils import LoggerManager
|
|
27
|
+
|
|
28
|
+
from glaip_sdk.client.run_rendering import AgentRunRenderingManager
|
|
29
|
+
from glaip_sdk.hitl import PauseResumeCallback
|
|
24
30
|
from glaip_sdk.runner.base import BaseRunner
|
|
25
31
|
from glaip_sdk.runner.deps import (
|
|
26
32
|
check_local_runtime_available,
|
|
27
33
|
get_local_runtime_missing_message,
|
|
28
34
|
)
|
|
29
|
-
from glaip_sdk.utils.
|
|
30
|
-
from gllm_core.utils import LoggerManager
|
|
35
|
+
from glaip_sdk.utils.tool_storage_provider import build_tool_output_manager
|
|
31
36
|
|
|
32
37
|
if TYPE_CHECKING:
|
|
38
|
+
from langchain_core.messages import BaseMessage
|
|
39
|
+
|
|
33
40
|
from glaip_sdk.agents.base import Agent
|
|
34
41
|
|
|
42
|
+
|
|
43
|
+
_AIP_LOGS_SWALLOWED = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _swallow_aip_logs(level: int = logging.ERROR) -> None:
|
|
47
|
+
"""Consume noisy AIPAgents logs once (opt-in via runner flag)."""
|
|
48
|
+
global _AIP_LOGS_SWALLOWED
|
|
49
|
+
if _AIP_LOGS_SWALLOWED:
|
|
50
|
+
return
|
|
51
|
+
prefixes = ("aip_agents.",)
|
|
52
|
+
|
|
53
|
+
def _silence(name: str) -> None:
|
|
54
|
+
lg = logging.getLogger(name)
|
|
55
|
+
lg.handlers = [logging.NullHandler()]
|
|
56
|
+
lg.propagate = False
|
|
57
|
+
lg.setLevel(level)
|
|
58
|
+
|
|
59
|
+
# Silence any already-registered loggers under the given prefixes
|
|
60
|
+
for logger_name in logging.root.manager.loggerDict:
|
|
61
|
+
if any(logger_name.startswith(prefix) for prefix in prefixes):
|
|
62
|
+
_silence(logger_name)
|
|
63
|
+
|
|
64
|
+
# Also set the base prefix loggers so future children inherit silence
|
|
65
|
+
for prefix in prefixes:
|
|
66
|
+
_silence(prefix.rstrip("."))
|
|
67
|
+
_AIP_LOGS_SWALLOWED = True
|
|
68
|
+
|
|
69
|
+
|
|
35
70
|
logger = LoggerManager().get_logger(__name__)
|
|
36
71
|
|
|
37
|
-
|
|
38
|
-
|
|
72
|
+
|
|
73
|
+
def _convert_chat_history_to_messages(
|
|
74
|
+
chat_history: list[dict[str, str]] | None,
|
|
75
|
+
) -> list[BaseMessage]:
|
|
76
|
+
"""Convert chat history dicts to LangChain messages.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
chat_history: List of dicts with "role" and "content" keys.
|
|
80
|
+
Supported roles: "user"/"human", "assistant"/"ai", "system".
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of LangChain BaseMessage instances.
|
|
84
|
+
"""
|
|
85
|
+
if not chat_history:
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage # noqa: PLC0415
|
|
89
|
+
|
|
90
|
+
messages: list[BaseMessage] = []
|
|
91
|
+
for msg in chat_history:
|
|
92
|
+
role = msg.get("role", "").lower()
|
|
93
|
+
content = msg.get("content", "")
|
|
94
|
+
|
|
95
|
+
if role in ("user", "human"):
|
|
96
|
+
messages.append(HumanMessage(content=content))
|
|
97
|
+
elif role in ("assistant", "ai"):
|
|
98
|
+
messages.append(AIMessage(content=content))
|
|
99
|
+
elif role == "system":
|
|
100
|
+
messages.append(SystemMessage(content=content))
|
|
101
|
+
else:
|
|
102
|
+
# Default to human message for unknown roles
|
|
103
|
+
logger.warning("Unknown chat history role '%s', treating as user message", role)
|
|
104
|
+
messages.append(HumanMessage(content=content))
|
|
105
|
+
|
|
106
|
+
return messages
|
|
39
107
|
|
|
40
108
|
|
|
41
109
|
@dataclass(frozen=True, slots=True)
|
|
42
110
|
class LangGraphRunner(BaseRunner):
|
|
43
111
|
"""Runner implementation using aip-agents LangGraphReactAgent.
|
|
44
112
|
|
|
45
|
-
|
|
46
|
-
- Execute via `LangGraphReactAgent.
|
|
47
|
-
-
|
|
113
|
+
Current behavior:
|
|
114
|
+
- Execute via `LangGraphReactAgent.arun_sse_stream()` (normalized SSE-compatible stream)
|
|
115
|
+
- Route all events through `AgentRunRenderingManager.async_process_stream_events`
|
|
116
|
+
for unified rendering between local and remote agents
|
|
48
117
|
|
|
49
118
|
Attributes:
|
|
50
119
|
default_model: Model name to use when agent.model is not set.
|
|
@@ -58,8 +127,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
58
127
|
agent: Agent,
|
|
59
128
|
message: str,
|
|
60
129
|
verbose: bool = False,
|
|
61
|
-
runtime_config: dict[str, Any] | None = None,
|
|
62
|
-
chat_history:
|
|
130
|
+
runtime_config: dict[str, Any] | None = None,
|
|
131
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
132
|
+
*,
|
|
133
|
+
swallow_aip_logs: bool = True,
|
|
63
134
|
**kwargs: Any,
|
|
64
135
|
) -> str:
|
|
65
136
|
"""Execute agent synchronously and return final response text.
|
|
@@ -72,7 +143,11 @@ class LangGraphRunner(BaseRunner):
|
|
|
72
143
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
73
144
|
Defaults to None. (Implemented in PR-04+)
|
|
74
145
|
chat_history: Optional list of prior conversation messages.
|
|
75
|
-
|
|
146
|
+
Each message is a dict with "role" and "content" keys.
|
|
147
|
+
Defaults to None.
|
|
148
|
+
swallow_aip_logs: When True (default), silence noisy logs from aip-agents,
|
|
149
|
+
gllm_inference, OpenAILMInvoker, and httpx. Set to False to honor user
|
|
150
|
+
logging configuration.
|
|
76
151
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
77
152
|
|
|
78
153
|
Returns:
|
|
@@ -85,23 +160,37 @@ class LangGraphRunner(BaseRunner):
|
|
|
85
160
|
if not check_local_runtime_available():
|
|
86
161
|
raise RuntimeError(get_local_runtime_missing_message())
|
|
87
162
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
163
|
+
try:
|
|
164
|
+
asyncio.get_running_loop()
|
|
165
|
+
except RuntimeError:
|
|
166
|
+
pass
|
|
167
|
+
else:
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
"LangGraphRunner.run() cannot be called from a running event loop. "
|
|
170
|
+
"Use 'await LangGraphRunner.arun(...)' instead."
|
|
95
171
|
)
|
|
172
|
+
|
|
173
|
+
coro = self._arun_internal(
|
|
174
|
+
agent=agent,
|
|
175
|
+
message=message,
|
|
176
|
+
verbose=verbose,
|
|
177
|
+
runtime_config=runtime_config,
|
|
178
|
+
chat_history=chat_history,
|
|
179
|
+
swallow_aip_logs=swallow_aip_logs,
|
|
180
|
+
**kwargs,
|
|
96
181
|
)
|
|
97
182
|
|
|
183
|
+
return asyncio.run(coro)
|
|
184
|
+
|
|
98
185
|
async def arun(
|
|
99
186
|
self,
|
|
100
187
|
agent: Agent,
|
|
101
188
|
message: str,
|
|
102
189
|
verbose: bool = False,
|
|
103
190
|
runtime_config: dict[str, Any] | None = None,
|
|
104
|
-
chat_history:
|
|
191
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
192
|
+
*,
|
|
193
|
+
swallow_aip_logs: bool = True,
|
|
105
194
|
**kwargs: Any,
|
|
106
195
|
) -> str:
|
|
107
196
|
"""Execute agent asynchronously and return final response text.
|
|
@@ -114,7 +203,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
114
203
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
115
204
|
Defaults to None. (Implemented in PR-04+)
|
|
116
205
|
chat_history: Optional list of prior conversation messages.
|
|
117
|
-
|
|
206
|
+
Each message is a dict with "role" and "content" keys.
|
|
207
|
+
Defaults to None.
|
|
208
|
+
swallow_aip_logs: When True (default), silence noisy AIPAgents logs.
|
|
118
209
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
119
210
|
|
|
120
211
|
Returns:
|
|
@@ -128,6 +219,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
128
219
|
message=message,
|
|
129
220
|
verbose=verbose,
|
|
130
221
|
runtime_config=runtime_config,
|
|
222
|
+
chat_history=chat_history,
|
|
223
|
+
swallow_aip_logs=swallow_aip_logs,
|
|
131
224
|
**kwargs,
|
|
132
225
|
)
|
|
133
226
|
|
|
@@ -137,6 +230,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
137
230
|
message: str,
|
|
138
231
|
verbose: bool = False,
|
|
139
232
|
runtime_config: dict[str, Any] | None = None,
|
|
233
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
234
|
+
*,
|
|
235
|
+
swallow_aip_logs: bool = True,
|
|
140
236
|
**kwargs: Any,
|
|
141
237
|
) -> str:
|
|
142
238
|
"""Internal async implementation of agent execution.
|
|
@@ -146,28 +242,83 @@ class LangGraphRunner(BaseRunner):
|
|
|
146
242
|
message: The user message to send to the agent.
|
|
147
243
|
verbose: If True, emit debug trace output during execution.
|
|
148
244
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
245
|
+
chat_history: Optional list of prior conversation messages.
|
|
246
|
+
swallow_aip_logs: When True (default), silence noisy AIPAgents logs.
|
|
149
247
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
150
248
|
|
|
151
249
|
Returns:
|
|
152
250
|
The final response text from the agent.
|
|
153
251
|
"""
|
|
252
|
+
# Optionally swallow noisy AIPAgents logs
|
|
253
|
+
if swallow_aip_logs:
|
|
254
|
+
_swallow_aip_logs()
|
|
255
|
+
|
|
256
|
+
# POC/MVP: Create pause/resume callback for interactive HITL input
|
|
257
|
+
pause_resume_callback = PauseResumeCallback()
|
|
258
|
+
|
|
154
259
|
# Build the local LangGraphReactAgent from the glaip_sdk Agent
|
|
155
|
-
local_agent = self.build_langgraph_agent(
|
|
260
|
+
local_agent = self.build_langgraph_agent(
|
|
261
|
+
agent, runtime_config=runtime_config, pause_resume_callback=pause_resume_callback
|
|
262
|
+
)
|
|
156
263
|
|
|
157
|
-
#
|
|
158
|
-
|
|
264
|
+
# Convert chat history to LangChain messages for the agent
|
|
265
|
+
langchain_messages = _convert_chat_history_to_messages(chat_history)
|
|
266
|
+
if langchain_messages:
|
|
267
|
+
kwargs["messages"] = langchain_messages
|
|
268
|
+
logger.debug(
|
|
269
|
+
"Passing %d chat history messages to agent '%s'",
|
|
270
|
+
len(langchain_messages),
|
|
271
|
+
agent.name,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Use shared render manager for unified processing
|
|
275
|
+
render_manager = AgentRunRenderingManager(logger)
|
|
276
|
+
renderer = render_manager.create_renderer(kwargs.get("renderer"), verbose=verbose)
|
|
277
|
+
|
|
278
|
+
# POC/MVP: Set renderer on callback so LocalPromptHandler can pause/resume Live
|
|
279
|
+
pause_resume_callback.set_renderer(renderer)
|
|
280
|
+
|
|
281
|
+
meta = render_manager.build_initial_metadata(agent.name, message, kwargs)
|
|
282
|
+
render_manager.start_renderer(renderer, meta)
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
# Use shared async stream processor for unified event handling
|
|
286
|
+
(
|
|
287
|
+
final_text,
|
|
288
|
+
stats_usage,
|
|
289
|
+
started_monotonic,
|
|
290
|
+
finished_monotonic,
|
|
291
|
+
) = await render_manager.async_process_stream_events(
|
|
292
|
+
local_agent.arun_sse_stream(message, **kwargs),
|
|
293
|
+
renderer,
|
|
294
|
+
meta,
|
|
295
|
+
skip_final_render=True,
|
|
296
|
+
)
|
|
297
|
+
except KeyboardInterrupt:
|
|
298
|
+
try:
|
|
299
|
+
renderer.close()
|
|
300
|
+
finally:
|
|
301
|
+
raise
|
|
302
|
+
except Exception:
|
|
303
|
+
try:
|
|
304
|
+
renderer.close()
|
|
305
|
+
finally:
|
|
306
|
+
raise
|
|
159
307
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
self._log_event(event)
|
|
163
|
-
events.append(event)
|
|
308
|
+
# Use shared finalizer to avoid code duplication
|
|
309
|
+
from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
|
|
164
310
|
|
|
165
|
-
return
|
|
311
|
+
return finalize_render_manager(
|
|
312
|
+
render_manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
|
|
313
|
+
)
|
|
166
314
|
|
|
167
315
|
def build_langgraph_agent(
|
|
168
316
|
self,
|
|
169
317
|
agent: Agent,
|
|
170
318
|
runtime_config: dict[str, Any] | None = None,
|
|
319
|
+
shared_tool_output_manager: Any | None = None,
|
|
320
|
+
*,
|
|
321
|
+
pause_resume_callback: Any | None = None,
|
|
171
322
|
) -> Any:
|
|
172
323
|
"""Build a LangGraphReactAgent from a glaip_sdk Agent definition.
|
|
173
324
|
|
|
@@ -175,6 +326,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
175
326
|
agent: The glaip_sdk Agent to convert.
|
|
176
327
|
runtime_config: Optional runtime configuration with tool_configs,
|
|
177
328
|
mcp_configs, agent_config, and agent-specific overrides.
|
|
329
|
+
shared_tool_output_manager: Optional ToolOutputManager to reuse across
|
|
330
|
+
agents with tool_output_sharing enabled.
|
|
331
|
+
pause_resume_callback: Optional callback used to pause/resume the renderer
|
|
332
|
+
during interactive HITL prompts.
|
|
178
333
|
|
|
179
334
|
Returns:
|
|
180
335
|
A configured LangGraphReactAgent instance.
|
|
@@ -184,17 +339,18 @@ class LangGraphRunner(BaseRunner):
|
|
|
184
339
|
ValueError: If agent has unsupported tools, MCPs, or sub-agents for local mode.
|
|
185
340
|
"""
|
|
186
341
|
from aip_agents.agent import LangGraphReactAgent # noqa: PLC0415
|
|
342
|
+
|
|
187
343
|
from glaip_sdk.runner.tool_adapter import LangChainToolAdapter # noqa: PLC0415
|
|
188
344
|
|
|
189
345
|
# Adapt tools for local execution
|
|
346
|
+
# NOTE: CLI parity waiver - local tool execution is SDK-only for MVP.
|
|
347
|
+
# See specs/f/local-agent-runtime/plan.md: "CLI parity is explicitly deferred
|
|
348
|
+
# and will require SDK Technical Lead sign-off per constitution principle IV."
|
|
190
349
|
langchain_tools: list[Any] = []
|
|
191
350
|
if agent.tools:
|
|
192
351
|
adapter = LangChainToolAdapter()
|
|
193
352
|
langchain_tools = adapter.adapt_tools(agent.tools)
|
|
194
353
|
|
|
195
|
-
# Build sub-agents recursively
|
|
196
|
-
sub_agent_instances = self._build_sub_agents(agent.agents, runtime_config)
|
|
197
|
-
|
|
198
354
|
# Normalize runtime config: merge global and agent-specific configs
|
|
199
355
|
normalized_config = self._normalize_runtime_config(runtime_config, agent)
|
|
200
356
|
|
|
@@ -208,6 +364,19 @@ class LangGraphRunner(BaseRunner):
|
|
|
208
364
|
merged_agent_config = self._merge_agent_config(agent, normalized_config)
|
|
209
365
|
agent_config_params, agent_config_kwargs = self._apply_agent_config(merged_agent_config)
|
|
210
366
|
|
|
367
|
+
tool_output_manager = self._resolve_tool_output_manager(
|
|
368
|
+
agent,
|
|
369
|
+
merged_agent_config,
|
|
370
|
+
shared_tool_output_manager,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Build sub-agents recursively, sharing tool output manager when enabled.
|
|
374
|
+
sub_agent_instances = self._build_sub_agents(
|
|
375
|
+
agent.agents,
|
|
376
|
+
runtime_config,
|
|
377
|
+
shared_tool_output_manager=tool_output_manager,
|
|
378
|
+
)
|
|
379
|
+
|
|
211
380
|
# Build the LangGraphReactAgent with tools, sub-agents, and configs
|
|
212
381
|
local_agent = LangGraphReactAgent(
|
|
213
382
|
name=agent.name,
|
|
@@ -217,6 +386,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
217
386
|
tools=langchain_tools,
|
|
218
387
|
agents=sub_agent_instances if sub_agent_instances else None,
|
|
219
388
|
tool_configs=tool_configs if tool_configs else None,
|
|
389
|
+
tool_output_manager=tool_output_manager,
|
|
390
|
+
guardrail=agent.guardrail,
|
|
220
391
|
**agent_config_params,
|
|
221
392
|
**agent_config_kwargs,
|
|
222
393
|
)
|
|
@@ -224,6 +395,11 @@ class LangGraphRunner(BaseRunner):
|
|
|
224
395
|
# Add MCP servers if configured
|
|
225
396
|
self._add_mcp_servers(local_agent, agent, mcp_configs)
|
|
226
397
|
|
|
398
|
+
# Inject local HITL manager only if hitl_enabled is True (master switch).
|
|
399
|
+
# This matches remote behavior: hitl_enabled gates the HITL plumbing.
|
|
400
|
+
# Tool-level HITL configs are only enforced when hitl_enabled=True.
|
|
401
|
+
self._inject_hitl_manager(local_agent, merged_agent_config, agent.name, pause_resume_callback)
|
|
402
|
+
|
|
227
403
|
logger.debug(
|
|
228
404
|
"Built local LangGraphReactAgent for agent '%s' with %d tools, %d sub-agents, and %d MCPs",
|
|
229
405
|
agent.name,
|
|
@@ -233,16 +409,63 @@ class LangGraphRunner(BaseRunner):
|
|
|
233
409
|
)
|
|
234
410
|
return local_agent
|
|
235
411
|
|
|
412
|
+
def _resolve_tool_output_manager(
|
|
413
|
+
self,
|
|
414
|
+
agent: Agent,
|
|
415
|
+
merged_agent_config: dict[str, Any],
|
|
416
|
+
shared_tool_output_manager: Any | None,
|
|
417
|
+
) -> Any | None:
|
|
418
|
+
"""Resolve tool output manager for local agent execution."""
|
|
419
|
+
tool_output_sharing_enabled = merged_agent_config.get("tool_output_sharing", False)
|
|
420
|
+
if not tool_output_sharing_enabled:
|
|
421
|
+
return None
|
|
422
|
+
if shared_tool_output_manager is not None:
|
|
423
|
+
return shared_tool_output_manager
|
|
424
|
+
return build_tool_output_manager(agent.name, merged_agent_config)
|
|
425
|
+
|
|
426
|
+
def _inject_hitl_manager(
|
|
427
|
+
self,
|
|
428
|
+
local_agent: Any,
|
|
429
|
+
merged_agent_config: dict[str, Any],
|
|
430
|
+
agent_name: str,
|
|
431
|
+
pause_resume_callback: Any | None,
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Inject HITL manager when enabled, mirroring remote gating behavior."""
|
|
434
|
+
hitl_enabled = merged_agent_config.get("hitl_enabled", False)
|
|
435
|
+
if hitl_enabled:
|
|
436
|
+
try:
|
|
437
|
+
from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
|
|
438
|
+
from glaip_sdk.hitl import LocalPromptHandler # noqa: PLC0415
|
|
439
|
+
|
|
440
|
+
local_agent.hitl_manager = ApprovalManager(
|
|
441
|
+
prompt_handler=LocalPromptHandler(pause_resume_callback=pause_resume_callback)
|
|
442
|
+
)
|
|
443
|
+
# Store callback reference for setting renderer later
|
|
444
|
+
if pause_resume_callback:
|
|
445
|
+
local_agent._pause_resume_callback = pause_resume_callback
|
|
446
|
+
logger.debug("HITL manager injected for agent '%s' (hitl_enabled=True)", agent_name)
|
|
447
|
+
except ImportError as e:
|
|
448
|
+
# Missing dependencies - fail fast
|
|
449
|
+
raise ImportError("Local HITL requires aip_agents. Install with: pip install 'glaip-sdk[local]'") from e
|
|
450
|
+
except Exception as e:
|
|
451
|
+
# Other errors during HITL setup - fail fast
|
|
452
|
+
raise RuntimeError(f"Failed to initialize HITL manager for agent '{agent_name}'") from e
|
|
453
|
+
else:
|
|
454
|
+
logger.debug("HITL manager not injected for agent '%s' (hitl_enabled=False)", agent_name)
|
|
455
|
+
|
|
236
456
|
def _build_sub_agents(
|
|
237
457
|
self,
|
|
238
458
|
sub_agents: list[Any] | None,
|
|
239
459
|
runtime_config: dict[str, Any] | None,
|
|
460
|
+
shared_tool_output_manager: Any | None = None,
|
|
240
461
|
) -> list[Any]:
|
|
241
462
|
"""Build sub-agent instances recursively.
|
|
242
463
|
|
|
243
464
|
Args:
|
|
244
465
|
sub_agents: List of sub-agent definitions.
|
|
245
466
|
runtime_config: Runtime config to pass to sub-agents.
|
|
467
|
+
shared_tool_output_manager: Optional ToolOutputManager to reuse across
|
|
468
|
+
agents with tool_output_sharing enabled.
|
|
246
469
|
|
|
247
470
|
Returns:
|
|
248
471
|
List of built sub-agent instances.
|
|
@@ -255,15 +478,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
255
478
|
|
|
256
479
|
sub_agent_instances = []
|
|
257
480
|
for sub_agent in sub_agents:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
"Define the sub-agent locally with Agent(name=..., instruction=...) instead."
|
|
481
|
+
self._validate_sub_agent_for_local_mode(sub_agent)
|
|
482
|
+
sub_agent_instances.append(
|
|
483
|
+
self.build_langgraph_agent(
|
|
484
|
+
sub_agent,
|
|
485
|
+
runtime_config,
|
|
486
|
+
shared_tool_output_manager=shared_tool_output_manager,
|
|
265
487
|
)
|
|
266
|
-
|
|
488
|
+
)
|
|
267
489
|
return sub_agent_instances
|
|
268
490
|
|
|
269
491
|
def _add_mcp_servers(
|
|
@@ -286,16 +508,12 @@ class LangGraphRunner(BaseRunner):
|
|
|
286
508
|
|
|
287
509
|
mcp_adapter = LangChainMCPAdapter()
|
|
288
510
|
base_mcp_configs = mcp_adapter.adapt_mcps(agent.mcps)
|
|
289
|
-
logger.debug("Base MCP configs from adapter: %s", base_mcp_configs)
|
|
290
511
|
|
|
291
512
|
# Apply merged mcp_configs overrides (agent definition + runtime)
|
|
292
|
-
logger.debug("Merged mcp_configs to apply: %s", merged_mcp_configs)
|
|
293
513
|
if merged_mcp_configs:
|
|
294
514
|
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
515
|
|
|
297
516
|
if base_mcp_configs:
|
|
298
|
-
logger.info("MCP configs being sent to aip-agents: %s", base_mcp_configs)
|
|
299
517
|
local_agent.add_mcp_server(base_mcp_configs)
|
|
300
518
|
logger.debug(
|
|
301
519
|
"Registered %d MCP server(s) for agent '%s'",
|
|
@@ -361,6 +579,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
361
579
|
Returns:
|
|
362
580
|
Agent-specific config dict, or empty dict if not found.
|
|
363
581
|
"""
|
|
582
|
+
from glaip_sdk.utils.resource_refs import is_uuid # noqa: PLC0415
|
|
364
583
|
from glaip_sdk.utils.runtime_config import get_name_from_key # noqa: PLC0415
|
|
365
584
|
|
|
366
585
|
# Reserved keys at the top level
|
|
@@ -371,6 +590,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
371
590
|
if key in reserved_keys:
|
|
372
591
|
continue # Skip global configs
|
|
373
592
|
|
|
593
|
+
if isinstance(key, str) and is_uuid(key):
|
|
594
|
+
logger.warning(
|
|
595
|
+
"UUID agent override key '%s' is not supported in local mode; skipping. "
|
|
596
|
+
"Use agent name string or Agent instance as the key instead.",
|
|
597
|
+
key,
|
|
598
|
+
)
|
|
599
|
+
continue
|
|
600
|
+
|
|
374
601
|
# Check if this key matches the agent
|
|
375
602
|
try:
|
|
376
603
|
key_name = get_name_from_key(key)
|
|
@@ -505,7 +732,13 @@ class LangGraphRunner(BaseRunner):
|
|
|
505
732
|
if "planning" in agent_config:
|
|
506
733
|
direct_params["planning"] = agent_config["planning"]
|
|
507
734
|
|
|
735
|
+
if "enable_a2a_token_streaming" in agent_config:
|
|
736
|
+
direct_params["enable_a2a_token_streaming"] = agent_config["enable_a2a_token_streaming"]
|
|
737
|
+
|
|
508
738
|
# Kwargs parameters (passed through **kwargs to BaseAgent)
|
|
739
|
+
if "enable_pii" in agent_config:
|
|
740
|
+
kwargs_params["enable_pii"] = agent_config["enable_pii"]
|
|
741
|
+
|
|
509
742
|
if "memory" in agent_config:
|
|
510
743
|
# Map "memory" to "memory_backend" for aip-agents compatibility
|
|
511
744
|
kwargs_params["memory_backend"] = agent_config["memory"]
|
|
@@ -579,6 +812,49 @@ class LangGraphRunner(BaseRunner):
|
|
|
579
812
|
|
|
580
813
|
return merged
|
|
581
814
|
|
|
815
|
+
def _validate_sub_agent_for_local_mode(self, sub_agent: Any) -> None:
|
|
816
|
+
"""Validate that a sub-agent reference is supported for local execution.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
sub_agent: The sub-agent reference to validate.
|
|
820
|
+
|
|
821
|
+
Raises:
|
|
822
|
+
ValueError: If the sub-agent is not supported in local mode.
|
|
823
|
+
"""
|
|
824
|
+
# String references are allowed by SDK API but not for local mode
|
|
825
|
+
if isinstance(sub_agent, str):
|
|
826
|
+
raise ValueError(
|
|
827
|
+
f"Sub-agent '{sub_agent}' is a string reference and cannot be used in local mode. "
|
|
828
|
+
"String sub-agent references are only supported for server execution. "
|
|
829
|
+
"For local mode, define the sub-agent with Agent(name=..., instruction=...)."
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
# Validate sub-agent is not a class
|
|
833
|
+
if inspect.isclass(sub_agent):
|
|
834
|
+
raise ValueError(
|
|
835
|
+
f"Sub-agent '{sub_agent.__name__}' is a class, not an instance. "
|
|
836
|
+
"Local mode requires Agent INSTANCES. "
|
|
837
|
+
"Did you forget to instantiate it? e.g., Agent(...), not Agent"
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Validate sub-agent is an Agent-like object (has required attributes)
|
|
841
|
+
if not hasattr(sub_agent, "name") or not hasattr(sub_agent, "instruction"):
|
|
842
|
+
raise ValueError(
|
|
843
|
+
f"Sub-agent {type(sub_agent).__name__} is not supported in local mode. "
|
|
844
|
+
"Local mode requires Agent instances with 'name' and 'instruction' attributes. "
|
|
845
|
+
"Define the sub-agent with Agent(name=..., instruction=...)."
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
# Validate sub-agent is not platform-only (from_id, from_native)
|
|
849
|
+
if getattr(sub_agent, "_lookup_only", False):
|
|
850
|
+
agent_name = getattr(sub_agent, "name", "<unknown>")
|
|
851
|
+
raise ValueError(
|
|
852
|
+
f"Sub-agent '{agent_name}' is not supported in local mode. "
|
|
853
|
+
"Platform agents (from_id, from_native) cannot be used as "
|
|
854
|
+
"sub-agents in local execution. "
|
|
855
|
+
"Define the sub-agent locally with Agent(name=..., instruction=...) instead."
|
|
856
|
+
)
|
|
857
|
+
|
|
582
858
|
def _log_event(self, event: dict[str, Any]) -> None:
|
|
583
859
|
"""Log an A2AEvent for verbose debug output.
|
|
584
860
|
|