aip-agents-binary 0.5.21__py3-none-macosx_13_0_arm64.whl → 0.6.8__py3-none-macosx_13_0_arm64.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.
- aip_agents/agent/__init__.py +44 -4
- aip_agents/agent/base_langgraph_agent.py +169 -74
- aip_agents/agent/base_langgraph_agent.pyi +3 -2
- aip_agents/agent/langgraph_memory_enhancer_agent.py +368 -34
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +3 -2
- aip_agents/agent/langgraph_react_agent.py +424 -35
- aip_agents/agent/langgraph_react_agent.pyi +46 -2
- aip_agents/examples/{hello_world_langgraph_bosa_twitter.py → hello_world_langgraph_gl_connector_twitter.py} +10 -7
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc.pyi +5 -0
- aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
- aip_agents/examples/hello_world_ptc_custom_tools.pyi +7 -0
- aip_agents/examples/hello_world_sentry.py +2 -2
- aip_agents/examples/hello_world_tool_output_client.py +9 -0
- aip_agents/examples/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/multiply_tool.pyi +18 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/__init__.pyi +6 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/__init__.pyi +4 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/base.pyi +61 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/nemo.pyi +46 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/exceptions.pyi +23 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/manager.pyi +42 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/middleware.pyi +87 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/schemas.pyi +43 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/guardrails/utils.pyi +19 -0
- aip_agents/mcp/client/__init__.py +38 -2
- aip_agents/mcp/client/connection_manager.py +36 -1
- aip_agents/mcp/client/connection_manager.pyi +3 -0
- aip_agents/mcp/client/persistent_session.py +318 -65
- aip_agents/mcp/client/persistent_session.pyi +9 -0
- aip_agents/mcp/client/transports.py +52 -4
- aip_agents/mcp/client/transports.pyi +9 -0
- aip_agents/memory/adapters/base_adapter.py +98 -0
- aip_agents/memory/adapters/base_adapter.pyi +25 -0
- aip_agents/middleware/base.py +8 -0
- aip_agents/middleware/base.pyi +4 -0
- aip_agents/middleware/manager.py +22 -0
- aip_agents/middleware/manager.pyi +4 -0
- aip_agents/ptc/__init__.py +87 -0
- aip_agents/ptc/__init__.pyi +14 -0
- aip_agents/ptc/custom_tools.py +473 -0
- aip_agents/ptc/custom_tools.pyi +184 -0
- aip_agents/ptc/custom_tools_payload.py +400 -0
- aip_agents/ptc/custom_tools_payload.pyi +31 -0
- aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
- aip_agents/ptc/custom_tools_templates/__init__.pyi +0 -0
- aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +23 -0
- aip_agents/ptc/custom_tools_templates/custom_init.py.template +15 -0
- aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +60 -0
- aip_agents/ptc/custom_tools_templates/custom_registry.py.template +87 -0
- aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
- aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +19 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/doc_gen.pyi +40 -0
- aip_agents/ptc/exceptions.py +57 -0
- aip_agents/ptc/exceptions.pyi +37 -0
- aip_agents/ptc/executor.py +261 -0
- aip_agents/ptc/executor.pyi +99 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/__init__.pyi +7 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
- aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
- aip_agents/ptc/naming.py +196 -0
- aip_agents/ptc/naming.pyi +85 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/payload.pyi +15 -0
- aip_agents/ptc/prompt_builder.py +673 -0
- aip_agents/ptc/prompt_builder.pyi +59 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/ptc_helper.pyi +1 -0
- aip_agents/ptc/sandbox_bridge.py +256 -0
- aip_agents/ptc/sandbox_bridge.pyi +38 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/template_utils.pyi +13 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/__init__.pyi +0 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/ptc/tool_def_helpers.py +101 -0
- aip_agents/ptc/tool_def_helpers.pyi +38 -0
- aip_agents/ptc/tool_enrichment.py +163 -0
- aip_agents/ptc/tool_enrichment.pyi +60 -0
- aip_agents/sandbox/__init__.py +43 -0
- aip_agents/sandbox/__init__.pyi +5 -0
- aip_agents/sandbox/defaults.py +205 -0
- aip_agents/sandbox/defaults.pyi +30 -0
- aip_agents/sandbox/e2b_runtime.py +295 -0
- aip_agents/sandbox/e2b_runtime.pyi +57 -0
- aip_agents/sandbox/template_builder.py +131 -0
- aip_agents/sandbox/template_builder.pyi +36 -0
- aip_agents/sandbox/types.py +24 -0
- aip_agents/sandbox/types.pyi +14 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/sandbox/validation.pyi +20 -0
- aip_agents/sentry/__init__.py +1 -1
- aip_agents/sentry/sentry.py +33 -12
- aip_agents/sentry/sentry.pyi +5 -4
- aip_agents/tools/__init__.py +20 -3
- aip_agents/tools/__init__.pyi +4 -2
- aip_agents/tools/browser_use/browser_use_tool.py +8 -0
- aip_agents/tools/browser_use/streaming.py +2 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +80 -31
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +25 -9
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +6 -6
- aip_agents/tools/constants.py +24 -12
- aip_agents/tools/constants.pyi +14 -11
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/date_range_tool.pyi +21 -0
- aip_agents/tools/execute_ptc_code.py +357 -0
- aip_agents/tools/execute_ptc_code.pyi +90 -0
- aip_agents/tools/gl_connector/__init__.py +1 -1
- aip_agents/tools/gl_connector/tool.py +62 -30
- aip_agents/tools/gl_connector/tool.pyi +3 -3
- aip_agents/tools/gl_connector_tools.py +119 -0
- aip_agents/tools/gl_connector_tools.pyi +39 -0
- aip_agents/tools/memory_search/__init__.py +8 -1
- aip_agents/tools/memory_search/__init__.pyi +3 -3
- aip_agents/tools/memory_search/mem0.py +114 -1
- aip_agents/tools/memory_search/mem0.pyi +11 -1
- aip_agents/tools/memory_search/schema.py +33 -0
- aip_agents/tools/memory_search/schema.pyi +10 -0
- aip_agents/tools/memory_search_tool.py +8 -0
- aip_agents/tools/memory_search_tool.pyi +2 -2
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +26 -1
- aip_agents/utils/langgraph/tool_output_management.py +80 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +37 -0
- {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/METADATA +14 -22
- {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/RECORD +144 -58
- {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/WHEEL +1 -1
- aip_agents/examples/demo_memory_recall.py +0 -401
- aip_agents/examples/demo_memory_recall.pyi +0 -58
- aip_agents/examples/hello_world_langgraph_bosa_twitter.pyi +0 -5
- aip_agents/tools/bosa_tools.py +0 -105
- aip_agents/tools/bosa_tools.pyi +0 -37
- {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/top_level.txt +0 -0
|
@@ -6,8 +6,11 @@ Authors:
|
|
|
6
6
|
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
7
7
|
Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
8
8
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
Reinhart Linanda (reinhart.linanda@gdplabs.id)
|
|
9
10
|
"""
|
|
10
11
|
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
11
14
|
import asyncio
|
|
12
15
|
import time
|
|
13
16
|
import uuid
|
|
@@ -15,11 +18,14 @@ from collections.abc import Awaitable, Callable, Sequence
|
|
|
15
18
|
from dataclasses import asdict, dataclass
|
|
16
19
|
from functools import reduce
|
|
17
20
|
from textwrap import dedent
|
|
18
|
-
from typing import Annotated, Any
|
|
21
|
+
from typing import TYPE_CHECKING, Annotated, Any, cast
|
|
22
|
+
|
|
23
|
+
from deprecated import deprecated # type: ignore[import-untyped]
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
from
|
|
22
|
-
from gllm_core.
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from aip_agents.guardrails.manager import GuardrailManager
|
|
27
|
+
from gllm_core.event import EventEmitter # type: ignore[import-untyped]
|
|
28
|
+
from gllm_core.schema import Chunk # type: ignore[import-untyped]
|
|
23
29
|
from langchain_core.language_models import BaseChatModel
|
|
24
30
|
from langchain_core.messages import (
|
|
25
31
|
AIMessage,
|
|
@@ -48,7 +54,7 @@ from aip_agents.schema.a2a import A2AStreamEventType
|
|
|
48
54
|
from aip_agents.schema.hitl import ApprovalDecision, HitlMetadata
|
|
49
55
|
from aip_agents.schema.langgraph import ToolCallResult, ToolStorageParams
|
|
50
56
|
from aip_agents.schema.step_limit import MaxStepsExceededError, StepLimitConfig
|
|
51
|
-
from aip_agents.tools.memory_search_tool import MEMORY_SEARCH_TOOL_NAME
|
|
57
|
+
from aip_agents.tools.memory_search_tool import MEMORY_DELETE_TOOL_NAME, MEMORY_SEARCH_TOOL_NAME
|
|
52
58
|
from aip_agents.tools.tool_config_injector import TOOL_CONFIGS_KEY
|
|
53
59
|
from aip_agents.utils import add_references_chunks
|
|
54
60
|
from aip_agents.utils.langgraph import (
|
|
@@ -81,6 +87,9 @@ from aip_agents.utils.token_usage_helper import (
|
|
|
81
87
|
extract_token_usage_from_tool_output,
|
|
82
88
|
)
|
|
83
89
|
|
|
90
|
+
if TYPE_CHECKING:
|
|
91
|
+
from aip_agents.ptc import PTCSandboxConfig
|
|
92
|
+
|
|
84
93
|
logger = get_logger(__name__)
|
|
85
94
|
|
|
86
95
|
# Default instruction for ReAct agents
|
|
@@ -157,7 +166,9 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
157
166
|
tool_output_manager: ToolOutputManager | None = None,
|
|
158
167
|
planning: bool = False,
|
|
159
168
|
middlewares: Sequence[AgentMiddleware] | None = None,
|
|
169
|
+
guardrail: GuardrailManager | None = None,
|
|
160
170
|
step_limit_config: StepLimitConfig | None = None,
|
|
171
|
+
ptc_config: PTCSandboxConfig | None = None,
|
|
161
172
|
**kwargs: Any,
|
|
162
173
|
):
|
|
163
174
|
"""Initialize the LangGraph ReAct Agent.
|
|
@@ -178,10 +189,19 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
178
189
|
planning: Enable planning capabilities with TodoListMiddleware. Defaults to False.
|
|
179
190
|
middlewares: Optional sequence of custom middleware to COMPOSE (not override) with built-in middleware.
|
|
180
191
|
Execution order: [TodoListMiddleware (if planning=True),
|
|
192
|
+
GuardrailMiddleware (if guardrail provided),
|
|
181
193
|
...custom middlewares in order provided]
|
|
182
194
|
All middleware hooks execute - this extends capabilities, never replaces them.
|
|
195
|
+
guardrail: Optional GuardrailManager for content filtering and safety checks.
|
|
196
|
+
When provided, automatically wraps in GuardrailMiddleware for transparent
|
|
197
|
+
input/output filtering during agent execution.
|
|
183
198
|
enable_pii: Optional toggle to enable PII handling for tool inputs and outputs.
|
|
184
199
|
step_limit_config: Optional configuration for step limits and delegation depth.
|
|
200
|
+
ptc_config: Optional configuration for PTC sandbox execution. See PTCSandboxConfig
|
|
201
|
+
for available options including enabled flag, sandbox timeout, and template settings.
|
|
202
|
+
PTC is enabled when ptc_config is not None and ptc_config.enabled is True.
|
|
203
|
+
When enabled, prompt guidance is automatically injected into the agent's instruction.
|
|
204
|
+
PTC runs in a sandbox only; there is no in-process trusted PTC path.
|
|
185
205
|
**kwargs: Additional keyword arguments passed to BaseLangGraphAgent.
|
|
186
206
|
"""
|
|
187
207
|
# Use LangGraph's standard AgentState for ReAct
|
|
@@ -201,6 +221,12 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
201
221
|
**kwargs,
|
|
202
222
|
)
|
|
203
223
|
|
|
224
|
+
if self.model is None and self.lm_invoker is None:
|
|
225
|
+
logger.warning(
|
|
226
|
+
"Agent '%s': Model and LM invoker are both unset. Calls that require a model will fail.",
|
|
227
|
+
self.name,
|
|
228
|
+
)
|
|
229
|
+
|
|
204
230
|
# Handle tool output management
|
|
205
231
|
self.tool_output_manager = tool_output_manager
|
|
206
232
|
self._pii_handlers_by_thread: dict[str, ToolPIIHandler] = {}
|
|
@@ -212,6 +238,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
212
238
|
# Setup middleware
|
|
213
239
|
self._middleware_manager = self._setup_middleware(
|
|
214
240
|
planning=planning,
|
|
241
|
+
guardrail=guardrail,
|
|
215
242
|
custom_middlewares=middlewares,
|
|
216
243
|
)
|
|
217
244
|
|
|
@@ -221,18 +248,32 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
221
248
|
|
|
222
249
|
self.step_limit_config = step_limit_config
|
|
223
250
|
|
|
251
|
+
# Initialize PTC state (Programmatic Tool Calling)
|
|
252
|
+
self._ptc_config: PTCSandboxConfig | None = None
|
|
253
|
+
self._ptc_tool_synced = False
|
|
254
|
+
self._ptc_tool: BaseTool | None = None
|
|
255
|
+
self._ptc_prompt_hash: str = ""
|
|
256
|
+
# Capture instruction after middleware setup so middleware prompts are preserved
|
|
257
|
+
self._original_instruction: str = self.instruction
|
|
258
|
+
|
|
259
|
+
# Enable PTC if requested via constructor
|
|
260
|
+
if ptc_config is not None and ptc_config.enabled:
|
|
261
|
+
self.enable_ptc(ptc_config)
|
|
262
|
+
|
|
224
263
|
def _setup_middleware(
|
|
225
264
|
self,
|
|
226
265
|
planning: bool,
|
|
266
|
+
guardrail: GuardrailManager | None,
|
|
227
267
|
custom_middlewares: Sequence[AgentMiddleware] | None,
|
|
228
268
|
) -> MiddlewareManager | None:
|
|
229
269
|
"""Setup middleware based on configuration.
|
|
230
270
|
|
|
231
|
-
Creates auto-configured middleware (planning) and composes
|
|
271
|
+
Creates auto-configured middleware (planning, guardrails) and composes
|
|
232
272
|
with custom middleware if provided.
|
|
233
273
|
|
|
234
274
|
Args:
|
|
235
275
|
planning: Whether to enable TodoListMiddleware.
|
|
276
|
+
guardrail: Optional GuardrailManager to wrap in GuardrailMiddleware.
|
|
236
277
|
custom_middlewares: Optional custom middlewares to append.
|
|
237
278
|
|
|
238
279
|
Returns:
|
|
@@ -242,7 +283,13 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
242
283
|
|
|
243
284
|
# Auto-configure TodoListMiddleware if planning enabled
|
|
244
285
|
if planning:
|
|
245
|
-
middleware_list.append(TodoListMiddleware())
|
|
286
|
+
middleware_list.append(cast(AgentMiddleware, TodoListMiddleware()))
|
|
287
|
+
|
|
288
|
+
# Auto-configure GuardrailMiddleware if guardrail provided
|
|
289
|
+
if guardrail:
|
|
290
|
+
from aip_agents.guardrails.middleware import GuardrailMiddleware
|
|
291
|
+
|
|
292
|
+
middleware_list.append(GuardrailMiddleware(guardrail))
|
|
246
293
|
|
|
247
294
|
# Append custom middlewares
|
|
248
295
|
if custom_middlewares:
|
|
@@ -399,9 +446,9 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
399
446
|
)
|
|
400
447
|
|
|
401
448
|
def _rebuild_resolved_tools(self) -> None:
|
|
402
|
-
"""Rebuild resolved tools including middleware tools.
|
|
449
|
+
"""Rebuild resolved tools including middleware and PTC tools.
|
|
403
450
|
|
|
404
|
-
Overrides base class to ensure middleware tools are preserved
|
|
451
|
+
Overrides base class to ensure middleware tools and the PTC tool are preserved
|
|
405
452
|
when tools are rebuilt (e.g., after update_regular_tools).
|
|
406
453
|
"""
|
|
407
454
|
# Call base class to rebuild with regular, a2a, delegation, and mcp tools
|
|
@@ -411,6 +458,10 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
411
458
|
if hasattr(self, "_middleware_tools") and self._middleware_tools:
|
|
412
459
|
self.resolved_tools.extend(self._middleware_tools)
|
|
413
460
|
|
|
461
|
+
# Add PTC tool if synced
|
|
462
|
+
if hasattr(self, "_ptc_tool") and self._ptc_tool is not None:
|
|
463
|
+
self.resolved_tools.append(self._ptc_tool)
|
|
464
|
+
|
|
414
465
|
def _handle_tool_artifacts(
|
|
415
466
|
self, tool_output: Any, pending_artifacts: list[dict[str, Any]]
|
|
416
467
|
) -> tuple[str, list[dict[str, Any]]]:
|
|
@@ -533,6 +584,29 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
533
584
|
|
|
534
585
|
return memory_node
|
|
535
586
|
|
|
587
|
+
def _should_save_interaction(self, final_state: dict[str, Any] | None) -> bool:
|
|
588
|
+
"""Return True when interaction should be saved to memory."""
|
|
589
|
+
if self._contains_memory_delete_action(final_state):
|
|
590
|
+
logger.info("Memory: Skipping save_interaction due to memory delete action in state.")
|
|
591
|
+
return False
|
|
592
|
+
return True
|
|
593
|
+
|
|
594
|
+
@staticmethod
|
|
595
|
+
def _contains_memory_delete_action(final_state: dict[str, Any] | None) -> bool:
|
|
596
|
+
"""Return True when final state includes a delete memory action block."""
|
|
597
|
+
if not isinstance(final_state, dict):
|
|
598
|
+
return False
|
|
599
|
+
messages = final_state.get("messages")
|
|
600
|
+
if not isinstance(messages, list):
|
|
601
|
+
return False
|
|
602
|
+
for message in messages:
|
|
603
|
+
content = getattr(message, "content", None)
|
|
604
|
+
if not isinstance(content, str):
|
|
605
|
+
continue
|
|
606
|
+
if "<MEMORY_ACTION>" in content and "action=delete" in content:
|
|
607
|
+
return True
|
|
608
|
+
return False
|
|
609
|
+
|
|
536
610
|
def _extract_user_query_from_messages(self, messages: list[Any]) -> str | None:
|
|
537
611
|
"""Get latest user query string from a list of messages.
|
|
538
612
|
|
|
@@ -579,14 +653,29 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
579
653
|
current_messages = state["messages"]
|
|
580
654
|
|
|
581
655
|
# Execute LLM call
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
656
|
+
try:
|
|
657
|
+
if self.lm_invoker:
|
|
658
|
+
result = await self._handle_lm_invoker_call(current_messages, state, config)
|
|
659
|
+
elif isinstance(self.model, BaseChatModel):
|
|
660
|
+
result = await self._handle_langchain_model_call(current_messages, state, config)
|
|
661
|
+
else:
|
|
662
|
+
raise ValueError(
|
|
663
|
+
f"Agent '{self.name}': No valid LMInvoker or LangChain model configured for ReAct agent node."
|
|
664
|
+
)
|
|
665
|
+
except Exception as e:
|
|
666
|
+
# Lazy import to support optional guardrails dependency
|
|
667
|
+
from aip_agents.guardrails.exceptions import GuardrailViolationError
|
|
668
|
+
|
|
669
|
+
if isinstance(e, GuardrailViolationError):
|
|
670
|
+
return {
|
|
671
|
+
"messages": [
|
|
672
|
+
AIMessage(
|
|
673
|
+
content=f"⚠️ Guardrail violation: {e.result.reason}",
|
|
674
|
+
response_metadata={"finish_reason": "stop"},
|
|
675
|
+
)
|
|
676
|
+
]
|
|
677
|
+
}
|
|
678
|
+
raise
|
|
590
679
|
|
|
591
680
|
# Increment step counter after successful execution
|
|
592
681
|
manager.increment_step()
|
|
@@ -697,7 +786,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
697
786
|
pending_artifacts: list[dict[str, Any]] = state.get("artifacts") or []
|
|
698
787
|
reference_updates: list[Chunk] = []
|
|
699
788
|
tool_map = {tool.name: tool for tool in self.resolved_tools}
|
|
700
|
-
pii_mapping = {}
|
|
789
|
+
pii_mapping: dict[str, str] = {}
|
|
701
790
|
|
|
702
791
|
aggregated_metadata_delta: dict[str, Any] = {}
|
|
703
792
|
total_tools_token_usage: list[UsageMetadata] = []
|
|
@@ -721,7 +810,8 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
721
810
|
),
|
|
722
811
|
)
|
|
723
812
|
|
|
724
|
-
|
|
813
|
+
normalized_tool_calls = [self._normalize_tool_call(tc) for tc in last_message.tool_calls]
|
|
814
|
+
tasks = [asyncio.create_task(run_tool(tc)) for tc in normalized_tool_calls]
|
|
725
815
|
|
|
726
816
|
for coro in asyncio.as_completed(tasks):
|
|
727
817
|
tool_result = await coro
|
|
@@ -744,6 +834,31 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
744
834
|
pii_mapping,
|
|
745
835
|
)
|
|
746
836
|
|
|
837
|
+
def _normalize_tool_call(self, tool_call: Any) -> dict[str, Any]:
|
|
838
|
+
"""Normalize tool call inputs into a dict with required keys."""
|
|
839
|
+
if isinstance(tool_call, dict):
|
|
840
|
+
normalized = dict(tool_call)
|
|
841
|
+
elif hasattr(tool_call, "model_dump"):
|
|
842
|
+
normalized = tool_call.model_dump()
|
|
843
|
+
elif hasattr(tool_call, "dict"):
|
|
844
|
+
normalized = tool_call.dict()
|
|
845
|
+
elif hasattr(tool_call, "name") and hasattr(tool_call, "args"):
|
|
846
|
+
normalized = {
|
|
847
|
+
"id": getattr(tool_call, "id", None),
|
|
848
|
+
"name": getattr(tool_call, "name", None),
|
|
849
|
+
"args": getattr(tool_call, "args", None),
|
|
850
|
+
}
|
|
851
|
+
else:
|
|
852
|
+
raise TypeError("Tool call must be a dict-like object or ToolCall instance.")
|
|
853
|
+
|
|
854
|
+
if not isinstance(normalized, dict):
|
|
855
|
+
raise TypeError("Tool call normalization did not produce a dict.")
|
|
856
|
+
|
|
857
|
+
if "name" not in normalized or "args" not in normalized:
|
|
858
|
+
raise TypeError("Tool call must include 'name' and 'args' fields.")
|
|
859
|
+
|
|
860
|
+
return normalized
|
|
861
|
+
|
|
747
862
|
def _accumulate_tool_result( # noqa: PLR0913
|
|
748
863
|
self,
|
|
749
864
|
tool_result: Any,
|
|
@@ -752,7 +867,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
752
867
|
aggregated_metadata_delta: dict[str, Any],
|
|
753
868
|
reference_updates: list[Chunk],
|
|
754
869
|
total_tools_token_usage: list[UsageMetadata],
|
|
755
|
-
pii_mapping: dict[str, str]
|
|
870
|
+
pii_mapping: dict[str, str],
|
|
756
871
|
) -> None: # noqa: PLR0913
|
|
757
872
|
"""Accumulate results from a single tool call.
|
|
758
873
|
|
|
@@ -1198,13 +1313,16 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1198
1313
|
|
|
1199
1314
|
# Create enhanced tool configuration with output management
|
|
1200
1315
|
tool_config = self._create_enhanced_tool_config(config, state, tool_call["name"], tool_call_id)
|
|
1316
|
+
if not isinstance(tool_config, dict):
|
|
1317
|
+
raise TypeError("Tool configuration must be a dictionary.")
|
|
1318
|
+
tool_config_runnable = tool_config
|
|
1201
1319
|
|
|
1202
1320
|
arun_streaming_method = getattr(tool, TOOL_RUN_STREAMING_METHOD, None)
|
|
1203
1321
|
|
|
1204
1322
|
if arun_streaming_method and callable(arun_streaming_method):
|
|
1205
1323
|
tool_output = await self._execute_tool_with_streaming(tool, tool_call, tool_config)
|
|
1206
1324
|
else:
|
|
1207
|
-
tool_output = await tool.ainvoke(resolved_args,
|
|
1325
|
+
tool_output = await tool.ainvoke(resolved_args, tool_config_runnable)
|
|
1208
1326
|
|
|
1209
1327
|
references = extract_references_from_tool(tool, tool_output)
|
|
1210
1328
|
|
|
@@ -1478,7 +1596,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1478
1596
|
tool_call: dict[str, Any],
|
|
1479
1597
|
execution_time: float,
|
|
1480
1598
|
pending_artifacts: list[dict[str, Any]],
|
|
1481
|
-
) -> tuple[list[
|
|
1599
|
+
) -> tuple[list[ToolMessage], list[dict[str, Any]], dict[str, Any]]:
|
|
1482
1600
|
"""Process tool output into messages, artifacts, and metadata.
|
|
1483
1601
|
|
|
1484
1602
|
Args:
|
|
@@ -1506,7 +1624,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1506
1624
|
|
|
1507
1625
|
def _handle_command_output(
|
|
1508
1626
|
self, tool_output: Command, tool_call: dict[str, Any], execution_time: float, metadata_delta: dict[str, Any]
|
|
1509
|
-
) -> tuple[list[
|
|
1627
|
+
) -> tuple[list[ToolMessage], list[dict[str, Any]], dict[str, Any]]:
|
|
1510
1628
|
"""Handle Command type tool outputs.
|
|
1511
1629
|
|
|
1512
1630
|
Args:
|
|
@@ -1535,7 +1653,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1535
1653
|
|
|
1536
1654
|
def _handle_string_output(
|
|
1537
1655
|
self, tool_output: str, tool_call: dict[str, Any], execution_time: float
|
|
1538
|
-
) -> tuple[list[
|
|
1656
|
+
) -> tuple[list[ToolMessage], list[dict[str, Any]], dict[str, Any]]:
|
|
1539
1657
|
"""Handle string type tool outputs.
|
|
1540
1658
|
|
|
1541
1659
|
Args:
|
|
@@ -1561,7 +1679,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1561
1679
|
execution_time: float,
|
|
1562
1680
|
pending_artifacts: list[dict[str, Any]],
|
|
1563
1681
|
metadata_delta: dict[str, Any],
|
|
1564
|
-
) -> tuple[list[
|
|
1682
|
+
) -> tuple[list[ToolMessage], list[dict[str, Any]], dict[str, Any]]:
|
|
1565
1683
|
"""Handle legacy dict and other tool outputs.
|
|
1566
1684
|
|
|
1567
1685
|
Args:
|
|
@@ -1659,8 +1777,11 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1659
1777
|
self._emit_default_tool_call_event(writer, tool_name, tool_call_id, tool_args)
|
|
1660
1778
|
|
|
1661
1779
|
streaming_kwargs = self._build_streaming_kwargs(tool_args, tool_config)
|
|
1780
|
+
arun_streaming_method = getattr(tool, TOOL_RUN_STREAMING_METHOD, None)
|
|
1781
|
+
if not callable(arun_streaming_method):
|
|
1782
|
+
raise RuntimeError(f"Tool '{tool_name}' does not implement streaming.")
|
|
1662
1783
|
|
|
1663
|
-
async for chunk in
|
|
1784
|
+
async for chunk in arun_streaming_method(**streaming_kwargs):
|
|
1664
1785
|
final_output, saw_tool_result = self._handle_streaming_chunk(
|
|
1665
1786
|
chunk=chunk,
|
|
1666
1787
|
writer=writer,
|
|
@@ -1954,6 +2075,47 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1954
2075
|
)
|
|
1955
2076
|
writer(a2a_event)
|
|
1956
2077
|
|
|
2078
|
+
async def _execute_abefore_model_hook(self, state: dict[str, Any]) -> None:
|
|
2079
|
+
"""Asynchronously execute abefore_model middleware hook and update state.
|
|
2080
|
+
|
|
2081
|
+
Args:
|
|
2082
|
+
state: Current agent state to potentially update.
|
|
2083
|
+
"""
|
|
2084
|
+
if self._middleware_manager:
|
|
2085
|
+
try:
|
|
2086
|
+
before_updates = await self._middleware_manager.abefore_model(state)
|
|
2087
|
+
if before_updates:
|
|
2088
|
+
state.update(before_updates)
|
|
2089
|
+
except Exception as e:
|
|
2090
|
+
# Lazy import to support optional guardrails dependency
|
|
2091
|
+
from aip_agents.guardrails.exceptions import GuardrailViolationError
|
|
2092
|
+
|
|
2093
|
+
if isinstance(e, GuardrailViolationError):
|
|
2094
|
+
# Re-raise guardrail violations to be caught by the agent node
|
|
2095
|
+
raise
|
|
2096
|
+
logger.error(f"Agent '{self.name}': Middleware abefore_model hook failed: {e}")
|
|
2097
|
+
|
|
2098
|
+
async def _execute_aafter_model_hook(self, state_updates: dict[str, Any], state: dict[str, Any]) -> None:
|
|
2099
|
+
"""Asynchronously execute aafter_model middleware hook.
|
|
2100
|
+
|
|
2101
|
+
Args:
|
|
2102
|
+
state_updates: Updates to be merged into state.
|
|
2103
|
+
state: Current agent state for context.
|
|
2104
|
+
"""
|
|
2105
|
+
if self._middleware_manager:
|
|
2106
|
+
try:
|
|
2107
|
+
after_updates = await self._middleware_manager.aafter_model(state)
|
|
2108
|
+
if after_updates:
|
|
2109
|
+
state_updates.update(after_updates)
|
|
2110
|
+
except Exception as e:
|
|
2111
|
+
# Lazy import to support optional guardrails dependency
|
|
2112
|
+
from aip_agents.guardrails.exceptions import GuardrailViolationError
|
|
2113
|
+
|
|
2114
|
+
if isinstance(e, GuardrailViolationError):
|
|
2115
|
+
# Re-raise guardrail violations
|
|
2116
|
+
raise
|
|
2117
|
+
logger.error(f"Agent '{self.name}': Middleware aafter_model hook failed: {e}")
|
|
2118
|
+
|
|
1957
2119
|
def _execute_before_model_hook(self, state: dict[str, Any]) -> None:
|
|
1958
2120
|
"""Execute before_model middleware hook and update state.
|
|
1959
2121
|
|
|
@@ -1966,6 +2128,12 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
1966
2128
|
if before_updates:
|
|
1967
2129
|
state.update(before_updates)
|
|
1968
2130
|
except Exception as e:
|
|
2131
|
+
# Lazy import to support optional guardrails dependency
|
|
2132
|
+
from aip_agents.guardrails.exceptions import GuardrailViolationError
|
|
2133
|
+
|
|
2134
|
+
if isinstance(e, GuardrailViolationError):
|
|
2135
|
+
# Re-raise guardrail violations to be caught by the agent node
|
|
2136
|
+
raise
|
|
1969
2137
|
logger.error(f"Agent '{self.name}': Middleware before_model hook failed: {e}")
|
|
1970
2138
|
|
|
1971
2139
|
def _execute_modify_model_request_hook(
|
|
@@ -2029,7 +2197,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2029
2197
|
dict[str, Any]: A dictionary containing the new messages and updated token usage.
|
|
2030
2198
|
"""
|
|
2031
2199
|
# Execute before_model middleware hook
|
|
2032
|
-
self.
|
|
2200
|
+
await self._execute_abefore_model_hook(state)
|
|
2033
2201
|
|
|
2034
2202
|
# Build tool output aware instruction
|
|
2035
2203
|
enhanced_instruction = self._build_tool_output_aware_instruction(self.instruction, state, config)
|
|
@@ -2043,6 +2211,9 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2043
2211
|
|
|
2044
2212
|
effective_event_emitter = state.get("event_emitter") or self.event_emitter
|
|
2045
2213
|
|
|
2214
|
+
if self.lm_invoker is None:
|
|
2215
|
+
raise RuntimeError("LM invoker is required for this execution path.")
|
|
2216
|
+
|
|
2046
2217
|
if self.resolved_tools:
|
|
2047
2218
|
self.lm_invoker.set_tools(self.resolved_tools)
|
|
2048
2219
|
|
|
@@ -2063,7 +2234,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2063
2234
|
state_updates.update(token_usage_updates)
|
|
2064
2235
|
|
|
2065
2236
|
# Execute after_model middleware hook
|
|
2066
|
-
self.
|
|
2237
|
+
await self._execute_aafter_model_hook(state_updates, state)
|
|
2067
2238
|
|
|
2068
2239
|
return state_updates
|
|
2069
2240
|
|
|
@@ -2081,7 +2252,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2081
2252
|
dict[str, Any]: A dictionary containing the new messages and updated token usage.
|
|
2082
2253
|
"""
|
|
2083
2254
|
# Execute before_model middleware hook
|
|
2084
|
-
self.
|
|
2255
|
+
await self._execute_abefore_model_hook(state)
|
|
2085
2256
|
|
|
2086
2257
|
# Build tool output aware instruction
|
|
2087
2258
|
enhanced_instruction = self._build_tool_output_aware_instruction(self.instruction, state, config)
|
|
@@ -2101,6 +2272,9 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2101
2272
|
):
|
|
2102
2273
|
langchain_prompt = [SystemMessage(content=enhanced_instruction)] + list(current_messages)
|
|
2103
2274
|
|
|
2275
|
+
if self.model is None:
|
|
2276
|
+
raise RuntimeError("Model is required for this execution path.")
|
|
2277
|
+
|
|
2104
2278
|
model_with_tools = self.model.bind_tools(self.resolved_tools) if self.resolved_tools else self.model
|
|
2105
2279
|
|
|
2106
2280
|
ai_message = await model_with_tools.ainvoke(langchain_prompt, config)
|
|
@@ -2113,7 +2287,7 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2113
2287
|
state_updates.update(token_usage_updates)
|
|
2114
2288
|
|
|
2115
2289
|
# Execute after_model middleware hook
|
|
2116
|
-
self.
|
|
2290
|
+
await self._execute_aafter_model_hook(state_updates, state)
|
|
2117
2291
|
|
|
2118
2292
|
return state_updates
|
|
2119
2293
|
|
|
@@ -2126,11 +2300,12 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2126
2300
|
"""
|
|
2127
2301
|
try:
|
|
2128
2302
|
tool_cfgs = metadata.get(TOOL_CONFIGS_KEY, {})
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
per_tool_config
|
|
2132
|
-
|
|
2133
|
-
|
|
2303
|
+
for tool_name in (MEMORY_SEARCH_TOOL_NAME, MEMORY_DELETE_TOOL_NAME):
|
|
2304
|
+
per_tool_config = tool_cfgs.get(tool_name)
|
|
2305
|
+
if not isinstance(per_tool_config, dict):
|
|
2306
|
+
per_tool_config = {}
|
|
2307
|
+
per_tool_config["user_id"] = memory_user_id
|
|
2308
|
+
tool_cfgs[tool_name] = per_tool_config
|
|
2134
2309
|
metadata[TOOL_CONFIGS_KEY] = tool_cfgs
|
|
2135
2310
|
except Exception as e:
|
|
2136
2311
|
# Non-fatal; metadata injection is best-effort
|
|
@@ -2494,6 +2669,220 @@ class LangGraphReactAgent(LangGraphHitLMixin, BaseLangGraphAgent):
|
|
|
2494
2669
|
if current_thread_id:
|
|
2495
2670
|
self._pii_handlers_by_thread.pop(current_thread_id, None)
|
|
2496
2671
|
|
|
2672
|
+
# ==========================================================================
|
|
2673
|
+
# Programmatic Tool Calling (PTC) Methods
|
|
2674
|
+
# ==========================================================================
|
|
2675
|
+
|
|
2676
|
+
def add_mcp_server(self, mcp_config: dict[str, dict[str, Any]]) -> None:
|
|
2677
|
+
"""Add MCP servers and refresh PTC tool state if needed."""
|
|
2678
|
+
super().add_mcp_server(mcp_config)
|
|
2679
|
+
|
|
2680
|
+
if not self._ptc_config or not self._ptc_config.enabled:
|
|
2681
|
+
return
|
|
2682
|
+
|
|
2683
|
+
if self._ptc_tool is not None:
|
|
2684
|
+
self._ptc_tool = None
|
|
2685
|
+
|
|
2686
|
+
self._ptc_tool_synced = False
|
|
2687
|
+
logger.debug(f"Agent '{self.name}': PTC tool will resync after MCP changes")
|
|
2688
|
+
|
|
2689
|
+
def enable_ptc(self, config: PTCSandboxConfig | None = None) -> None:
|
|
2690
|
+
"""Enable Programmatic Tool Calling (PTC) for this agent.
|
|
2691
|
+
|
|
2692
|
+
PTC allows the LLM to execute Python code that calls MCP tools
|
|
2693
|
+
programmatically inside a sandboxed environment. This is useful for
|
|
2694
|
+
chaining multiple tool calls with local data processing.
|
|
2695
|
+
|
|
2696
|
+
The execute_ptc_code tool is automatically added to the agent's tools
|
|
2697
|
+
after MCP servers are configured. If no MCP servers are configured,
|
|
2698
|
+
the tool sync is deferred until servers are added.
|
|
2699
|
+
|
|
2700
|
+
Args:
|
|
2701
|
+
config: Optional configuration for PTC sandbox execution.
|
|
2702
|
+
See PTCSandboxConfig for options like enabled flag and sandbox_timeout.
|
|
2703
|
+
If None is passed, a default config with enabled=True will be created.
|
|
2704
|
+
|
|
2705
|
+
Example:
|
|
2706
|
+
agent.enable_ptc(PTCSandboxConfig(enabled=True))
|
|
2707
|
+
agent.add_mcp_server({"yfinance": {...}})
|
|
2708
|
+
# execute_ptc_code tool is now available
|
|
2709
|
+
|
|
2710
|
+
Note:
|
|
2711
|
+
PTC can also be enabled via the constructor by passing
|
|
2712
|
+
ptc_config=PTCSandboxConfig(enabled=True, ...).
|
|
2713
|
+
"""
|
|
2714
|
+
# Lazy import to avoid circular dependencies
|
|
2715
|
+
from aip_agents.ptc.executor import PTCSandboxConfig
|
|
2716
|
+
|
|
2717
|
+
self._ptc_config = config or PTCSandboxConfig()
|
|
2718
|
+
self._ptc_config.enabled = True
|
|
2719
|
+
self._ptc_tool_synced = False
|
|
2720
|
+
|
|
2721
|
+
logger.info(f"Agent '{self.name}': PTC enabled")
|
|
2722
|
+
|
|
2723
|
+
# Attempt to sync PTC tool if MCP client is available
|
|
2724
|
+
self._sync_ptc_tool()
|
|
2725
|
+
|
|
2726
|
+
def _enrich_custom_tool_metadata(self) -> None:
|
|
2727
|
+
"""Enrich custom tool definitions with metadata from agent's tools.
|
|
2728
|
+
|
|
2729
|
+
This method matches custom tool definitions in ptc_config with actual tool
|
|
2730
|
+
objects from self.tools and enriches them with description and input_schema.
|
|
2731
|
+
Called at PTC sync time to ensure tool definitions have accurate metadata.
|
|
2732
|
+
|
|
2733
|
+
The matching is done by comparing tool names (both original and sanitized).
|
|
2734
|
+
|
|
2735
|
+
Note: This method modifies self._ptc_config.custom_tools.tools in-place.
|
|
2736
|
+
"""
|
|
2737
|
+
if not self._ptc_config:
|
|
2738
|
+
return
|
|
2739
|
+
|
|
2740
|
+
# Lazy import to avoid circular dependencies
|
|
2741
|
+
from aip_agents.ptc import enrich_custom_tools_from_agent
|
|
2742
|
+
|
|
2743
|
+
enrich_custom_tools_from_agent(
|
|
2744
|
+
self._ptc_config.custom_tools,
|
|
2745
|
+
self.tools,
|
|
2746
|
+
agent_name=self.name,
|
|
2747
|
+
)
|
|
2748
|
+
|
|
2749
|
+
def _sync_ptc_tool(self) -> None:
|
|
2750
|
+
"""Build and register the execute_ptc_code tool when MCP or custom tools are available.
|
|
2751
|
+
|
|
2752
|
+
This method is called after enable_ptc() and after MCP servers are added.
|
|
2753
|
+
It creates the execute_ptc_code tool using the current MCP client
|
|
2754
|
+
configuration and/or custom tools config and adds it to the agent's resolved tools.
|
|
2755
|
+
|
|
2756
|
+
The tool is only created once. Subsequent calls are no-ops if the tool
|
|
2757
|
+
has already been synced.
|
|
2758
|
+
"""
|
|
2759
|
+
if not self._ptc_config or not self._ptc_config.enabled:
|
|
2760
|
+
return
|
|
2761
|
+
|
|
2762
|
+
if self._ptc_tool_synced:
|
|
2763
|
+
return
|
|
2764
|
+
|
|
2765
|
+
# Check if we have custom tools enabled
|
|
2766
|
+
has_custom_tools = self._ptc_config.custom_tools.enabled and self._ptc_config.custom_tools.tools
|
|
2767
|
+
has_mcp_servers = bool(self.mcp_config)
|
|
2768
|
+
|
|
2769
|
+
if has_mcp_servers:
|
|
2770
|
+
if not self.mcp_client:
|
|
2771
|
+
logger.debug(f"Agent '{self.name}': PTC tool sync deferred - no MCP client yet")
|
|
2772
|
+
return
|
|
2773
|
+
|
|
2774
|
+
if not self.mcp_client.is_initialized:
|
|
2775
|
+
logger.debug(f"Agent '{self.name}': PTC tool sync deferred - MCP client not initialized")
|
|
2776
|
+
return
|
|
2777
|
+
|
|
2778
|
+
mcp_client_to_use = self.mcp_client
|
|
2779
|
+
elif has_custom_tools:
|
|
2780
|
+
mcp_client_to_use = None
|
|
2781
|
+
else:
|
|
2782
|
+
return
|
|
2783
|
+
|
|
2784
|
+
# Enrich custom tool definitions with metadata from actual tool objects
|
|
2785
|
+
if has_custom_tools:
|
|
2786
|
+
self._enrich_custom_tool_metadata()
|
|
2787
|
+
|
|
2788
|
+
# Lazy import to avoid circular dependencies
|
|
2789
|
+
from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool
|
|
2790
|
+
|
|
2791
|
+
if has_custom_tools and not mcp_client_to_use:
|
|
2792
|
+
logger.info(f"Agent '{self.name}': Syncing PTC tool with custom tools only (no MCP)")
|
|
2793
|
+
else:
|
|
2794
|
+
logger.info(f"Agent '{self.name}': Syncing PTC tool with MCP client")
|
|
2795
|
+
|
|
2796
|
+
# Create the execute_ptc_code tool with agent's tool configs
|
|
2797
|
+
self._ptc_tool = create_execute_ptc_code_tool(
|
|
2798
|
+
mcp_client_to_use, self._ptc_config, agent_tool_configs=self.tool_configs
|
|
2799
|
+
)
|
|
2800
|
+
|
|
2801
|
+
# Rebuild graph to include PTC tool
|
|
2802
|
+
self._rebuild_graph()
|
|
2803
|
+
|
|
2804
|
+
self._ptc_tool_synced = True
|
|
2805
|
+
logger.info(f"Agent '{self.name}': PTC tool synced successfully")
|
|
2806
|
+
|
|
2807
|
+
# Sync PTC prompt guidance
|
|
2808
|
+
self._sync_ptc_prompt()
|
|
2809
|
+
|
|
2810
|
+
def _sync_ptc_prompt(self) -> None:
|
|
2811
|
+
"""Sync PTC usage guidance into the agent instruction.
|
|
2812
|
+
|
|
2813
|
+
This method builds and injects a PTC usage block into the agent's
|
|
2814
|
+
instruction when PTC is enabled. The prompt is refreshed when MCP
|
|
2815
|
+
or custom tools configuration changes (detected via hash).
|
|
2816
|
+
"""
|
|
2817
|
+
if not self._ptc_config or not self._ptc_config.enabled:
|
|
2818
|
+
return
|
|
2819
|
+
|
|
2820
|
+
# Check if we have custom tools enabled
|
|
2821
|
+
has_custom_tools = self._ptc_config.custom_tools.enabled and self._ptc_config.custom_tools.tools
|
|
2822
|
+
|
|
2823
|
+
# For custom-only configs, allow None mcp_client
|
|
2824
|
+
# For MCP-only or MCP+custom, require mcp_client
|
|
2825
|
+
if not has_custom_tools and not self.mcp_client:
|
|
2826
|
+
return
|
|
2827
|
+
|
|
2828
|
+
# Lazy import to avoid circular dependencies
|
|
2829
|
+
from aip_agents.ptc.prompt_builder import build_ptc_prompt, compute_ptc_prompt_hash
|
|
2830
|
+
|
|
2831
|
+
# Get prompt config and custom tools config from PTC sandbox config
|
|
2832
|
+
prompt_config = self._ptc_config.prompt if self._ptc_config else None
|
|
2833
|
+
custom_tools_config = self._ptc_config.custom_tools if self._ptc_config else None
|
|
2834
|
+
|
|
2835
|
+
# Use mcp_client if available, None for custom-only
|
|
2836
|
+
mcp_client_to_use = self.mcp_client if self.mcp_client else None
|
|
2837
|
+
|
|
2838
|
+
# Check if MCP or custom tools config has changed
|
|
2839
|
+
current_hash = compute_ptc_prompt_hash(
|
|
2840
|
+
mcp_client_to_use, config=prompt_config, custom_tools_config=custom_tools_config
|
|
2841
|
+
)
|
|
2842
|
+
if current_hash == self._ptc_prompt_hash:
|
|
2843
|
+
logger.debug(f"Agent '{self.name}': PTC prompt unchanged, skipping refresh")
|
|
2844
|
+
return
|
|
2845
|
+
|
|
2846
|
+
# Build and inject the prompt
|
|
2847
|
+
ptc_prompt = build_ptc_prompt(mcp_client_to_use, config=prompt_config, custom_tools_config=custom_tools_config)
|
|
2848
|
+
|
|
2849
|
+
# Rebuild instruction from original + PTC guidance
|
|
2850
|
+
self.instruction = f"{self._original_instruction}\n\n{ptc_prompt}"
|
|
2851
|
+
self._ptc_prompt_hash = current_hash
|
|
2852
|
+
|
|
2853
|
+
logger.info(f"Agent '{self.name}': PTC prompt guidance injected")
|
|
2854
|
+
|
|
2855
|
+
async def _register_mcp_tools(self) -> None:
|
|
2856
|
+
"""Override to sync PTC tool after MCP tools are registered.
|
|
2857
|
+
|
|
2858
|
+
This extends the base implementation to ensure the execute_ptc_code
|
|
2859
|
+
tool is added after MCP servers are initialized.
|
|
2860
|
+
"""
|
|
2861
|
+
await super()._register_mcp_tools()
|
|
2862
|
+
|
|
2863
|
+
# Sync PTC tool after MCP tools are registered
|
|
2864
|
+
if self._ptc_config and self._ptc_config.enabled and not self._ptc_tool_synced:
|
|
2865
|
+
self._sync_ptc_tool()
|
|
2866
|
+
|
|
2867
|
+
async def cleanup(self) -> None:
|
|
2868
|
+
"""Cleanup agent resources including PTC sandbox.
|
|
2869
|
+
|
|
2870
|
+
Extends base cleanup to also cleanup the PTC sandbox runtime if
|
|
2871
|
+
execute_ptc_code tool was created.
|
|
2872
|
+
"""
|
|
2873
|
+
# Cleanup PTC tool's sandbox runtime if present
|
|
2874
|
+
if self._ptc_tool is not None:
|
|
2875
|
+
try:
|
|
2876
|
+
cleanup_method = getattr(self._ptc_tool, "cleanup", None)
|
|
2877
|
+
if cleanup_method and callable(cleanup_method):
|
|
2878
|
+
await cleanup_method()
|
|
2879
|
+
logger.debug(f"Agent '{self.name}': PTC sandbox cleanup completed")
|
|
2880
|
+
except Exception as e:
|
|
2881
|
+
logger.warning(f"Agent '{self.name}': Error during PTC sandbox cleanup: {e}")
|
|
2882
|
+
|
|
2883
|
+
# Call parent cleanup for MCP client
|
|
2884
|
+
await super().cleanup()
|
|
2885
|
+
|
|
2497
2886
|
def _format_graph_output(self, final_state_result: dict[str, Any]) -> Any:
|
|
2498
2887
|
"""Convert final graph state to user-friendly output.
|
|
2499
2888
|
|