Skip to content

Commit 13a0bef

Browse files
refactor: address review feedback for AgentCoreToolSearchPlugin
- Rename DefaultIntentProvider to StrandsIntentProvider - Filter to user-only messages to prevent PII leakage - Add configurable system_prompt parameter - Add existing tool check to prevent overwriting static tools - Add missing __init__.py at gateway/integrations/ and gateway/integrations/strands/ - Add module docstrings for ruff compliance
1 parent 78c2e31 commit 13a0bef

8 files changed

Lines changed: 76 additions & 24 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Gateway integrations."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Strands Agents integrations for AgentCore Gateway."""

src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A semantic tool discovery plugin for [Strands Agents](https://github.com/strands
1313
## Installation
1414

1515
```bash
16-
pip install agentcore-tool-search-plugin
16+
pip install 'bedrock-agentcore[strands-agents]'
1717
```
1818

1919
## Usage
@@ -22,7 +22,7 @@ pip install agentcore-tool-search-plugin
2222
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
2323
from strands import Agent
2424
from strands.tools.mcp import MCPClient
25-
from agentcore_tool_search_plugin import AgentCoreToolSearchPlugin
25+
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin
2626

2727
mcp_client = MCPClient(lambda: aws_iam_streamablehttp_client(
2828
endpoint="https://<gateway-id>.gateway.bedrock-agentcore.<region>.amazonaws.com/mcp",
@@ -63,9 +63,9 @@ Previously loaded tools are cleared before each search, so the agent always has
6363

6464
An `IntentProvider` is responsible for analyzing conversation messages and producing a concise intent string that drives tool search. The plugin calls `derive_intent(messages, model)` before each invocation to determine what tools to load.
6565

66-
### Default Intent Provider
66+
### StrandsIntentProvider
6767

68-
`DefaultIntentProvider` uses an LLM to classify the last few conversation messages into a concise intent string. By default it uses the agent's model.
68+
`StrandsIntentProvider` uses a Strands Agent to classify the last few conversation messages into a concise intent string. By default it uses the parent agent's model.
6969

7070
**Basic usage (uses the agent's model automatically):**
7171

@@ -82,13 +82,29 @@ agent = Agent(plugins=[
8282
```python
8383
from strands.models.bedrock import BedrockModel
8484
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin
85-
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import DefaultIntentProvider
85+
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider
8686

8787
intent_model = BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0")
8888
agent = Agent(plugins=[
8989
AgentCoreToolSearchPlugin(
9090
mcp_client=mcp_client,
91-
intent_provider=DefaultIntentProvider(model=intent_model),
91+
intent_provider=StrandsIntentProvider(model=intent_model),
92+
)
93+
])
94+
```
95+
96+
**With a custom system prompt:**
97+
98+
```python
99+
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin
100+
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider
101+
102+
agent = Agent(plugins=[
103+
AgentCoreToolSearchPlugin(
104+
mcp_client=mcp_client,
105+
intent_provider=StrandsIntentProvider(
106+
system_prompt="Classify the user's intent in one sentence. Focus on the action, not details."
107+
),
92108
)
93109
])
94110
```
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
from .intent_providers import IntentProvider, DefaultIntentProvider
1+
"""AgentCore Tool Search plugin for Strands Agents."""
22

3-
__all__ = ["IntentProvider", "DefaultIntentProvider"]
3+
from .intent_providers import IntentProvider, StrandsIntentProvider
4+
from .plugin import AgentCoreToolSearchPlugin
5+
6+
__all__ = ["AgentCoreToolSearchPlugin", "IntentProvider", "StrandsIntentProvider"]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Intent provider interfaces and implementations."""
22

3-
from .default_intent_provider import DefaultIntentProvider
43
from .intent_provider import IntentProvider
4+
from .strands_intent_provider import StrandsIntentProvider
55

6-
__all__ = ["DefaultIntentProvider", "IntentProvider"]
6+
__all__ = ["StrandsIntentProvider", "IntentProvider"]

src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Intent provider abstract interface."""
2+
13
from abc import ABC, abstractmethod
24

35

src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py renamed to src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/strands_intent_provider.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Strands Agent-based intent provider implementation."""
2+
13
import logging
24

35
from strands import Agent
@@ -14,21 +16,29 @@
1416
)
1517

1618

17-
class DefaultIntentProvider(IntentProvider):
18-
"""LLM-based intent provider that classifies the last N messages."""
19+
class StrandsIntentProvider(IntentProvider):
20+
"""LLM-based intent provider that uses a Strands Agent to classify the last N messages."""
21+
22+
def __init__(self, message_window: int = 5, model=None, system_prompt: str = INTENT_SYSTEM_PROMPT):
23+
"""Initialize StrandsIntentProvider.
1924
20-
def __init__(self, message_window: int = 5, model=None):
25+
Args:
26+
message_window: Number of recent messages to consider.
27+
model: Optional explicit model for intent classification.
28+
system_prompt: System prompt for the intent classifier. Defaults to INTENT_SYSTEM_PROMPT.
29+
"""
2130
self._message_window = message_window
2231
self._explicit_model = model
32+
self._system_prompt = system_prompt
2333

2434
def derive_intent(self, messages: list[dict], model=None) -> str:
2535
"""Derive intent using an LLM. Falls back to agent's model if no explicit model set."""
2636
try:
27-
recent_messages = messages[-self._message_window:] if messages else []
37+
recent_messages = messages[-self._message_window :] if messages else []
2838
if not recent_messages:
2939
return ""
3040

31-
kwargs = {"system_prompt": INTENT_SYSTEM_PROMPT, "tools": []}
41+
kwargs = {"system_prompt": self._system_prompt, "tools": []}
3242
# Priority: explicit model > agent's model > Strands default
3343
resolved_model = self._explicit_model or model
3444
if resolved_model:
@@ -42,17 +52,22 @@ def derive_intent(self, messages: list[dict], model=None) -> str:
4252
return ""
4353

4454
def _format_messages_for_prompt(self, messages: list[dict]) -> str:
45-
"""Format messages into a text prompt for the intent LLM."""
55+
"""Format user messages into a text prompt for the intent LLM.
56+
57+
Only includes user-role messages to avoid leaking PII or sensitive data
58+
from tool results or assistant responses.
59+
"""
4660
parts = []
4761
for msg in messages:
48-
role = msg.get("role", "unknown")
62+
role = msg.get("role", "")
63+
if role != "user":
64+
continue
4965
content = msg.get("content", [])
5066
text = ""
5167
if isinstance(content, list):
5268
text = " ".join(
53-
block.get("text", "")
54-
for block in content
55-
if isinstance(block, dict) and "text" in block
69+
block.get("text", "") for block in content if isinstance(block, dict) and "text" in block
5670
)
57-
parts.append(f"{role}: {text}")
71+
if text.strip():
72+
parts.append(text.strip())
5873
return "\n".join(parts)

src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from strands.tools.mcp import MCPClient
1010
from strands.tools.mcp.mcp_agent_tool import MCPAgentTool
1111

12-
from .intent_providers import DefaultIntentProvider, IntentProvider
12+
from .intent_providers import IntentProvider, StrandsIntentProvider
1313

1414
logger = logging.getLogger(__name__)
1515

@@ -19,7 +19,7 @@ class AgentCoreToolSearchPlugin(Plugin):
1919
2020
Args:
2121
mcp_client: MCPClient connected to an AgentCore Gateway.
22-
intent_provider: Strategy for deriving intent. Defaults to DefaultIntentProvider.
22+
intent_provider: Strategy for deriving intent. Defaults to StrandsIntentProvider.
2323
"""
2424

2525
name = "agentcore-tool-search-plugin"
@@ -29,13 +29,20 @@ def __init__(
2929
mcp_client: MCPClient,
3030
intent_provider: IntentProvider | None = None,
3131
):
32+
"""Initialize the plugin.
33+
34+
Args:
35+
mcp_client: MCPClient connected to an AgentCore Gateway.
36+
intent_provider: Strategy for deriving intent. Defaults to StrandsIntentProvider.
37+
"""
3238
super().__init__()
33-
self._intent_provider = intent_provider or DefaultIntentProvider()
39+
self._intent_provider = intent_provider or StrandsIntentProvider()
3440
self._mcp_client = mcp_client
3541
self._loaded_tool_names: set[str] = set()
3642

3743
@property
3844
def tools(self):
45+
"""Return empty list; tools are loaded dynamically via the hook."""
3946
return []
4047

4148
@hook
@@ -68,6 +75,13 @@ def on_before_invocation(self, event: BeforeInvocationEvent) -> None:
6875

6976
for agent_tool in agent_tools:
7077
try:
78+
# Skip if a non-dynamic tool with this name already exists
79+
if (
80+
agent_tool.tool_name in event.agent.tool_registry.registry
81+
and agent_tool.tool_name not in self._loaded_tool_names
82+
):
83+
logger.debug("Skipping tool %s: already registered as a static tool", agent_tool.tool_name)
84+
continue
7185
event.agent.tool_registry.register_tool(agent_tool)
7286
self._loaded_tool_names.add(agent_tool.tool_name)
7387
except Exception as e:

0 commit comments

Comments
 (0)