aip-agents-binary 0.6.4__py3-none-macosx_13_0_arm64.whl → 0.6.6__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/langgraph_react_agent.py +66 -19
- 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/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/multiply_tool.pyi +18 -0
- aip_agents/mcp/client/__init__.py +38 -2
- aip_agents/ptc/__init__.py +42 -3
- aip_agents/ptc/__init__.pyi +5 -1
- 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/exceptions.py +18 -0
- aip_agents/ptc/exceptions.pyi +15 -0
- aip_agents/ptc/executor.py +151 -33
- aip_agents/ptc/executor.pyi +34 -8
- aip_agents/ptc/naming.py +13 -1
- aip_agents/ptc/naming.pyi +9 -0
- aip_agents/ptc/prompt_builder.py +118 -16
- aip_agents/ptc/prompt_builder.pyi +12 -8
- aip_agents/ptc/sandbox_bridge.py +206 -8
- aip_agents/ptc/sandbox_bridge.pyi +18 -5
- 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/defaults.py +197 -1
- aip_agents/sandbox/defaults.pyi +28 -0
- aip_agents/sandbox/e2b_runtime.py +28 -0
- aip_agents/sandbox/e2b_runtime.pyi +7 -1
- aip_agents/sandbox/template_builder.py +2 -2
- aip_agents/sentry/sentry.py +29 -8
- aip_agents/sentry/sentry.pyi +3 -2
- aip_agents/tools/execute_ptc_code.py +59 -10
- aip_agents/tools/execute_ptc_code.pyi +5 -5
- {aip_agents_binary-0.6.4.dist-info → aip_agents_binary-0.6.6.dist-info}/METADATA +3 -3
- {aip_agents_binary-0.6.4.dist-info → aip_agents_binary-0.6.6.dist-info}/RECORD +48 -28
- {aip_agents_binary-0.6.4.dist-info → aip_agents_binary-0.6.6.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.6.4.dist-info → aip_agents_binary-0.6.6.dist-info}/top_level.txt +0 -0
aip_agents/ptc/prompt_builder.py
CHANGED
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from typing import TYPE_CHECKING, Any, Literal
|
|
14
14
|
|
|
15
|
+
from aip_agents.ptc.custom_tools import PTCCustomToolConfig
|
|
15
16
|
from aip_agents.ptc.naming import (
|
|
16
17
|
example_value_from_schema,
|
|
17
18
|
sanitize_function_name,
|
|
@@ -53,7 +54,9 @@ PTC_USAGE_RULES = """## PTC (Programmatic Tool Calling) Usage
|
|
|
53
54
|
|
|
54
55
|
When using `execute_ptc_code`, follow these rules:
|
|
55
56
|
|
|
56
|
-
1. **Import
|
|
57
|
+
1. **Import patterns**:
|
|
58
|
+
- MCP tools: `from tools.<server> import <tool_name>`
|
|
59
|
+
- Custom tools: `from tools.custom import <tool_name>`
|
|
57
60
|
2. **Output**: Only `print()` output is returned to you. Always print results.
|
|
58
61
|
3. **Parameter names**: All parameters are lowercase with underscores.
|
|
59
62
|
- Example: `userId` becomes `userid`, `user-id` becomes `user_id`
|
|
@@ -63,19 +66,22 @@ When using `execute_ptc_code`, follow these rules:
|
|
|
63
66
|
def build_ptc_prompt(
|
|
64
67
|
mcp_client: BaseMCPClient | None = None,
|
|
65
68
|
config: PromptConfig | None = None,
|
|
69
|
+
custom_tools_config: PTCCustomToolConfig | None = None,
|
|
66
70
|
) -> str:
|
|
67
|
-
"""Build PTC usage guidance prompt from MCP
|
|
71
|
+
"""Build PTC usage guidance prompt from MCP and custom tool configurations.
|
|
68
72
|
|
|
69
73
|
Generates a short usage block that includes:
|
|
70
|
-
- The import
|
|
74
|
+
- The import patterns: MCP (`from tools.<server> import <tool>`) and
|
|
75
|
+
custom (`from tools.custom import <tool>`)
|
|
71
76
|
- Rule: use `print()`; only printed output returns
|
|
72
77
|
- Rule: parameter names are sanitized to lowercase/underscored
|
|
73
78
|
- Prompt mode content (minimal/index/full)
|
|
74
79
|
- Examples based on the resolved prompt mode
|
|
75
80
|
|
|
76
81
|
Args:
|
|
77
|
-
mcp_client: The MCP client with configured servers.
|
|
82
|
+
mcp_client: The MCP client with configured servers. Can be None if only custom tools.
|
|
78
83
|
config: Prompt configuration. If None, uses default PromptConfig.
|
|
84
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
79
85
|
|
|
80
86
|
Returns:
|
|
81
87
|
PTC usage guidance prompt string.
|
|
@@ -90,19 +96,24 @@ def build_ptc_prompt(
|
|
|
90
96
|
tools = _get_server_tools(mcp_client, server_name)
|
|
91
97
|
server_infos.append({"name": server_name, "tools": tools})
|
|
92
98
|
|
|
99
|
+
# Collect custom tool info
|
|
100
|
+
custom_tool_infos: list[dict[str, Any]] = []
|
|
101
|
+
if custom_tools_config and custom_tools_config.enabled and custom_tools_config.tools:
|
|
102
|
+
custom_tool_infos = _get_custom_tool_infos(custom_tools_config)
|
|
103
|
+
|
|
93
104
|
# Check if we have any tools
|
|
94
|
-
if not server_infos:
|
|
105
|
+
if not server_infos and not custom_tool_infos:
|
|
95
106
|
return _build_placeholder_prompt()
|
|
96
107
|
|
|
97
108
|
# Resolve mode and build appropriate prompt
|
|
98
|
-
resolved_mode = _resolve_mode(config, server_infos)
|
|
109
|
+
resolved_mode = _resolve_mode(config, server_infos, custom_tool_infos)
|
|
99
110
|
|
|
100
111
|
if resolved_mode == "minimal":
|
|
101
|
-
return _build_minimal_prompt(server_infos, config.include_example)
|
|
112
|
+
return _build_minimal_prompt(server_infos, config.include_example, custom_tool_infos)
|
|
102
113
|
elif resolved_mode == "index":
|
|
103
|
-
return _build_index_prompt(server_infos, config.include_example)
|
|
114
|
+
return _build_index_prompt(server_infos, config.include_example, custom_tool_infos)
|
|
104
115
|
else: # full
|
|
105
|
-
return _build_full_prompt(server_infos, config.include_example)
|
|
116
|
+
return _build_full_prompt(server_infos, config.include_example, custom_tool_infos)
|
|
106
117
|
|
|
107
118
|
|
|
108
119
|
def _get_server_tools(
|
|
@@ -187,29 +198,61 @@ def _get_allowed_tools_from_config(mcp_client: BaseMCPClient, server_name: str)
|
|
|
187
198
|
return None
|
|
188
199
|
|
|
189
200
|
|
|
201
|
+
def _get_custom_tool_infos(custom_tools_config: PTCCustomToolConfig) -> list[dict[str, Any]]:
|
|
202
|
+
"""Get tool info dicts from custom tools configuration.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
custom_tools_config: Custom tools configuration.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of tool info dicts with name, description, and input_schema.
|
|
209
|
+
"""
|
|
210
|
+
tools: list[dict[str, Any]] = []
|
|
211
|
+
for tool_def in custom_tools_config.tools:
|
|
212
|
+
name = tool_def.get("name", "")
|
|
213
|
+
description = tool_def.get("description", "")
|
|
214
|
+
input_schema = tool_def.get("input_schema", {"type": "object", "properties": {}})
|
|
215
|
+
|
|
216
|
+
tools.append(
|
|
217
|
+
{
|
|
218
|
+
"name": name,
|
|
219
|
+
"description": description,
|
|
220
|
+
"input_schema": input_schema,
|
|
221
|
+
"stub": not description and not input_schema.get("properties"),
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
return tools
|
|
225
|
+
|
|
226
|
+
|
|
190
227
|
def _count_total_tools(
|
|
191
228
|
server_infos: list[dict[str, Any]],
|
|
229
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
192
230
|
) -> int:
|
|
193
|
-
"""Count total tools across all servers.
|
|
231
|
+
"""Count total tools across all servers and custom tools.
|
|
194
232
|
|
|
195
233
|
Args:
|
|
196
234
|
server_infos: List of server info dicts with name and tools.
|
|
235
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
197
236
|
|
|
198
237
|
Returns:
|
|
199
238
|
Total tool count.
|
|
200
239
|
"""
|
|
201
|
-
|
|
240
|
+
mcp_count = sum(len(info.get("tools", [])) for info in server_infos)
|
|
241
|
+
custom_count = len(custom_tool_infos) if custom_tool_infos else 0
|
|
242
|
+
return mcp_count + custom_count
|
|
202
243
|
|
|
203
244
|
|
|
204
245
|
def _resolve_mode(
|
|
205
246
|
config: PromptConfig,
|
|
206
247
|
server_infos: list[dict[str, Any]],
|
|
248
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
207
249
|
) -> PromptMode:
|
|
208
250
|
"""Resolve auto mode to concrete mode based on tool count.
|
|
209
251
|
|
|
210
252
|
Args:
|
|
211
253
|
config: Prompt configuration.
|
|
212
254
|
server_infos: List of server info dicts.
|
|
255
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
213
256
|
|
|
214
257
|
Returns:
|
|
215
258
|
Resolved mode (minimal, index, or full).
|
|
@@ -217,7 +260,7 @@ def _resolve_mode(
|
|
|
217
260
|
if config.mode != "auto":
|
|
218
261
|
return config.mode
|
|
219
262
|
|
|
220
|
-
total_tools = _count_total_tools(server_infos)
|
|
263
|
+
total_tools = _count_total_tools(server_infos, custom_tool_infos)
|
|
221
264
|
if total_tools == 0 or total_tools > config.auto_threshold:
|
|
222
265
|
return "minimal"
|
|
223
266
|
return "full"
|
|
@@ -243,12 +286,14 @@ print(doc["doc"])"""
|
|
|
243
286
|
def _build_minimal_prompt(
|
|
244
287
|
server_infos: list[dict[str, Any]],
|
|
245
288
|
include_example: bool,
|
|
289
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
246
290
|
) -> str:
|
|
247
291
|
"""Build minimal prompt with rules and package list only.
|
|
248
292
|
|
|
249
293
|
Args:
|
|
250
294
|
server_infos: List of server info dicts with name and tools.
|
|
251
295
|
include_example: Whether to include discovery example.
|
|
296
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
252
297
|
|
|
253
298
|
Returns:
|
|
254
299
|
Minimal PTC usage prompt.
|
|
@@ -265,6 +310,10 @@ def _build_minimal_prompt(
|
|
|
265
310
|
for pkg in package_names:
|
|
266
311
|
lines.append(f"- `tools.{pkg}`")
|
|
267
312
|
|
|
313
|
+
# Add custom tools package if present
|
|
314
|
+
if custom_tool_infos:
|
|
315
|
+
lines.append("- `tools.custom`")
|
|
316
|
+
|
|
268
317
|
lines.append("")
|
|
269
318
|
lines.append("Use `tools.ptc_helper` to discover available tools and their signatures.")
|
|
270
319
|
|
|
@@ -286,12 +335,14 @@ def _build_minimal_prompt(
|
|
|
286
335
|
def _build_index_prompt(
|
|
287
336
|
server_infos: list[dict[str, Any]],
|
|
288
337
|
include_example: bool,
|
|
338
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
289
339
|
) -> str:
|
|
290
340
|
"""Build index prompt with rules, package list, and tool names.
|
|
291
341
|
|
|
292
342
|
Args:
|
|
293
343
|
server_infos: List of server info dicts with name and tools.
|
|
294
344
|
include_example: Whether to include discovery example.
|
|
345
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
295
346
|
|
|
296
347
|
Returns:
|
|
297
348
|
Index PTC usage prompt.
|
|
@@ -316,6 +367,14 @@ def _build_index_prompt(
|
|
|
316
367
|
lines.append(f" Tools: {', '.join(tool_names)}")
|
|
317
368
|
lines.append("")
|
|
318
369
|
|
|
370
|
+
# Add custom tools section if present
|
|
371
|
+
if custom_tool_infos:
|
|
372
|
+
lines.append("**`tools.custom`**")
|
|
373
|
+
sorted_custom = sorted(custom_tool_infos, key=lambda t: sanitize_function_name(t["name"]))
|
|
374
|
+
tool_names = [sanitize_function_name(t["name"]) for t in sorted_custom]
|
|
375
|
+
lines.append(f" Tools: {', '.join(tool_names)}")
|
|
376
|
+
lines.append("")
|
|
377
|
+
|
|
319
378
|
lines.append("Use `tools.ptc_helper` to get tool signatures and descriptions.")
|
|
320
379
|
|
|
321
380
|
if include_example:
|
|
@@ -336,12 +395,14 @@ def _build_index_prompt(
|
|
|
336
395
|
def _build_full_prompt(
|
|
337
396
|
server_infos: list[dict[str, Any]],
|
|
338
397
|
include_example: bool,
|
|
398
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
339
399
|
) -> str:
|
|
340
400
|
"""Build full prompt with rules, signatures, and descriptions.
|
|
341
401
|
|
|
342
402
|
Args:
|
|
343
403
|
server_infos: List of server info dicts with name and tools.
|
|
344
404
|
include_example: Whether to include real tool example.
|
|
405
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
345
406
|
|
|
346
407
|
Returns:
|
|
347
408
|
Full PTC usage prompt.
|
|
@@ -377,8 +438,28 @@ def _build_full_prompt(
|
|
|
377
438
|
|
|
378
439
|
lines.append("")
|
|
379
440
|
|
|
441
|
+
# Add custom tools section if present
|
|
442
|
+
if custom_tool_infos:
|
|
443
|
+
lines.append("**Custom Tools** (from `tools.custom`)")
|
|
444
|
+
lines.append("")
|
|
445
|
+
|
|
446
|
+
sorted_custom = sorted(custom_tool_infos, key=lambda t: sanitize_function_name(t["name"]))
|
|
447
|
+
|
|
448
|
+
for tool in sorted_custom:
|
|
449
|
+
func_name = sanitize_function_name(tool["name"])
|
|
450
|
+
schema = tool.get("input_schema", {})
|
|
451
|
+
params = schema_to_params(schema)
|
|
452
|
+
raw_desc = tool.get("description", "")
|
|
453
|
+
desc = raw_desc[:120]
|
|
454
|
+
if raw_desc and len(raw_desc) > 120:
|
|
455
|
+
desc += "..."
|
|
456
|
+
|
|
457
|
+
lines.append(f"- `{func_name}({params})`: {desc}")
|
|
458
|
+
|
|
459
|
+
lines.append("")
|
|
460
|
+
|
|
380
461
|
if include_example:
|
|
381
|
-
example = _build_example(server_infos)
|
|
462
|
+
example = _build_example(server_infos, custom_tool_infos)
|
|
382
463
|
lines.extend(
|
|
383
464
|
[
|
|
384
465
|
"### Example",
|
|
@@ -406,15 +487,18 @@ def _build_prompt_from_servers(server_infos: list[dict[str, Any]]) -> str:
|
|
|
406
487
|
|
|
407
488
|
def _build_example(
|
|
408
489
|
server_infos: list[dict[str, Any]],
|
|
490
|
+
custom_tool_infos: list[dict[str, Any]] | None = None,
|
|
409
491
|
) -> str:
|
|
410
492
|
"""Build an example code snippet using the first available tool.
|
|
411
493
|
|
|
412
494
|
Args:
|
|
413
495
|
server_infos: List of server info dicts.
|
|
496
|
+
custom_tool_infos: List of custom tool info dicts.
|
|
414
497
|
|
|
415
498
|
Returns:
|
|
416
499
|
Example code string.
|
|
417
500
|
"""
|
|
501
|
+
# Try MCP tools first
|
|
418
502
|
if server_infos:
|
|
419
503
|
sorted_servers = sorted(server_infos, key=lambda info: sanitize_module_name_with_reserved(info["name"]))
|
|
420
504
|
for server in sorted_servers:
|
|
@@ -427,6 +511,17 @@ def _build_example(
|
|
|
427
511
|
args_str = _build_example_args_from_schema(tool.get("input_schema", {}))
|
|
428
512
|
return f"""from tools.{safe_server} import {func_name}
|
|
429
513
|
|
|
514
|
+
result = {func_name}({args_str})
|
|
515
|
+
print(result)"""
|
|
516
|
+
|
|
517
|
+
# Fall back to custom tools
|
|
518
|
+
if custom_tool_infos:
|
|
519
|
+
sorted_custom = sorted(custom_tool_infos, key=lambda t: sanitize_function_name(t["name"]))
|
|
520
|
+
tool = sorted_custom[0]
|
|
521
|
+
func_name = sanitize_function_name(tool["name"])
|
|
522
|
+
args_str = _build_example_args_from_schema(tool.get("input_schema", {}))
|
|
523
|
+
return f"""from tools.custom import {func_name}
|
|
524
|
+
|
|
430
525
|
result = {func_name}({args_str})
|
|
431
526
|
print(result)"""
|
|
432
527
|
|
|
@@ -534,15 +629,17 @@ def _build_server_hash_part(mcp_client: BaseMCPClient, server_name: str) -> str:
|
|
|
534
629
|
def compute_ptc_prompt_hash(
|
|
535
630
|
mcp_client: BaseMCPClient | None = None,
|
|
536
631
|
config: PromptConfig | None = None,
|
|
632
|
+
custom_tools_config: PTCCustomToolConfig | None = None,
|
|
537
633
|
) -> str:
|
|
538
|
-
"""Compute a hash of the MCP configuration for change detection.
|
|
634
|
+
"""Compute a hash of the MCP and custom tool configuration for change detection.
|
|
539
635
|
|
|
540
|
-
Includes PromptConfig fields and
|
|
636
|
+
Includes PromptConfig fields, allowed_tools, and custom tools in hash computation
|
|
541
637
|
so prompt updates re-sync correctly when configuration changes.
|
|
542
638
|
|
|
543
639
|
Args:
|
|
544
|
-
mcp_client: MCP client instance.
|
|
640
|
+
mcp_client: MCP client instance. Can be None if only custom tools.
|
|
545
641
|
config: Prompt configuration. If None, uses default PromptConfig.
|
|
642
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
546
643
|
|
|
547
644
|
Returns:
|
|
548
645
|
Hash string representing current configuration.
|
|
@@ -563,6 +660,11 @@ def compute_ptc_prompt_hash(
|
|
|
563
660
|
for server_name in sorted(mcp_client.servers.keys()):
|
|
564
661
|
parts.append(_build_server_hash_part(mcp_client, server_name))
|
|
565
662
|
|
|
663
|
+
# Add custom tools parts
|
|
664
|
+
if custom_tools_config and custom_tools_config.enabled and custom_tools_config.tools:
|
|
665
|
+
custom_tool_names = sorted(sanitize_function_name(t.get("name", "")) for t in custom_tools_config.tools)
|
|
666
|
+
parts.append(f"custom:{','.join(custom_tool_names)}")
|
|
667
|
+
|
|
566
668
|
# Return empty hash if no tools configured
|
|
567
669
|
if len(parts) == 1:
|
|
568
670
|
return ""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from _typeshed import Incomplete
|
|
2
2
|
from aip_agents.mcp.client.base_mcp_client import BaseMCPClient as BaseMCPClient
|
|
3
|
+
from aip_agents.ptc.custom_tools import PTCCustomToolConfig as PTCCustomToolConfig
|
|
3
4
|
from aip_agents.ptc.naming import example_value_from_schema as example_value_from_schema, sanitize_function_name as sanitize_function_name, sanitize_module_name_with_reserved as sanitize_module_name_with_reserved, sanitize_param_name as sanitize_param_name, schema_to_params as schema_to_params
|
|
4
5
|
from aip_agents.utils.logger import get_logger as get_logger
|
|
5
6
|
from dataclasses import dataclass
|
|
@@ -23,32 +24,35 @@ class PromptConfig:
|
|
|
23
24
|
|
|
24
25
|
PTC_USAGE_RULES: str
|
|
25
26
|
|
|
26
|
-
def build_ptc_prompt(mcp_client: BaseMCPClient | None = None, config: PromptConfig | None = None) -> str:
|
|
27
|
-
"""Build PTC usage guidance prompt from MCP
|
|
27
|
+
def build_ptc_prompt(mcp_client: BaseMCPClient | None = None, config: PromptConfig | None = None, custom_tools_config: PTCCustomToolConfig | None = None) -> str:
|
|
28
|
+
"""Build PTC usage guidance prompt from MCP and custom tool configurations.
|
|
28
29
|
|
|
29
30
|
Generates a short usage block that includes:
|
|
30
|
-
- The import
|
|
31
|
+
- The import patterns: MCP (`from tools.<server> import <tool>`) and
|
|
32
|
+
custom (`from tools.custom import <tool>`)
|
|
31
33
|
- Rule: use `print()`; only printed output returns
|
|
32
34
|
- Rule: parameter names are sanitized to lowercase/underscored
|
|
33
35
|
- Prompt mode content (minimal/index/full)
|
|
34
36
|
- Examples based on the resolved prompt mode
|
|
35
37
|
|
|
36
38
|
Args:
|
|
37
|
-
mcp_client: The MCP client with configured servers.
|
|
39
|
+
mcp_client: The MCP client with configured servers. Can be None if only custom tools.
|
|
38
40
|
config: Prompt configuration. If None, uses default PromptConfig.
|
|
41
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
39
42
|
|
|
40
43
|
Returns:
|
|
41
44
|
PTC usage guidance prompt string.
|
|
42
45
|
"""
|
|
43
|
-
def compute_ptc_prompt_hash(mcp_client: BaseMCPClient | None = None, config: PromptConfig | None = None) -> str:
|
|
44
|
-
"""Compute a hash of the MCP configuration for change detection.
|
|
46
|
+
def compute_ptc_prompt_hash(mcp_client: BaseMCPClient | None = None, config: PromptConfig | None = None, custom_tools_config: PTCCustomToolConfig | None = None) -> str:
|
|
47
|
+
"""Compute a hash of the MCP and custom tool configuration for change detection.
|
|
45
48
|
|
|
46
|
-
Includes PromptConfig fields and
|
|
49
|
+
Includes PromptConfig fields, allowed_tools, and custom tools in hash computation
|
|
47
50
|
so prompt updates re-sync correctly when configuration changes.
|
|
48
51
|
|
|
49
52
|
Args:
|
|
50
|
-
mcp_client: MCP client instance.
|
|
53
|
+
mcp_client: MCP client instance. Can be None if only custom tools.
|
|
51
54
|
config: Prompt configuration. If None, uses default PromptConfig.
|
|
55
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
52
56
|
|
|
53
57
|
Returns:
|
|
54
58
|
Hash string representing current configuration.
|
aip_agents/ptc/sandbox_bridge.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Top-level PTC Sandbox Bridge
|
|
1
|
+
"""Top-level PTC Sandbox Bridge.
|
|
2
2
|
|
|
3
3
|
This module provides the unified entry point for building sandbox payloads
|
|
4
|
-
|
|
4
|
+
across different tool sources (MCP, custom tools, etc.).
|
|
5
5
|
|
|
6
6
|
Authors:
|
|
7
7
|
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
@@ -9,37 +9,225 @@ Authors:
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import json
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
12
15
|
from aip_agents.mcp.client.base_mcp_client import BaseMCPClient
|
|
16
|
+
from aip_agents.ptc.custom_tools import (
|
|
17
|
+
PTCCustomToolConfig,
|
|
18
|
+
validate_custom_tool_config,
|
|
19
|
+
)
|
|
20
|
+
from aip_agents.ptc.custom_tools_payload import build_custom_tools_payload
|
|
21
|
+
from aip_agents.ptc.doc_gen import (
|
|
22
|
+
render_tool_doc,
|
|
23
|
+
)
|
|
24
|
+
from aip_agents.ptc.exceptions import PTCPayloadConflictError
|
|
13
25
|
from aip_agents.ptc.mcp.sandbox_bridge import build_mcp_payload
|
|
26
|
+
from aip_agents.ptc.naming import sanitize_function_name, schema_to_params
|
|
14
27
|
from aip_agents.ptc.payload import SandboxPayload
|
|
28
|
+
from aip_agents.ptc.ptc_helper import _generate_ptc_helper_module
|
|
15
29
|
|
|
16
30
|
|
|
17
31
|
async def build_sandbox_payload(
|
|
18
32
|
mcp_client: BaseMCPClient | None = None,
|
|
19
33
|
default_tool_timeout: float = 60.0,
|
|
34
|
+
custom_tools_config: PTCCustomToolConfig | None = None,
|
|
35
|
+
tool_configs: dict[str, dict] | None = None,
|
|
20
36
|
) -> SandboxPayload:
|
|
21
|
-
"""Build sandbox payload from
|
|
37
|
+
"""Build sandbox payload from all configured tool sources.
|
|
38
|
+
|
|
39
|
+
Composes MCP and custom LangChain tool payloads into a single payload.
|
|
22
40
|
|
|
23
41
|
Args:
|
|
24
|
-
mcp_client: The MCP client with configured servers.
|
|
42
|
+
mcp_client: The MCP client with configured servers. Can be None if only custom tools.
|
|
25
43
|
default_tool_timeout: Default timeout for tool calls in seconds.
|
|
44
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
45
|
+
tool_configs: Optional per-tool config values for custom tools.
|
|
26
46
|
|
|
27
47
|
Returns:
|
|
28
48
|
SandboxPayload containing files and env vars for the sandbox.
|
|
29
49
|
"""
|
|
30
50
|
# Build MCP payload
|
|
51
|
+
mcp_payload = SandboxPayload()
|
|
31
52
|
if mcp_client:
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
mcp_payload = await build_mcp_payload(mcp_client, default_tool_timeout)
|
|
54
|
+
|
|
55
|
+
# Build custom tools payload if enabled
|
|
56
|
+
custom_payload = SandboxPayload()
|
|
57
|
+
if custom_tools_config and custom_tools_config.enabled:
|
|
58
|
+
# Validate config before building payload (fail fast)
|
|
59
|
+
validate_custom_tool_config(custom_tools_config)
|
|
60
|
+
result = build_custom_tools_payload(custom_tools_config, tool_configs)
|
|
61
|
+
custom_payload = result.payload
|
|
62
|
+
|
|
63
|
+
# Check for conflicts before merging payloads
|
|
64
|
+
_check_payload_conflicts(mcp_payload, custom_payload)
|
|
65
|
+
|
|
66
|
+
# Merge payloads (custom tools files should not conflict with MCP files)
|
|
67
|
+
merged = SandboxPayload()
|
|
68
|
+
merged.files.update(mcp_payload.files)
|
|
69
|
+
merged.files.update(custom_payload.files)
|
|
70
|
+
merged.per_run_files.update(mcp_payload.per_run_files)
|
|
71
|
+
merged.per_run_files.update(custom_payload.per_run_files)
|
|
72
|
+
merged.env.update(mcp_payload.env)
|
|
73
|
+
merged.env.update(custom_payload.env)
|
|
74
|
+
|
|
75
|
+
# Merge custom tools into ptc_index.json
|
|
76
|
+
if custom_tools_config and custom_tools_config.enabled and custom_tools_config.tools:
|
|
77
|
+
merged.files["tools/ptc_index.json"] = _merge_custom_tools_into_index(
|
|
78
|
+
merged.files.get("tools/ptc_index.json"),
|
|
79
|
+
custom_tools_config,
|
|
80
|
+
)
|
|
81
|
+
if "tools/ptc_helper.py" not in merged.files:
|
|
82
|
+
merged.files["tools/ptc_helper.py"] = _generate_ptc_helper_module()
|
|
83
|
+
# Generate documentation for custom tools
|
|
84
|
+
custom_docs = _generate_custom_tool_docs(custom_tools_config)
|
|
85
|
+
merged.files.update(custom_docs)
|
|
86
|
+
|
|
87
|
+
return merged
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _check_payload_conflicts(
|
|
91
|
+
mcp_payload: SandboxPayload,
|
|
92
|
+
custom_payload: SandboxPayload,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Check for conflicts between MCP and custom tool payloads.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
mcp_payload: Payload from MCP tool configuration.
|
|
98
|
+
custom_payload: Payload from custom tool configuration.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
PTCPayloadConflictError: If any conflicts are detected in files,
|
|
102
|
+
per-run files, or environment variables.
|
|
103
|
+
"""
|
|
104
|
+
mcp_files = set(mcp_payload.files.keys())
|
|
105
|
+
custom_files = set(custom_payload.files.keys())
|
|
106
|
+
file_conflicts = mcp_files & custom_files
|
|
107
|
+
|
|
108
|
+
mcp_per_run = set(mcp_payload.per_run_files.keys())
|
|
109
|
+
custom_per_run = set(custom_payload.per_run_files.keys())
|
|
110
|
+
per_run_conflicts = mcp_per_run & custom_per_run
|
|
111
|
+
|
|
112
|
+
mcp_env_keys = set(mcp_payload.env.keys())
|
|
113
|
+
custom_env_keys = set(custom_payload.env.keys())
|
|
114
|
+
env_conflicts = mcp_env_keys & custom_env_keys
|
|
115
|
+
|
|
116
|
+
all_conflicts = file_conflicts | per_run_conflicts | env_conflicts
|
|
117
|
+
if all_conflicts:
|
|
118
|
+
raise PTCPayloadConflictError(
|
|
119
|
+
f"Conflicts detected when merging MCP and custom tool payloads. "
|
|
120
|
+
f"Files: {sorted(file_conflicts)}, "
|
|
121
|
+
f"Per-run files: {sorted(per_run_conflicts)}, "
|
|
122
|
+
f"Environment variables: {sorted(env_conflicts)}",
|
|
123
|
+
conflicts=all_conflicts,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _merge_custom_tools_into_index(
|
|
128
|
+
existing_index_json: str | None,
|
|
129
|
+
custom_tools_config: PTCCustomToolConfig,
|
|
130
|
+
) -> str:
|
|
131
|
+
"""Merge custom tools into the ptc_index.json.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
existing_index_json: Existing ptc_index.json content (may be None).
|
|
135
|
+
custom_tools_config: Custom tools configuration.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Updated JSON string with custom tools included.
|
|
139
|
+
"""
|
|
140
|
+
# Parse existing index or create new one
|
|
141
|
+
if existing_index_json:
|
|
142
|
+
index = json.loads(existing_index_json)
|
|
143
|
+
else:
|
|
144
|
+
index = {"packages": {}}
|
|
145
|
+
|
|
146
|
+
# Add custom tools package
|
|
147
|
+
tool_entries = []
|
|
148
|
+
for tool_def in sorted(custom_tools_config.tools, key=lambda t: sanitize_function_name(t.get("name", ""))):
|
|
149
|
+
name = tool_def.get("name", "")
|
|
150
|
+
func_name = sanitize_function_name(name)
|
|
151
|
+
schema = tool_def.get("input_schema", {"type": "object", "properties": {}})
|
|
152
|
+
signature = f"{func_name}({schema_to_params(schema)})"
|
|
153
|
+
|
|
154
|
+
tool_entries.append(
|
|
155
|
+
{
|
|
156
|
+
"name": func_name,
|
|
157
|
+
"signature": signature,
|
|
158
|
+
"doc_path": f"tools/docs/custom/{func_name}.md",
|
|
159
|
+
}
|
|
160
|
+
)
|
|
34
161
|
|
|
162
|
+
index["packages"]["custom"] = {"tools": tool_entries}
|
|
35
163
|
|
|
36
|
-
|
|
37
|
-
|
|
164
|
+
return json.dumps(index, indent=2, sort_keys=True)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _generate_custom_tool_docs(custom_tools_config: PTCCustomToolConfig) -> dict[str, str]:
|
|
168
|
+
"""Generate documentation files for custom tools.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
custom_tools_config: Custom tools configuration.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Dict mapping file path to content.
|
|
175
|
+
"""
|
|
176
|
+
docs: dict[str, str] = {}
|
|
177
|
+
|
|
178
|
+
for tool_def in sorted(custom_tools_config.tools, key=lambda t: sanitize_function_name(t.get("name", ""))):
|
|
179
|
+
name = tool_def.get("name", "")
|
|
180
|
+
func_name = sanitize_function_name(name)
|
|
181
|
+
description = tool_def.get("description", "")
|
|
182
|
+
schema = tool_def.get("input_schema", {"type": "object", "properties": {}})
|
|
183
|
+
|
|
184
|
+
doc_content = _generate_tool_doc_content(func_name, description, schema)
|
|
185
|
+
docs[f"tools/docs/custom/{func_name}.md"] = doc_content
|
|
186
|
+
|
|
187
|
+
return docs
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _generate_tool_doc_content(
|
|
191
|
+
func_name: str,
|
|
192
|
+
description: str,
|
|
193
|
+
schema: dict[str, Any],
|
|
194
|
+
) -> str:
|
|
195
|
+
"""Generate markdown documentation for a single tool.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
func_name: Sanitized function name.
|
|
199
|
+
description: Tool description.
|
|
200
|
+
schema: JSON schema for tool input.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Markdown documentation string.
|
|
204
|
+
"""
|
|
205
|
+
# Use schema_to_params for consistent signatures
|
|
206
|
+
params = schema_to_params(schema)
|
|
207
|
+
signature = f"{func_name}({params})"
|
|
208
|
+
|
|
209
|
+
example_code = f"from tools.custom import {func_name}\nresult = {func_name}(...)\nprint(result)"
|
|
210
|
+
|
|
211
|
+
return render_tool_doc(
|
|
212
|
+
func_name=func_name,
|
|
213
|
+
signature=signature,
|
|
214
|
+
description=description,
|
|
215
|
+
schema=schema,
|
|
216
|
+
example_code=example_code,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def wrap_ptc_code(code: str, include_packages_path: bool = False) -> str:
|
|
221
|
+
"""Wrap user PTC code with necessary imports and setup.
|
|
38
222
|
|
|
39
223
|
This prepends sys.path setup to ensure the tools package is importable.
|
|
224
|
+
When custom tools with bundled package sources are enabled, also adds
|
|
225
|
+
the packages/ directory to sys.path.
|
|
40
226
|
|
|
41
227
|
Args:
|
|
42
228
|
code: User-provided Python code.
|
|
229
|
+
include_packages_path: If True, also add packages/ dir to sys.path
|
|
230
|
+
for bundled package sources from custom LangChain tools.
|
|
43
231
|
|
|
44
232
|
Returns:
|
|
45
233
|
Wrapped code ready for sandbox execution.
|
|
@@ -52,7 +240,17 @@ import os
|
|
|
52
240
|
_tools_dir = os.path.dirname(os.path.abspath(__file__)) if "__file__" in dir() else os.getcwd()
|
|
53
241
|
if _tools_dir not in sys.path:
|
|
54
242
|
sys.path.insert(0, _tools_dir)
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
if include_packages_path:
|
|
246
|
+
preamble += """
|
|
247
|
+
# Add packages directory to path for bundled custom tool sources
|
|
248
|
+
_packages_dir = os.path.join(_tools_dir, "packages")
|
|
249
|
+
if os.path.isdir(_packages_dir) and _packages_dir not in sys.path:
|
|
250
|
+
sys.path.insert(0, _packages_dir)
|
|
251
|
+
"""
|
|
55
252
|
|
|
253
|
+
preamble += """
|
|
56
254
|
# User code below
|
|
57
255
|
"""
|
|
58
256
|
return preamble + code
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
from aip_agents.mcp.client.base_mcp_client import BaseMCPClient as BaseMCPClient
|
|
2
|
+
from aip_agents.ptc.custom_tools import PTCCustomToolConfig as PTCCustomToolConfig, validate_custom_tool_config as validate_custom_tool_config
|
|
3
|
+
from aip_agents.ptc.custom_tools_payload import build_custom_tools_payload as build_custom_tools_payload
|
|
4
|
+
from aip_agents.ptc.doc_gen import render_tool_doc as render_tool_doc
|
|
5
|
+
from aip_agents.ptc.exceptions import PTCPayloadConflictError as PTCPayloadConflictError
|
|
2
6
|
from aip_agents.ptc.mcp.sandbox_bridge import build_mcp_payload as build_mcp_payload
|
|
7
|
+
from aip_agents.ptc.naming import sanitize_function_name as sanitize_function_name, schema_to_params as schema_to_params
|
|
3
8
|
from aip_agents.ptc.payload import SandboxPayload as SandboxPayload
|
|
4
9
|
|
|
5
|
-
async def build_sandbox_payload(mcp_client: BaseMCPClient | None = None, default_tool_timeout: float = 60.0) -> SandboxPayload:
|
|
6
|
-
"""Build sandbox payload from
|
|
10
|
+
async def build_sandbox_payload(mcp_client: BaseMCPClient | None = None, default_tool_timeout: float = 60.0, custom_tools_config: PTCCustomToolConfig | None = None, tool_configs: dict[str, dict] | None = None) -> SandboxPayload:
|
|
11
|
+
"""Build sandbox payload from all configured tool sources.
|
|
12
|
+
|
|
13
|
+
Composes MCP and custom LangChain tool payloads into a single payload.
|
|
7
14
|
|
|
8
15
|
Args:
|
|
9
|
-
mcp_client: The MCP client with configured servers.
|
|
16
|
+
mcp_client: The MCP client with configured servers. Can be None if only custom tools.
|
|
10
17
|
default_tool_timeout: Default timeout for tool calls in seconds.
|
|
18
|
+
custom_tools_config: Optional custom LangChain tools configuration.
|
|
19
|
+
tool_configs: Optional per-tool config values for custom tools.
|
|
11
20
|
|
|
12
21
|
Returns:
|
|
13
22
|
SandboxPayload containing files and env vars for the sandbox.
|
|
14
23
|
"""
|
|
15
|
-
def wrap_ptc_code(code: str) -> str:
|
|
16
|
-
"""Wrap user PTC code with necessary imports and setup
|
|
24
|
+
def wrap_ptc_code(code: str, include_packages_path: bool = False) -> str:
|
|
25
|
+
"""Wrap user PTC code with necessary imports and setup.
|
|
17
26
|
|
|
18
27
|
This prepends sys.path setup to ensure the tools package is importable.
|
|
28
|
+
When custom tools with bundled package sources are enabled, also adds
|
|
29
|
+
the packages/ directory to sys.path.
|
|
19
30
|
|
|
20
31
|
Args:
|
|
21
32
|
code: User-provided Python code.
|
|
33
|
+
include_packages_path: If True, also add packages/ dir to sys.path
|
|
34
|
+
for bundled package sources from custom LangChain tools.
|
|
22
35
|
|
|
23
36
|
Returns:
|
|
24
37
|
Wrapped code ready for sandbox execution.
|