Skip to content
Merged
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
40 changes: 40 additions & 0 deletions src/assets/templates/agui-python-strands/Dockerfile.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
FROM public.ecr.aws/docker/library/python:3.12-slim-trixie

RUN pip install --no-cache-dir uv

ARG UV_DEFAULT_INDEX
ARG UV_INDEX

WORKDIR /app

ENV UV_SYSTEM_PYTHON=1 \
UV_COMPILE_BYTECODE=1 \
UV_NO_PROGRESS=1 \
PYTHONUNBUFFERED=1 \
DOCKER_CONTAINER=1 \
UV_DEFAULT_INDEX=${UV_DEFAULT_INDEX} \
UV_INDEX=${UV_INDEX} \
PATH="/app/.venv/bin:$PATH"

RUN useradd -m -u 1000 bedrock_agentcore

COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project

COPY --chown=bedrock_agentcore:bedrock_agentcore . .
RUN uv sync --frozen --no-dev

USER bedrock_agentcore

# AgentCore Runtime service contract ports
# https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-service-contract.html
# 8080: HTTP Mode
# 8000: MCP Mode
# 9000: A2A Mode
EXPOSE 8080 8000 9000

{{#if enableOtel}}
CMD ["opentelemetry-instrument", "python", "-m", "{{entrypoint}}"]
{{else}}
CMD ["python", "-m", "{{entrypoint}}"]
{{/if}}
39 changes: 39 additions & 0 deletions src/assets/templates/agui-python-strands/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# {{ name }}

An AG-UI agent deployed on Amazon Bedrock AgentCore using the Strands SDK.

## Overview

This agent speaks the [AG-UI protocol](https://docs.ag-ui.com/introduction), which
streams agent-to-UI events over HTTP. It exposes an `/invocations` endpoint that accepts
an AG-UI `RunAgentInput` body and streams the response back as AG-UI server-sent events,
matching the AgentCore Runtime HTTP service contract.

## Adding Tools

Define tools with the `@tool` decorator in `main.py` and add them to the agent's `tools` list:

```python
@tool
def my_tool(param: str) -> str:
"""Description of what the tool does."""
return f"Result: {param}"
```

## Developing locally

`agentcore project dev` starts the agent locally on `0.0.0.0:8080`. Post an AG-UI
`RunAgentInput` body to `http://127.0.0.1:8080/invocations` to invoke it, and check its
health at `http://127.0.0.1:8080/ping`.

## Deployment

`agentcore project deploy` deploys the agent into Amazon Bedrock AgentCore. Invoke the deployed
runtime with an AG-UI `RunAgentInput` payload:

```bash
agentcore project invoke runtime --name agui_python_strands \
--payload '{"threadId":"t1","runId":"r1","state":{},"messages":[{"id":"m1","role":"user","content":"Hello!"}],"tools":[],"context":[],"forwardedProps":{}}'
```

The response streams back as AG-UI server-sent events.
27 changes: 27 additions & 0 deletions src/assets/templates/agui-python-strands/dockerignore.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
.venv/
dist/
build/

# IDE
.vscode/
.idea/

# Testing
.pytest_cache/
.coverage
htmlcov/

# Secrets and environment files
.env
.env.*

# Version control
.git/

# AgentCore build artifacts
.agentcore/artifacts/
*.zip
41 changes: 41 additions & 0 deletions src/assets/templates/agui-python-strands/gitignore.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Environment variables
.env

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
.venv/
venv/
ENV/
env/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db
46 changes: 46 additions & 0 deletions src/assets/templates/agui-python-strands/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os

import uvicorn
from strands import Agent, tool
from ag_ui_strands import StrandsAgent, StrandsAgentConfig, create_strands_app
from model.load import load_model
{{#if hasMemory}}
from memory.session import get_memory_session_manager
{{/if}}


@tool
def add_numbers(a: int, b: int) -> int:
"""Return the sum of two numbers."""
return a + b


agent = Agent(
model=load_model(),
system_prompt="You are a helpful assistant. Use tools when appropriate.",
tools=[add_numbers],
)

{{#if hasMemory}}
# The AG-UI protocol carries a per-conversation thread_id; each thread gets its
# own session manager so history is scoped to the conversation. Returns None
# (in-process history only) until the deployed MEMORY_ID env var is set.
def session_manager_provider(input_data):
return get_memory_session_manager(input_data.thread_id, "default-user")


config = StrandsAgentConfig(session_manager_provider=session_manager_provider)
{{else}}
config = StrandsAgentConfig()
{{/if}}

agui_agent = StrandsAgent(
agent=agent, name="{{ name }}", description="A helpful assistant", config=config
)

# create_strands_app publishes the AG-UI endpoint at /invocations and a health
# check at /ping, matching the AgentCore Runtime HTTP service contract on 8080.
app = create_strands_app(agui_agent, path="/invocations", ping_path="/ping")

if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
Empty file.
47 changes: 47 additions & 0 deletions src/assets/templates/agui-python-strands/memory/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import uuid
from typing import Optional

from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryStrategies.length}}, RetrievalConfig{{/if}}
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager

MEMORY_ID = os.getenv("{{memoryEnvVarName}}")
REGION = os.getenv("AWS_REGION")


def get_memory_session_manager(
session_id: Optional[str], actor_id: str
) -> Optional[AgentCoreMemorySessionManager]:
if not MEMORY_ID:
return None

session_id = session_id or uuid.uuid4().hex

{{#if memoryStrategies.length}}
retrieval_config = {
{{#if (includes memoryStrategies "SEMANTIC")}}
f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5),
{{/if}}
{{#if (includes memoryStrategies "USER_PREFERENCE")}}
f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5),
{{/if}}
{{#if (includes memoryStrategies "EPISODIC")}}
f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5),
{{/if}}
{{#if (includes memoryStrategies "SUMMARIZATION")}}
f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5),
{{/if}}
}
{{/if}}

return AgentCoreMemorySessionManager(
AgentCoreMemoryConfig(
memory_id=MEMORY_ID,
session_id=session_id,
actor_id=actor_id,
{{#if memoryStrategies.length}}
retrieval_config=retrieval_config,
{{/if}}
),
REGION,
)
1 change: 1 addition & 0 deletions src/assets/templates/agui-python-strands/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Package marker
6 changes: 6 additions & 0 deletions src/assets/templates/agui-python-strands/model/load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from strands.models.bedrock import BedrockModel


def load_model() -> BedrockModel:
"""Get Bedrock model client using IAM credentials."""
return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0")
22 changes: 22 additions & 0 deletions src/assets/templates/agui-python-strands/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[build-system]
requires = ["hatchling ~= 1.27.0"]
build-backend = "hatchling.build"

[project]
name = "{{ name }}"
version = "0.1.0"
description = "AgentCore AG-UI Agent using Strands SDK"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ag-ui-strands >= 0.3.0, < 0.4.0",
"ag-ui-protocol >= 0.1.19, < 0.2.0",
"aws-opentelemetry-distro ~= 0.17.0",
"bedrock-agentcore ~= 1.9.1",
"botocore[crt] ~= 1.43.0",
"strands-agents ~= 1.15.0",
"uvicorn >= 0.34.3, < 1.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
75 changes: 75 additions & 0 deletions src/core/project/__snapshots__/manager.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,78 @@ exports[`FsProjectManager.create snapshots the Strands A2A project manifest and
],
}
`;

exports[`FsProjectManager.create snapshots the Strands AG-UI project manifest and runtime spec 1`] = `
{
"manifest": [
".gitignore",
"agentcore/.env.local",
"agentcore/agentcore.json",
"agentcore/aws-targets.json",
"agentcore/cdk/.gitignore",
"agentcore/cdk/.npmignore",
"agentcore/cdk/.prettierrc",
"agentcore/cdk/README.md",
"agentcore/cdk/bin/cdk.ts",
"agentcore/cdk/cdk.json",
"agentcore/cdk/jest.config.js",
"agentcore/cdk/lib/cdk-stack.ts",
"agentcore/cdk/package.json",
"agentcore/cdk/test/cdk.test.ts",
"agentcore/cdk/tsconfig.json",
"app/agui_python_strands/.gitignore",
"app/agui_python_strands/README.md",
"app/agui_python_strands/main.py",
"app/agui_python_strands/memory/__init__.py",
"app/agui_python_strands/memory/session.py",
"app/agui_python_strands/model/__init__.py",
"app/agui_python_strands/model/load.py",
"app/agui_python_strands/pyproject.toml",
],
"memories": [
{
"eventExpiryDuration": 30,
"name": "agui_python_strandsMemory",
"strategies": [
{
"namespaceTemplates": [
"/users/{actorId}/facts",
],
"type": "SEMANTIC",
},
{
"namespaceTemplates": [
"/users/{actorId}/preferences",
],
"type": "USER_PREFERENCE",
},
{
"namespaceTemplates": [
"/summaries/{actorId}/{sessionId}",
],
"type": "SUMMARIZATION",
},
{
"namespaceTemplates": [
"/episodes/{actorId}/{sessionId}",
],
"reflectionNamespaceTemplates": [
"/episodes/{actorId}",
],
"type": "EPISODIC",
},
],
},
],
"runtimes": [
{
"build": "CodeZip",
"codeLocation": "app/agui_python_strands",
"entrypoint": "main.py",
"name": "agui_python_strands",
"protocol": "AGUI",
"runtimeVersion": "PYTHON_3_14",
},
],
}
`;
Loading
Loading