glaip-sdk 0.6.15b3__py3-none-any.whl → 0.6.17__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/hitl/__init__.py +15 -0
- glaip_sdk/hitl/local.py +151 -0
- glaip_sdk/runner/langgraph.py +39 -1
- {glaip_sdk-0.6.15b3.dist-info → glaip_sdk-0.6.17.dist-info}/METADATA +6 -7
- {glaip_sdk-0.6.15b3.dist-info → glaip_sdk-0.6.17.dist-info}/RECORD +8 -6
- {glaip_sdk-0.6.15b3.dist-info → glaip_sdk-0.6.17.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.15b3.dist-info → glaip_sdk-0.6.17.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.6.15b3.dist-info → glaip_sdk-0.6.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Human-in-the-Loop (HITL) utilities for glaip-sdk.
|
|
2
|
+
|
|
3
|
+
This package provides utilities for HITL approval workflows in both local
|
|
4
|
+
and remote agent execution modes.
|
|
5
|
+
|
|
6
|
+
For local development, LocalPromptHandler is automatically injected when
|
|
7
|
+
agent_config.hitl_enabled is True. No manual setup required.
|
|
8
|
+
|
|
9
|
+
Authors:
|
|
10
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.hitl.local import LocalPromptHandler, PauseResumeCallback
|
|
14
|
+
|
|
15
|
+
__all__ = ["LocalPromptHandler", "PauseResumeCallback"]
|
glaip_sdk/hitl/local.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Local HITL prompt handler with interactive console support.
|
|
2
|
+
|
|
3
|
+
Author:
|
|
4
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from aip_agents.agent.hitl.prompt.base import BasePromptHandler
|
|
12
|
+
from aip_agents.schema.hitl import ApprovalDecision, ApprovalDecisionType, ApprovalRequest
|
|
13
|
+
except ImportError as e:
|
|
14
|
+
raise ImportError("aip_agents is required for local HITL. Install with: pip install 'glaip-sdk[local]'") from e
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.prompt import Prompt
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LocalPromptHandler(BasePromptHandler):
|
|
21
|
+
"""Local HITL prompt handler with interactive console prompts.
|
|
22
|
+
|
|
23
|
+
Experimental local HITL implementation with known limitations:
|
|
24
|
+
- Timeouts are not enforced (interactive prompts wait indefinitely)
|
|
25
|
+
- Relies on private renderer methods for pause/resume
|
|
26
|
+
- Only supports interactive terminal environments
|
|
27
|
+
|
|
28
|
+
The key insight from Rich documentation is that Live must be stopped before
|
|
29
|
+
using Prompt/input(), otherwise the input won't render properly.
|
|
30
|
+
|
|
31
|
+
Environment variables:
|
|
32
|
+
GLAIP_HITL_AUTO_APPROVE: Set to "true" (case-insensitive) to auto-approve
|
|
33
|
+
all requests without user interaction. Useful for integration tests and CI.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, *, pause_resume_callback: Any | None = None) -> None:
|
|
37
|
+
"""Initialize the prompt handler.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
pause_resume_callback: Optional callable with pause() and resume() methods
|
|
41
|
+
to control the live renderer during prompts. This is needed because
|
|
42
|
+
Rich Live interferes with Prompt/input().
|
|
43
|
+
"""
|
|
44
|
+
super().__init__()
|
|
45
|
+
self._pause_resume = pause_resume_callback
|
|
46
|
+
self._console = Console()
|
|
47
|
+
|
|
48
|
+
async def prompt_for_decision(
|
|
49
|
+
self,
|
|
50
|
+
request: ApprovalRequest,
|
|
51
|
+
timeout_seconds: int,
|
|
52
|
+
context_keys: list[str] | None = None,
|
|
53
|
+
) -> ApprovalDecision:
|
|
54
|
+
"""Prompt for approval decision with live renderer pause/resume.
|
|
55
|
+
|
|
56
|
+
Supports auto-approval via GLAIP_HITL_AUTO_APPROVE environment variable
|
|
57
|
+
for integration testing and CI environments. Set to "true" (case-insensitive) to enable.
|
|
58
|
+
"""
|
|
59
|
+
_ = (timeout_seconds, context_keys) # Suppress unused parameter warnings.
|
|
60
|
+
|
|
61
|
+
# Check for auto-approve mode (for integration tests/CI)
|
|
62
|
+
auto_approve = os.getenv("GLAIP_HITL_AUTO_APPROVE", "").lower() == "true"
|
|
63
|
+
|
|
64
|
+
if auto_approve:
|
|
65
|
+
# Auto-approve without user interaction
|
|
66
|
+
return ApprovalDecision(
|
|
67
|
+
request_id=request.request_id,
|
|
68
|
+
decision=ApprovalDecisionType.APPROVED,
|
|
69
|
+
operator_input="auto-approved",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Pause the live renderer if callback is available
|
|
73
|
+
if self._pause_resume:
|
|
74
|
+
self._pause_resume.pause()
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# POC/MVP: Show what we're approving (still auto-approve for now)
|
|
78
|
+
self._print_request_info(request)
|
|
79
|
+
|
|
80
|
+
# POC/MVP: For testing, we can do actual input here
|
|
81
|
+
# Uncomment to enable real prompting:
|
|
82
|
+
response = Prompt.ask(
|
|
83
|
+
"\n[yellow]Approve this tool call?[/yellow] [dim](y/n/s)[/dim]",
|
|
84
|
+
console=self._console,
|
|
85
|
+
default="y",
|
|
86
|
+
)
|
|
87
|
+
response = response.lower().strip()
|
|
88
|
+
|
|
89
|
+
if response in ("y", "yes"):
|
|
90
|
+
decision = ApprovalDecisionType.APPROVED
|
|
91
|
+
elif response in ("n", "no"):
|
|
92
|
+
decision = ApprovalDecisionType.REJECTED
|
|
93
|
+
else:
|
|
94
|
+
decision = ApprovalDecisionType.SKIPPED
|
|
95
|
+
|
|
96
|
+
return ApprovalDecision(
|
|
97
|
+
request_id=request.request_id,
|
|
98
|
+
decision=decision,
|
|
99
|
+
operator_input=response if decision != ApprovalDecisionType.SKIPPED else None,
|
|
100
|
+
)
|
|
101
|
+
finally:
|
|
102
|
+
# Always resume the live renderer
|
|
103
|
+
if self._pause_resume:
|
|
104
|
+
self._pause_resume.resume()
|
|
105
|
+
|
|
106
|
+
def _print_request_info(self, request: ApprovalRequest) -> None:
|
|
107
|
+
"""Print the approval request information."""
|
|
108
|
+
self._console.print()
|
|
109
|
+
self._console.rule("[yellow]HITL Approval Request[/yellow]", style="yellow")
|
|
110
|
+
|
|
111
|
+
tool_name = request.tool_name or "unknown"
|
|
112
|
+
self._console.print(f"[cyan]Tool:[/cyan] {tool_name}")
|
|
113
|
+
|
|
114
|
+
if hasattr(request, "arguments_preview") and request.arguments_preview:
|
|
115
|
+
self._console.print(f"[cyan]Arguments:[/cyan] {request.arguments_preview}")
|
|
116
|
+
|
|
117
|
+
if request.context:
|
|
118
|
+
self._console.print(f"[dim]Context: {request.context}[/dim]")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PauseResumeCallback:
|
|
122
|
+
"""Simple callback object for pausing/resuming the live renderer.
|
|
123
|
+
|
|
124
|
+
This allows the LocalPromptHandler to control the renderer without
|
|
125
|
+
directly coupling to the renderer implementation.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self) -> None:
|
|
129
|
+
"""Initialize the callback."""
|
|
130
|
+
self._renderer: Any | None = None
|
|
131
|
+
|
|
132
|
+
def set_renderer(self, renderer: Any) -> None:
|
|
133
|
+
"""Set the renderer instance.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
renderer: RichStreamRenderer instance with pause_live() and resume_live() methods.
|
|
137
|
+
"""
|
|
138
|
+
self._renderer = renderer
|
|
139
|
+
|
|
140
|
+
def pause(self) -> None:
|
|
141
|
+
"""Pause the live renderer before prompting."""
|
|
142
|
+
if self._renderer and hasattr(self._renderer, "_shutdown_live"):
|
|
143
|
+
self._renderer._shutdown_live()
|
|
144
|
+
|
|
145
|
+
def resume(self) -> None:
|
|
146
|
+
"""Resume the live renderer after prompting."""
|
|
147
|
+
if self._renderer and hasattr(self._renderer, "_ensure_live"):
|
|
148
|
+
self._renderer._ensure_live()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = ["LocalPromptHandler", "PauseResumeCallback"]
|
glaip_sdk/runner/langgraph.py
CHANGED
|
@@ -25,6 +25,9 @@ from typing import TYPE_CHECKING, Any
|
|
|
25
25
|
|
|
26
26
|
from gllm_core.utils import LoggerManager
|
|
27
27
|
|
|
28
|
+
from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
|
|
29
|
+
|
|
30
|
+
from glaip_sdk.hitl import LocalPromptHandler, PauseResumeCallback
|
|
28
31
|
from glaip_sdk.runner.base import BaseRunner
|
|
29
32
|
from glaip_sdk.runner.deps import (
|
|
30
33
|
check_local_runtime_available,
|
|
@@ -251,8 +254,13 @@ class LangGraphRunner(BaseRunner):
|
|
|
251
254
|
if swallow_aip_logs:
|
|
252
255
|
_swallow_aip_logs()
|
|
253
256
|
|
|
257
|
+
# POC/MVP: Create pause/resume callback for interactive HITL input
|
|
258
|
+
pause_resume_callback = PauseResumeCallback()
|
|
259
|
+
|
|
254
260
|
# Build the local LangGraphReactAgent from the glaip_sdk Agent
|
|
255
|
-
local_agent = self.build_langgraph_agent(
|
|
261
|
+
local_agent = self.build_langgraph_agent(
|
|
262
|
+
agent, runtime_config=runtime_config, pause_resume_callback=pause_resume_callback
|
|
263
|
+
)
|
|
256
264
|
|
|
257
265
|
# Convert chat history to LangChain messages for the agent
|
|
258
266
|
langchain_messages = _convert_chat_history_to_messages(chat_history)
|
|
@@ -267,6 +275,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
267
275
|
# Use shared render manager for unified processing
|
|
268
276
|
render_manager = AgentRunRenderingManager(logger)
|
|
269
277
|
renderer = render_manager.create_renderer(kwargs.get("renderer"), verbose=verbose)
|
|
278
|
+
|
|
279
|
+
# POC/MVP: Set renderer on callback so LocalPromptHandler can pause/resume Live
|
|
280
|
+
pause_resume_callback.set_renderer(renderer)
|
|
281
|
+
|
|
270
282
|
meta = render_manager.build_initial_metadata(agent.name, message, kwargs)
|
|
271
283
|
render_manager.start_renderer(renderer, meta)
|
|
272
284
|
|
|
@@ -305,6 +317,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
305
317
|
self,
|
|
306
318
|
agent: Agent,
|
|
307
319
|
runtime_config: dict[str, Any] | None = None,
|
|
320
|
+
*,
|
|
321
|
+
pause_resume_callback: Any | None = None,
|
|
308
322
|
) -> Any:
|
|
309
323
|
"""Build a LangGraphReactAgent from a glaip_sdk Agent definition.
|
|
310
324
|
|
|
@@ -312,6 +326,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
312
326
|
agent: The glaip_sdk Agent to convert.
|
|
313
327
|
runtime_config: Optional runtime configuration with tool_configs,
|
|
314
328
|
mcp_configs, agent_config, and agent-specific overrides.
|
|
329
|
+
pause_resume_callback: Optional callback used to pause/resume the renderer
|
|
330
|
+
during interactive HITL prompts.
|
|
315
331
|
|
|
316
332
|
Returns:
|
|
317
333
|
A configured LangGraphReactAgent instance.
|
|
@@ -365,6 +381,28 @@ class LangGraphRunner(BaseRunner):
|
|
|
365
381
|
# Add MCP servers if configured
|
|
366
382
|
self._add_mcp_servers(local_agent, agent, mcp_configs)
|
|
367
383
|
|
|
384
|
+
# Inject local HITL manager only if hitl_enabled is True (master switch).
|
|
385
|
+
# This matches remote behavior: hitl_enabled gates the HITL plumbing.
|
|
386
|
+
# Tool-level HITL configs are only enforced when hitl_enabled=True.
|
|
387
|
+
hitl_enabled = merged_agent_config.get("hitl_enabled", False)
|
|
388
|
+
if hitl_enabled:
|
|
389
|
+
try:
|
|
390
|
+
local_agent.hitl_manager = ApprovalManager(
|
|
391
|
+
prompt_handler=LocalPromptHandler(pause_resume_callback=pause_resume_callback)
|
|
392
|
+
)
|
|
393
|
+
# Store callback reference for setting renderer later
|
|
394
|
+
if pause_resume_callback:
|
|
395
|
+
local_agent._pause_resume_callback = pause_resume_callback
|
|
396
|
+
logger.debug("HITL manager injected for agent '%s' (hitl_enabled=True)", agent.name)
|
|
397
|
+
except ImportError as e:
|
|
398
|
+
# Missing dependencies - fail fast
|
|
399
|
+
raise ImportError("Local HITL requires aip_agents. Install with: pip install 'glaip-sdk[local]'") from e
|
|
400
|
+
except Exception as e:
|
|
401
|
+
# Other errors during HITL setup - fail fast
|
|
402
|
+
raise RuntimeError(f"Failed to initialize HITL manager for agent '{agent.name}'") from e
|
|
403
|
+
else:
|
|
404
|
+
logger.debug("HITL manager not injected for agent '%s' (hitl_enabled=False)", agent.name)
|
|
405
|
+
|
|
368
406
|
logger.debug(
|
|
369
407
|
"Built local LangGraphReactAgent for agent '%s' with %d tools, %d sub-agents, and %d MCPs",
|
|
370
408
|
agent.name,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.17
|
|
4
4
|
Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
|
|
5
5
|
Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
|
|
6
6
|
License: MIT
|
|
@@ -19,12 +19,11 @@ Requires-Dist: textual>=0.52.0
|
|
|
19
19
|
Requires-Dist: gllm-core-binary>=0.1.0
|
|
20
20
|
Requires-Dist: langchain-core>=0.3.0
|
|
21
21
|
Requires-Dist: gllm-tools-binary>=0.1.3
|
|
22
|
-
|
|
23
|
-
Requires-Dist: aip-agents-binary[local]>=0.5.11; extra == "local"
|
|
22
|
+
Requires-Dist: aip-agents-binary[local]>=0.5.13
|
|
24
23
|
Provides-Extra: memory
|
|
25
|
-
Requires-Dist: aip-agents-binary[memory]>=0.5.
|
|
24
|
+
Requires-Dist: aip-agents-binary[memory]>=0.5.13; (python_version >= "3.11" and python_version < "3.13") and extra == "memory"
|
|
26
25
|
Provides-Extra: privacy
|
|
27
|
-
Requires-Dist: aip-agents-binary[privacy]>=0.5.
|
|
26
|
+
Requires-Dist: aip-agents-binary[privacy]>=0.5.13; (python_version >= "3.11" and python_version < "3.13") and extra == "privacy"
|
|
28
27
|
Requires-Dist: en-core-web-sm; extra == "privacy"
|
|
29
28
|
Provides-Extra: dev
|
|
30
29
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
@@ -38,7 +37,7 @@ Requires-Dist: ruff>=0.14.0; extra == "dev"
|
|
|
38
37
|
|
|
39
38
|
# GL AIP — GDP Labs AI Agents Package
|
|
40
39
|
|
|
41
|
-
[](https://www.python.org/downloads/)
|
|
42
41
|
[](https://github.com/psf/black)
|
|
43
42
|
|
|
44
43
|
GL stands for **GDP Labs**—GL AIP is our AI Agents Package for building, running, and operating agents.
|
|
@@ -57,7 +56,7 @@ pip install --upgrade glaip-sdk
|
|
|
57
56
|
uv tool install glaip-sdk
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
**Requirements**: Python 3.
|
|
59
|
+
**Requirements**: Python 3.11 or 3.12
|
|
61
60
|
|
|
62
61
|
## 🐍 Hello World - Python SDK
|
|
63
62
|
|
|
@@ -74,6 +74,8 @@ glaip_sdk/client/shared.py,sha256=esHlsR0LEfL-pFDaWebQjKKOLl09jsRY-2pllBUn4nU,52
|
|
|
74
74
|
glaip_sdk/client/tools.py,sha256=kK0rBwX1e_5AlGQRjlO6rNz6gDlohhXWdlxN9AwotdE,22585
|
|
75
75
|
glaip_sdk/client/validators.py,sha256=ioF9VCs-LG2yLkaRDd7Hff74lojDZZ0_Q3CiLbdm1RY,8381
|
|
76
76
|
glaip_sdk/config/constants.py,sha256=Y03c6op0e7K0jTQ8bmWXhWAqsnjWxkAhWniq8Z0iEKY,1081
|
|
77
|
+
glaip_sdk/hitl/__init__.py,sha256=sg92Rpu8_vJIGi1ZEhx0-qWa1nGdvfrKyJAxtoDSKzo,494
|
|
78
|
+
glaip_sdk/hitl/local.py,sha256=rzmaRK15BxgRX7cmklUcGQUotMYg8x2Gd9BWf39k6hw,5661
|
|
77
79
|
glaip_sdk/mcps/__init__.py,sha256=4jYrt8K__oxrxexHRcmnRBXt-W_tbJN61H9Kf2lVh4Q,551
|
|
78
80
|
glaip_sdk/mcps/base.py,sha256=jWwHjDF67_mtDGRp9p5SolANjVeB8jt1PSwPBtX876M,11654
|
|
79
81
|
glaip_sdk/models/__init__.py,sha256=-qO4Yr1-fkyaYC9RcT3nYhplDjoXATrIFZr4JrqflHI,2577
|
|
@@ -92,7 +94,7 @@ glaip_sdk/registry/tool.py,sha256=rxrVxnO_VwO6E5kccqxxEUC337J9qbKpje-Gwl5a3sY,76
|
|
|
92
94
|
glaip_sdk/runner/__init__.py,sha256=8RrngoGfpF8x9X27RPdX4gJjch75ZvhtVt_6UV0ULLQ,1615
|
|
93
95
|
glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
|
|
94
96
|
glaip_sdk/runner/deps.py,sha256=Du3hr2R5RHOYCRAv7RVmx661x-ayVXIeZ8JD7ODirTA,3884
|
|
95
|
-
glaip_sdk/runner/langgraph.py,sha256=
|
|
97
|
+
glaip_sdk/runner/langgraph.py,sha256=tcpbcve2K1IpjkJx00deP11WtCV6qny6QSgWgQH4YuA,30835
|
|
96
98
|
glaip_sdk/runner/mcp_adapter/__init__.py,sha256=Rdttfg3N6kg3-DaTCKqaGXKByZyBt0Mwf6FV8s_5kI8,462
|
|
97
99
|
glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py,sha256=ic56fKgb3zgVZZQm3ClWUZi7pE1t4EVq8mOg6AM6hdA,1374
|
|
98
100
|
glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py,sha256=b58GuadPz7q7aXoJyTYs0eeJ_oqp-wLR1tcr_5cbV1s,9723
|
|
@@ -153,8 +155,8 @@ glaip_sdk/utils/rendering/steps/format.py,sha256=Chnq7OBaj8XMeBntSBxrX5zSmrYeGcO
|
|
|
153
155
|
glaip_sdk/utils/rendering/steps/manager.py,sha256=BiBmTeQMQhjRMykgICXsXNYh1hGsss-fH9BIGVMWFi0,13194
|
|
154
156
|
glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvDcYi2mAhXLXn5CjI,457
|
|
155
157
|
glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
|
|
156
|
-
glaip_sdk-0.6.
|
|
157
|
-
glaip_sdk-0.6.
|
|
158
|
-
glaip_sdk-0.6.
|
|
159
|
-
glaip_sdk-0.6.
|
|
160
|
-
glaip_sdk-0.6.
|
|
158
|
+
glaip_sdk-0.6.17.dist-info/METADATA,sha256=_pRNW4dous-_1TOJSdFtaVhSC5YLcxCSp3zBRDHPhvA,7716
|
|
159
|
+
glaip_sdk-0.6.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
160
|
+
glaip_sdk-0.6.17.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
|
|
161
|
+
glaip_sdk-0.6.17.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
|
|
162
|
+
glaip_sdk-0.6.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|