Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions src/assets/templates/strands-http-python/hooks/execution_limits.py

This file was deleted.

65 changes: 28 additions & 37 deletions src/assets/templates/strands-http-python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,21 @@
{{/if}}
{{/if}}
import asyncio
{{#if timeoutSeconds}}
import threading
{{/if}}
{{#if hasShell}}
import subprocess
{{/if}}
{{#if hasFileOperations}}
import os
{{/if}}
{{#if hasExecutionLimits}}
from strands.tools.executors import SequentialToolExecutor
from strands.types.exceptions import EventLoopException
from hooks.execution_limits import ExecutionLimitExceeded, ExecutionLimitsHook
{{/if}}
{{#if hasConfigBundle}}
from strands.hooks import HookProvider, HookRegistry, BeforeInvocationEvent, BeforeToolCallEvent
{{/if}}
{{#if truncationStrategy}}
{{#if (eq truncationStrategy "sliding_window")}}
from strands.agent.conversation_manager.sliding_window_conversation_manager import SlidingWindowConversationManager
from strands.agent.conversation_manager import SlidingWindowConversationManager
{{/if}}
{{#if (eq truncationStrategy "summarization")}}
from strands.agent.conversation_manager.summarizing_conversation_manager import SummarizingConversationManager
Expand Down Expand Up @@ -413,18 +411,7 @@ def get_or_create_agent(session_id, user_id{{#if hasSkillsFetcher}}, skill_plugi
{{#if hasSkillsFetcher}}
plugins=skill_plugins or None,
{{/if}}
{{#if hasExecutionLimits}}
tool_executor=SequentialToolExecutor(),
callback_handler=None,
{{/if}}
hooks=[
{{#if hasExecutionLimits}}
ExecutionLimitsHook(
{{#if maxIterations}}max_iterations={{maxIterations}},{{/if}}
{{#if maxTokens}}max_tokens={{maxTokens}},{{/if}}
{{#if timeoutSeconds}}timeout_seconds={{timeoutSeconds}},{{/if}}
),
{{/if}}
{{#if hasConfigBundle}}
ConfigBundleHook(),
{{/if}}
Expand Down Expand Up @@ -457,18 +444,7 @@ def get_or_create_agent(session_id{{#if hasSkillsFetcher}}, skill_plugins=None{{
{{#if hasSkillsFetcher}}
plugins=skill_plugins or None,
{{/if}}
{{#if hasExecutionLimits}}
tool_executor=SequentialToolExecutor(),
callback_handler=None,
{{/if}}
hooks=[
{{#if hasExecutionLimits}}
ExecutionLimitsHook(
{{#if maxIterations}}max_iterations={{maxIterations}},{{/if}}
{{#if maxTokens}}max_tokens={{maxTokens}},{{/if}}
{{#if timeoutSeconds}}timeout_seconds={{timeoutSeconds}},{{/if}}
),
{{/if}}
{{#if hasConfigBundle}}
ConfigBundleHook(),
{{/if}}
Expand Down Expand Up @@ -639,24 +615,36 @@ async def invoke(payload, context):
{{/if}}

{{#if hasExecutionLimits}}
timeout_seconds = {{#if timeoutSeconds}}{{timeoutSeconds}}{{else}}None{{/if}}
limits = {
{{#if maxIterations}}"turns": {{maxIterations}},{{/if}}
{{#if maxTokens}}"output_tokens": {{maxTokens}},{{/if}}
} or None
cancel_signal = {{#if timeoutSeconds}}threading.Event(){{else}}None{{/if}}
timeout_fired = False
watchdog_task = None
if timeout_seconds is not None:
{{#if timeoutSeconds}}
if cancel_signal is not None:
async def _timeout_watchdog():
nonlocal timeout_fired
await asyncio.sleep(timeout_seconds)
await asyncio.sleep({{timeoutSeconds}})
timeout_fired = True
agent.cancel()
cancel_signal.set()
watchdog_task = asyncio.create_task(_timeout_watchdog())
{{/if}}

try:
stop_reason = None
{{#if inlineFunctionTools}}
hit_inline_function = False
{{/if}}
async for event in agent.stream_async(
prompt,
limits=limits,
cancel_signal=cancel_signal,
):
if isinstance(event, dict) and "result" in event:
stop_reason = getattr(event["result"], "stop_reason", None)
continue
if not isinstance(event, dict) or "event" not in event:
continue
cbs = event["event"].get("contentBlockStart")
Expand All @@ -674,11 +662,14 @@ async def _timeout_watchdog():

if timeout_fired:
yield {"event": {"messageStop": {"stopReason": "timeout_exceeded"}}}
except EventLoopException as e:
if isinstance(e.original_exception, ExecutionLimitExceeded):
yield {"event": {"messageStop": {"stopReason": str(e.original_exception)}}}
return
raise
{{#if maxIterations}}
elif stop_reason == "limit_turns":
yield {"event": {"messageStop": {"stopReason": "Max iterations exceeded: {{maxIterations}}"}}}
{{/if}}
{{#if maxTokens}}
elif stop_reason == "limit_output_tokens":
yield {"event": {"messageStop": {"stopReason": "Max output tokens exceeded: {{maxTokens}}"}}}
{{/if}}
finally:
if watchdog_task is not None:
watchdog_task.cancel()
Expand Down
19 changes: 11 additions & 8 deletions src/assets/templates/strands-http-python/mcp_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,32 @@ def get_all_gateway_mcp_clients() -> list[MCPClient]:
{{#if headerCredentials}}
{{#each headerCredentials}}
@requires_api_key(provider_name="{{credentialName}}")
def _get_{{snakeCase ../name}}_{{snakeCase headerKey}}_key(api_key: str) -> str:
def _get_{{pythonName}}_key(api_key: str) -> str:
"""Fetch {{headerKey}} credential for {{../name}} from AgentCore Identity."""
return api_key

{{/each}}
{{/if}}
def get_{{snakeCase name}}_mcp_client() -> MCPClient | None:
def get_{{pythonName}}_mcp_client() -> MCPClient | None:
"""Returns an MCP Client for the {{name}} remote MCP server."""
url = {{safeJson url}}
{{#if headerCredentials}}
if os.getenv("LOCAL_DEV") == "1":
headers = { {{#each headerCredentials}}{{safeJson headerKey}}: os.environ.get("{{envVarName}}", ""){{#unless @last}}, {{/unless}}{{/each}} }
else:
headers = { {{#each headerCredentials}}{{safeJson headerKey}}: _get_{{snakeCase ../name}}_{{snakeCase headerKey}}_key(){{#unless @last}}, {{/unless}}{{/each}} }
return MCPClient(lambda: streamablehttp_client(url, headers=headers))
def transport():
if os.getenv("LOCAL_DEV") == "1":
headers = { {{#each headerCredentials}}{{safeJson headerKey}}: os.environ.get("{{envVarName}}", ""){{#unless @last}}, {{/unless}}{{/each}} }
else:
headers = { {{#each headerCredentials}}{{safeJson headerKey}}: _get_{{pythonName}}_key(){{#unless @last}}, {{/unless}}{{/each}} }
return streamablehttp_client(url, headers=headers)

return MCPClient(transport)
{{else}}
return MCPClient(lambda: streamablehttp_client(url))
{{/if}}

{{/each}}
def get_all_remote_mcp_clients() -> list[MCPClient]:
"""Returns all configured remote MCP clients."""
clients = [{{#each remoteMcpTools}}get_{{snakeCase name}}_mcp_client(){{#unless @last}}, {{/unless}}{{/each}}]
clients = [{{#each remoteMcpTools}}get_{{pythonName}}_mcp_client(){{#unless @last}}, {{/unless}}{{/each}}]
return [c for c in clients if c is not None]
{{/if}}
{{#unless (or hasGateway remoteMcpTools)}}
Expand Down
8 changes: 4 additions & 4 deletions src/assets/templates/strands-http-python/memory/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ def get_memory_session_manager(
{{#if memoryStrategies.length}}
retrieval_config = {
{{#if (includes memoryStrategies "SEMANTIC")}}
f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5),
f"/users/{actor_id}/facts": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}),
{{/if}}
{{#if (includes memoryStrategies "USER_PREFERENCE")}}
f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5),
f"/users/{actor_id}/preferences": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}),
{{/if}}
{{#if (includes memoryStrategies "EPISODIC")}}
f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5),
f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}5{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}),
{{/if}}
{{#if (includes memoryStrategies "SUMMARIZATION")}}
f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5),
f"/summaries/{actor_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}),
{{/if}}
}
{{/if}}
Expand Down
69 changes: 63 additions & 6 deletions src/assets/templates/strands-http-python/model/load.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{{#if (eq modelProvider "Bedrock")}}
{{#if bedrockMantle}}
import os
{{#if modelAdditionalParams}}
import json
{{/if}}

from aws_bedrock_token_generator import provide_token
{{#if (eq mantleApiFormat "chat_completions")}}
Expand Down Expand Up @@ -34,7 +37,7 @@ def load_model():
{{/if}}
client_args = {"api_key": token, "base_url": base_url}

params = {}
params = {{#if modelAdditionalParams}}json.loads({{pyJsonStr modelAdditionalParams}}){{else}}{}{{/if}}
{{#if modelMaxTokens}}
{{#if (eq mantleApiFormat "chat_completions")}}
params["max_completion_tokens"] = {{modelMaxTokens}}
Expand All @@ -60,12 +63,22 @@ def load_model():
{{/if}}
{{/if}}
{{else}}
{{#if modelAdditionalParams}}
import json
{{/if}}
from strands.models.bedrock import BedrockModel


def load_model() -> BedrockModel:
"""Get Bedrock model client using IAM credentials."""
return BedrockModel(model_id="{{#if modelId}}{{modelId}}{{else}}global.anthropic.claude-sonnet-4-5-20250929-v1:0{{/if}}"{{#if modelMaxTokens}}, max_tokens={{modelMaxTokens}}{{/if}}{{#if modelTemperature}}, temperature={{modelTemperature}}{{/if}}{{#if modelTopP}}, top_p={{modelTopP}}{{/if}})
return BedrockModel(
model_id="{{#if modelId}}{{modelId}}{{else}}global.anthropic.claude-sonnet-4-5-20250929-v1:0{{/if}}",
{{#if modelMaxTokens}}max_tokens={{modelMaxTokens}},
{{/if}}{{#if modelTemperature}}temperature={{modelTemperature}},
{{/if}}{{#if modelTopP}}top_p={{modelTopP}},
{{/if}}{{#if modelAdditionalParams}}additional_request_fields=json.loads({{pyJsonStr modelAdditionalParams}}),
{{/if}}
)
{{/if}}
{{/if}}
{{#if (eq modelProvider "Anthropic")}}
Expand Down Expand Up @@ -109,8 +122,15 @@ def load_model() -> AnthropicModel:
{{/if}}
{{#if (eq modelProvider "OpenAI")}}
import os
{{#if modelAdditionalParams}}
import json
{{/if}}

{{#if (eq modelApiFormat "responses")}}
from strands.models.openai_responses import OpenAIResponsesModel
{{else}}
from strands.models.openai import OpenAIModel
{{/if}}
from bedrock_agentcore.identity.auth import requires_api_key

IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
Expand Down Expand Up @@ -138,15 +158,29 @@ def _get_api_key() -> str:
return _agentcore_identity_api_key_provider()


def load_model() -> OpenAIModel:
def load_model():
"""Get authenticated OpenAI model client."""
return OpenAIModel(
params = {{#if modelAdditionalParams}}json.loads({{pyJsonStr modelAdditionalParams}}){{else}}{}{{/if}}
{{#if modelMaxTokens}}
params["{{#if (eq modelApiFormat "responses")}}max_output_tokens{{else}}max_completion_tokens{{/if}}"] = {{modelMaxTokens}}
{{/if}}
{{#if modelTemperature}}
params["temperature"] = {{modelTemperature}}
{{/if}}
{{#if modelTopP}}
params["top_p"] = {{modelTopP}}
{{/if}}
return {{#if (eq modelApiFormat "responses")}}OpenAIResponsesModel{{else}}OpenAIModel{{/if}}(
client_args={"api_key": _get_api_key()},
model_id="{{#if modelId}}{{modelId}}{{else}}gpt-4.1{{/if}}",
params=params,
)
{{/if}}
{{#if (eq modelProvider "Gemini")}}
import os
{{#if modelAdditionalParams}}
import json
{{/if}}

from strands.models.gemini import GeminiModel
from bedrock_agentcore.identity.auth import requires_api_key
Expand Down Expand Up @@ -178,14 +212,28 @@ def _get_api_key() -> str:

def load_model() -> GeminiModel:
"""Get authenticated Gemini model client."""
params = {{#if modelAdditionalParams}}json.loads({{pyJsonStr modelAdditionalParams}}){{else}}{}{{/if}}
{{#if modelMaxTokens}}
params["max_output_tokens"] = {{modelMaxTokens}}
{{/if}}
{{#if modelTemperature}}
params["temperature"] = {{modelTemperature}}
{{/if}}
{{#if modelTopP}}
params["top_p"] = {{modelTopP}}
{{/if}}
{{#if modelTopK}}
params["top_k"] = {{modelTopK}}
{{/if}}
return GeminiModel(
client_args={"api_key": _get_api_key()},
model_id="{{#if modelId}}{{modelId}}{{else}}gemini-2.5-flash{{/if}}",
params=params,
)
{{/if}}
{{#if (eq modelProvider "LiteLLM")}}
import os
{{#if litellmAdditionalParams}}
{{#if modelAdditionalParams}}
import json
{{/if}}

Expand Down Expand Up @@ -230,7 +278,16 @@ def load_model() -> LiteLLMModel:
{{#if litellmApiBase}}
client_args["api_base"] = {{safeJson litellmApiBase}}
{{/if}}
params = {{#if litellmAdditionalParams}}json.loads({{pyJsonStr litellmAdditionalParams}}){{else}}{}{{/if}}
params = {{#if modelAdditionalParams}}json.loads({{pyJsonStr modelAdditionalParams}}){{else}}{}{{/if}}
{{#if modelMaxTokens}}
params["max_tokens"] = {{modelMaxTokens}}
{{/if}}
{{#if modelTemperature}}
params["temperature"] = {{modelTemperature}}
{{/if}}
{{#if modelTopP}}
params["top_p"] = {{modelTopP}}
{{/if}}
return LiteLLMModel(
client_args=client_args,
model_id="{{#if modelId}}{{modelId}}{{else}}bedrock/us.anthropic.claude-sonnet-4-5-20250514-v1:0{{/if}}",
Expand Down
Loading
Loading