[Amazon Bedrock AgentCore] Code Interpreterで簡単なコードを実行して画像を作成してみた
はじめに
こんにちは、スーパーマーケットのラ・ムーを推ししているコンサルティング部の神野です。
安くてお買い得なスーパーですよね。
皆さんは先月リリースされたAmazon Bedrock AgentCoreを使っていますか?
私も毎日検証しています。たくさん機能があって検証する時間が足りないぐらいです。
いろんな機能がある内の今回はAmazon Bedrock AgentCore Code Interpreterを使ってみて、機能をご紹介したいと思います!
Amazon Bedrock AgentCore Code Interpreterとは
Code Interpreterは、AIエージェントが安全にコードを実行できるサンドボックス環境を提供するマネージドサービスです。公式ハンズオンの図では下記のようCode Interpreter機能は表現されています。
AIエージェントのツールとして実行できる形なんですね。
AIに作らせたコードを安全に実行できる環境というわけですね。確かに生成AIに作らせたコードをエージェントと同一環境で実行したら何が起こるか分からないから怖いですもんね・・・そこを独立した環境で実行できたら安心ですね!
Code Interpreterは以下の特徴があります。
- 完全に隔離されたサンドボックス環境でコード実行
- Python、JavaScript、TypeScriptに対応
- pandas、numpy、matplotlibなどのデータサイエンス系ライブラリが利用可能
- インライン100MB、S3経由で最大5GBのファイル処理が可能
- デフォルト15分、最大8時間の実行時間
- セッション内でのファイルや変数の状態維持
便利な機能が多いですね。特にデータサイエンス系ライブラリがビルトインなのはライブラリの準備など手間がかからず嬉しいですね。
今回は検証していないですが、S3経由でファイルを取得してコード実行できるのも試してみたいです。
身近なサービスで言うとGeminiのCanvasとかはまさにこんな感じで独立した環境でコード実行されているのかな?と少し気になりました。
今回実装する構成
今回は以下のようなアーキテクチャで簡単なデータ分析エージェントを実装します。
AIエージェント(Strands Agent使用)が質問を理解し、適切なPythonコードを生成し、Code Interpreterで安全に実行して、また分析結果を可視化してS3にアップロードして、サマリと画像のS3署名付きURLを返却する流れとなります。
前提条件
必要な環境
- Python 3.12
- AWS CLI 2.28
- AWSアカウント(us-west-2リージョン)
- Bedrockモデル有効化
- anthropic.claude-3-5-sonnet-20241022-v2:0を使用します。
今回はロールの作成やデプロイなどは割愛させてください・・・!
理由として準備段階でもかなりボリュームが多くなってしまうので、肝心のCode Interpreterまで辿り着くのが長くなってしまうからです。手順はGitHubに記載したのでこちらをご参照ください。
また一連の流れはMemory実装時と少し似ているので、こちらもご参考になれば幸いです。
実装手順
Strands Agentの実装
コード全量は長いので折りたたみます。
コード全量
import json
import boto3
import base64
import uuid
import os
from datetime import datetime
from typing import Dict, Any
from strands import Agent, tool
from strands.models import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from bedrock_agentcore.tools.code_interpreter_client import code_session
app = BedrockAgentCoreApp()
# S3設定
S3_BUCKET = os.environ.get('S3_BUCKET', 'your-bucket-name')
S3_REGION = os.environ.get('AWS_REGION', 'us-west-2')
# S3クライアントを初期化(エラーハンドリング付き)
try:
s3_client = boto3.client('s3', region_name=S3_REGION)
S3_AVAILABLE = True
except Exception as e:
print(f"S3 client initialization failed: {e}")
S3_AVAILABLE = False
# システムプロンプトを短縮
SYSTEM_PROMPT = """You are a data analysis expert AI assistant.
Key principles:
1. Verify all claims with code
2. Use execute_python tool for calculations
3. Show your work through code execution
4. ALWAYS provide S3 URLs when graphs are generated
When you create visualizations:
- Graphs are automatically uploaded to S3
- Share the S3 URLs with users (valid for 1 hour)
- Clearly indicate which figure each URL represents
- IMPORTANT: Only use matplotlib for plotting. DO NOT use seaborn or other plotting libraries
- Use matplotlib's built-in styles instead of seaborn themes
Available libraries:
- pandas, numpy for data manipulation
- matplotlib.pyplot for visualization (use ONLY this for plotting)
- Basic Python libraries (json, datetime, etc.)
The execute_python tool returns JSON with:
- isError: boolean indicating if error occurred
- structuredContent: includes image_urls (S3 links) or images (base64 fallback)
- debug_info: debugging information about code execution
Always mention generated graph URLs in your response."""
@tool
def execute_python(code: str, description: str = "") -> str:
"""Execute Python code and capture any generated graphs"""
if description:
code = f"# {description}\n{code}"
# Minimal image capture code
img_code = f"""
import matplotlib
matplotlib.use('Agg')
{code}
import matplotlib.pyplot as plt,base64,io,json
imgs=[]
for i in plt.get_fignums():
b=io.BytesIO()
plt.figure(i).savefig(b,format='png')
b.seek(0)
imgs.append({{'i':i,'d':base64.b64encode(b.read()).decode()}})
if imgs:print('_IMG_'+json.dumps(imgs)+'_END_')
"""
try:
# Keep session open while consuming the stream to avoid premature closure/timeouts
with code_session("us-west-2") as code_client:
response = code_client.invoke("executeCode", {
"code": img_code,
"language": "python",
"clearContext": False
})
# Consume stream inside the context
result = None
for event in response["stream"]:
result = event["result"]
if result is None:
# Safeguard: ensure we return a structured result even if no events arrived
result = {
"isError": True,
"structuredContent": {
"stdout": "",
"stderr": "No result events from Code Interpreter",
"exitCode": 1
}
}
# デバッグ情報を追加
result["debug_info"] = {
"code_size": len(img_code),
"original_code_size": len(code),
"img_code_preview": img_code[-200:], # 最後の200文字
}
# Check for images
stdout = result.get("structuredContent", {}).get("stdout", "")
# デバッグ情報を拡張
result["debug_info"]["stdout_length"] = len(stdout)
result["debug_info"]["img_marker_found"] = "_IMG_" in stdout
result["debug_info"]["stdout_tail"] = stdout[-300:] if len(stdout) > 300 else stdout
if "_IMG_" in stdout and "_END_" in stdout:
try:
start = stdout.find("_IMG_") + 5
end = stdout.find("_END_")
img_json = stdout[start:end]
imgs = json.loads(img_json)
# デバッグ情報にイメージ数を追加
result["debug_info"]["images_found"] = len(imgs)
# Clean output
clean_out = stdout[:stdout.find("_IMG_")].strip()
# S3へのアップロードを試行
image_urls = []
if S3_AVAILABLE and imgs:
for img_data in imgs:
try:
# 画像データをデコード
img_bytes = base64.b64decode(img_data['d'])
# ユニークなファイル名を生成
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_key = f"agent-outputs/{timestamp}_fig{img_data['i']}.png"
# S3にアップロード
s3_client.put_object(
Bucket=S3_BUCKET,
Key=file_key,
Body=img_bytes,
ContentType='image/png'
)
# 署名付きURLを生成(1時間有効)
url = s3_client.generate_presigned_url(
'get_object',
Params={'Bucket': S3_BUCKET, 'Key': file_key},
ExpiresIn=3600
)
image_urls.append({
'figure': img_data['i'],
'url': url,
's3_key': file_key
})
except Exception as e:
print(f"S3 upload error for figure {img_data['i']}: {e}")
result["debug_info"][f"s3_upload_error_fig{img_data['i']}"] = str(e)
result["debug_info"]["s3_fallback_message"] = "S3 upload failed, using base64 fallback"
# Enhanced result
result["structuredContent"]["stdout"] = clean_out
if image_urls:
result["structuredContent"]["image_urls"] = image_urls
result["debug_info"]["s3_upload_success"] = True
result["debug_info"]["uploaded_count"] = len(image_urls)
result["debug_info"]["s3_bucket"] = S3_BUCKET
else:
# S3が利用できない場合は元のbase64データを保持
result["structuredContent"]["images"] = imgs
result["debug_info"]["s3_upload_success"] = False
result["debug_info"]["s3_available"] = S3_AVAILABLE
if not S3_AVAILABLE:
result["debug_info"]["fallback_reason"] = "S3 client not available"
except Exception as e:
result["debug_info"]["image_parse_error"] = str(e)
else:
result["debug_info"]["images_found"] = 0
return json.dumps(result, ensure_ascii=False)
except Exception as e:
# エラー時も適切なフォーマットで返す
error_result = {
"isError": True,
"structuredContent": {
"stdout": "",
"stderr": f"Error executing code: {str(e)}",
"exitCode": 1
},
"debug_info": {
"error_type": type(e).__name__,
"error_message": str(e),
"code_size": len(img_code)
}
}
return json.dumps(error_result, ensure_ascii=False)
model = BedrockModel(
model_id="anthropic.claude-3-5-haiku-20241022-v1:0",
params={"max_tokens": 4096, "temperature": 0.7},
region="us-west-2"
)
agent = Agent(
tools=[execute_python],
system_prompt=SYSTEM_PROMPT,
model=model
)
@app.entrypoint
async def code_interpreter_agent(payload: Dict[str, Any]) -> str:
user_input = payload.get("prompt", "")
response_text = ""
tool_results = []
async for event in agent.stream_async(user_input):
if "data" in event:
response_text += event["data"]
# ツールの実行結果を収集
elif "tool_result" in event:
try:
result = json.loads(event["tool_result"])
if isinstance(result, dict) and "structuredContent" in result:
tool_results.append(result)
except:
pass
# S3 URLsがある場合はレスポンスに含める
image_urls = []
for tool_result in tool_results:
if "structuredContent" in tool_result and "image_urls" in tool_result["structuredContent"]:
image_urls.extend(tool_result["structuredContent"]["image_urls"])
if image_urls:
response_text += "\n\n **Generated Visualizations (S3 URLs - Valid for 1 hour):**"
for img_url_data in image_urls:
response_text += f"\n **Figure {img_url_data['figure']}**: [View Graph]({img_url_data['url']})"
response_text += f"\n └── Direct Link: {img_url_data['url']}"
return response_text
if __name__ == "__main__":
app.run()
ポイントごとに見ていきます。
まず、S3バケット名は環境変数および固定値を使っているので、適切な値に変更しておきましょう。
S3_BUCKET = os.environ.get('S3_BUCKET', 'your-bucket-name')
S3_REGION = os.environ.get('AWS_REGION', 'us-west-2')
また、プロンプトですが下記指示を英語で与えています。
SYSTEM_PROMPT = """You are a data analysis expert AI assistant.
Key principles:
1. Verify all claims with code
2. Use execute_python tool for calculations
3. Show your work through code execution
4. ALWAYS provide S3 URLs when graphs are generated
When you create visualizations:
- Graphs are automatically uploaded to S3
- Share the S3 URLs with users (valid for 1 hour)
- Clearly indicate which figure each URL represents
- IMPORTANT: Only use matplotlib for plotting. DO NOT use seaborn or other plotting libraries
- Use matplotlib's built-in styles instead of seaborn themes
Available libraries:
- pandas, numpy for data manipulation
- matplotlib.pyplot for visualization (use ONLY this for plotting)
- Basic Python libraries (json, datetime, etc.)
The execute_python tool returns JSON with:
- isError: boolean indicating if error occurred
- structuredContent: includes image_urls (S3 links) or images (base64 fallback)
- debug_info: debugging information about code execution
Always mention generated graph URLs in your response."""
与えられた指示をコード化して分析してその結果を教えてと指示するようにします。
画像も作成して、S3にアップロードされるからそのURLを教えてと指示するようにします。
使うライブラリも絞って使用するよう指示しておきます。
ツール実装
@tool
デコレータで実行する関数を登録し、関数は生成AIが考えたコードと、説明を引数に受け取ります。
画像生成用のコードも{code}
前後に仕込んでいるので、生成したコードをベースに画像を作成するようにしています! _IMG_
と_END_
で挟んで画像のバイナリ(Base64形式)を取り出せるようにしておきます!
あくまでCode Interpreterを試したく今回無理やり画像を捻り出す方法にしていますが、もっといいやり方を考えたいですね・・・
@tool
def execute_python(code: str, description: str = "") -> str:
"""Pythonコードを実行し、生成されたグラフをS3にアップロードします"""
# コードにコメントで説明を追加
if description:
code = f"# {description}\n{code}"
# 軽量な画像キャプチャコードを追加
img_code = f"""
import matplotlib
matplotlib.use('Agg')
{code}
import matplotlib.pyplot as plt,base64,io,json
imgs=[]
for i in plt.get_fignums():
b=io.BytesIO()
plt.figure(i).savefig(b,format='png')
b.seek(0)
imgs.append({{'i':i,'d':base64.b64encode(b.read()).decode()}})
if imgs:print('_IMG_'+json.dumps(imgs)+'_END_')
"""
#中略
code_session
で新規セッションを作成し、コードを引数に渡すだけで、Code Interpreterで実行されます。シンプルですね。
with code_session("us-west-2") as code_client:
response = code_client.invoke("executeCode", {
"code": img_code,
"language": "python",
"clearContext": False
})
# Consume stream inside the context
result = None
for event in response["stream"]:
result = event["result"]
出力結果からマーカーである_IMG_
と_END_
を探して、画像データを取り出します。
# 画像処理:標準出力から画像データを検出
stdout = result.get("structuredContent", {}).get("stdout", "")
if "_IMG_" in stdout and "_END_" in stdout:
# 画像データを抽出
start = stdout.find("_IMG_") + 5
end = stdout.find("_END_")
img_json = stdout[start:end]
imgs = json.loads(img_json)
# 標準出力をクリーンアップ
clean_out = stdout[:stdout.find("_IMG_")].strip()
取り出した画像データをS3にアップロードして、署名付きURLを発行します。
# S3へのアップロードを試行
image_urls = []
if S3_AVAILABLE and imgs:
for img_data in imgs:
try:
# 画像データをデコード
img_bytes = base64.b64decode(img_data['d'])
# ユニークなファイル名を生成
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_key = f"agent-outputs/{timestamp}_fig{img_data['i']}.png"
# S3にアップロード
s3_client.put_object(
Bucket=S3_BUCKET,
Key=file_key,
Body=img_bytes,
ContentType='image/png'
)
# 署名付きURLを生成(1時間有効)
url = s3_client.generate_presigned_url(
'get_object',
Params={'Bucket': S3_BUCKET, 'Key': file_key},
ExpiresIn=3600
)
発行したS3の署名付きURLを結果に含めて返却するようにします!
# S3 URLsがある場合はレスポンスに含める
if image_urls:
response_text += "\n\n **Generated Visualizations (S3 URLs - Valid for 1 hour):**"
for img_url_data in image_urls:
response_text += f"\n **Figure {img_url_data['figure']}**: [View Graph]({img_url_data['url']})"
response_text += f"\n └── Direct Link: {img_url_data['url']}"
コードの実装はこれで完了です!!
実際に動くか試してみましょう。
動作確認
AgentCoreのRuntimeを起動する簡単なスクリプトです。
長いので全体は折りたたみます。
コード全量
"""
デプロイしたCode Interpreter Agentを呼び出すテストスクリプト
"""
import boto3
import json
def invoke_code_interpreter_agent():
"""デプロイしたエージェントを呼び出し"""
# デプロイ時に取得したAgent ARNを設定
agent_arn = "arn:aws:bedrock-agentcore:us-west-2:YOUR_ACCOUNT_ID:runtime/YOUR_AGENT_ID"
# 上記のagent_arnを実際の値に置き換えてから実行してください!
if "YOUR_ACCOUNT" in agent_arn:
print("エラー: agent_arnを実際の値に置き換えてください")
print(" deploy_runtime.pyの実行結果から取得したAgent ARNを設定してください")
return None
client = boto3.client('bedrock-agentcore', region_name='us-west-2')
# Test queries in English
queries = [
"""Analyze the following sales data:
- Product A: [100, 120, 95, 140, 160, 180, 200]
- Product B: [80, 85, 90, 95, 100, 105, 110]
- Product C: [200, 180, 220, 190, 240, 260, 280]
Please visualize the sales trends and calculate growth rates."""
]
for i, query in enumerate(queries, 1):
print(f"\n{'='*60}")
print(f"テスト {i}: {query}")
print(f"{'='*60}")
payload = json.dumps({
"prompt": query
}).encode('utf-8')
try:
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
qualifier="DEFAULT",
payload=payload,
contentType='application/json',
accept='application/json'
)
# レスポンスの処理
if response.get('contentType') == 'application/json':
content = []
for chunk in response.get('response', []):
content.append(chunk.decode('utf-8'))
try:
result = json.loads(''.join(content))
print("エージェント応答:")
print(result)
except json.JSONDecodeError:
print("📄 Raw応答:")
raw_content = ''.join(content)
print(raw_content)
else:
print(f"予期しないContent-Type: {response.get('contentType')}")
print(f"レスポンス: {response}")
except Exception as e:
print(f"エラー: {e}")
if hasattr(e, 'response'):
error_message = e.response.get('Error', {}).get('Message', 'No message')
print(f"エラーメッセージ: {error_message}")
if __name__ == "__main__":
invoke_code_interpreter_agent()
agent_arn
にデプロイしたRuntimeのARNを記載します。
agent_arn = "arn:aws:bedrock-agentcore:us-west-2:YOUR_ACCOUNT_ID:runtime/YOUR_AGENT_ID"
簡易的な質問を実施する作りにしています。
配列なので複数質問したい場合は複数も設定可能です。
queries = [
"""Analyze the following sales data:
- Product A: [100, 120, 95, 140, 160, 180, 200]
- Product B: [80, 85, 90, 95, 100, 105, 110]
- Product C: [200, 180, 220, 190, 240, 260, 280]
Please visualize the sales trends and calculate growth rates."""
]
ここでは単位不明の謎売り上げデータを渡して分析させます。そして売上の傾向と、成長率を計算してと指示します。
実行してみます。
python invoke_agent.py
出力例
エージェント応答:
I'll help you analyze the sales data for Products A, B, and C. I'll create visualizations and calculate growth rates using Python.
1. First, let's create a visualization of the sales trends:Based on the analysis of the sales data, here are the key findings:
1. Sales Trends Visualization:
- The line graph shows the sales trends for all three products over the 7 periods
- Product C has consistently highest sales volume, ranging between 180-280 units
- Product A shows strong growth trend, starting at 100 and reaching 200 units
- Product B has the most stable but lowest sales volume, gradually increasing from 80 to 110 units
2. Overall Growth Rates:
- Product A showed the highest overall growth at 100% (doubled from 100 to 200 units)
- Product C grew by 40% (from 200 to 280 units)
- Product B had the lowest but still positive growth at 37.5% (from 80 to 110 units)
3. Period-over-Period Growth Rates:
- Product A shows the most volatile growth rates, ranging from -20.8% to +47.4%
- Product B shows the most consistent growth rates, steadily declining from 6.2% to 4.8%
- Product C shows moderate volatility, ranging from -13.6% to +26.3%
Key Observations:
1. Product A shows the most dramatic improvement but with highest volatility
2. Product B shows the most stable and predictable growth pattern
3. Product C maintains highest volume but with moderate volatility
The visualization can be viewed at: https://your-bucket-name.s3.amazonaws.com/agent-outputs/TIMESTAMP_fig1.png?[presigned-url-parameters]
分析してくれました!!最後にS3のリンクも付与されていて問題なくダウンロードできました!
売り上げと時期の推移を記載した図を描いてくれたんですね。
私が指示した謎の値が正確にプロットされていますね。何度か実行して全く同じ画像を作られたのと、Tool UseのログからCode Interpreter経由で実行されたと確認できました。
Tool Useのログは下記のように出力されていました。(カットして載せています)
```json
{
"resource": {
"attributes": {
"deployment.environment.name": "bedrock-agentcore:default",
"aws.local.service": "code_interpreter_agent_2.DEFAULT",
"service.name": "code_interpreter_agent_2.DEFAULT",
"cloud.region": "us-west-2",
"aws.log.stream.names": "runtime-logs",
"telemetry.sdk.name": "opentelemetry",
"aws.service.type": "gen_ai_agent",
"telemetry.sdk.language": "python",
"cloud.provider": "aws",
"cloud.resource_id": "arn:aws:bedrock-agentcore:us-west-2:YOUR_ACCOUNT_ID:runtime/YOUR_AGENT_ID/runtime-endpoint/DEFAULT:DEFAULT",
"aws.log.group.names": "/aws/bedrock-agentcore/runtimes/code_interpreter_agent_2-F1oystCkUi-DEFAULT",
"telemetry.sdk.version": "1.33.1",
"cloud.platform": "aws_bedrock_agentcore",
"telemetry.auto.version": "0.11.0-aws"
}
},
"scope": {
"name": "opentelemetry.instrumentation.botocore.bedrock-runtime",
"schemaUrl": "https://opentelemetry.io/schemas/1.30.0"
},
"timeUnixNano": 1756126601867181407,
"observedTimeUnixNano": 1756126601867186743,
"severityNumber": 9,
"severityText": "",
"body": {
"content": [
{
"text": "{\"content\": [{\"type\": \"text\", \"text\": \"Overall Growth Rates:\\nProduct A: 100.0%\\nProduct B: 37.5%\\nProduct C: 40.0%\\n\\nPeriod-over-Period Growth Rates:\\n\\nProduct A:\\n['20.0%', '-20.8%', '47.4%', '14.3%', '12.5%', '11.1%']\\n\\nProduct B:\\n['6.2%', '5.9%', '5.6%', '5.3%', '5.0%', '4.8%']\\n\\nProduct C:\\n['-10.0%', '22.2%', '-13.6%', '26.3%', '8.3%', '7.7%']\\n_IMG_[{\\\"i\\\": 1, \\\"d\\\": \\\"[BASE64_IMAGE_DATA_TRUNCATED]\\\"}]_END_\"}], \"structuredContent\": {\"stdout\": \"[OUTPUT_TRUNCATED]\", \"stderr\": \"\", \"exitCode\": 0, \"executionTime\": 1.2329983711242676, \"image_urls\": [{\"figure\": 1, \"url\": \"https://your-bucket-name.s3.amazonaws.com/[TRUNCATED]\"}]}, \"isError\": false, \"debug_info\": {\"code_size\": 1912, \"original_code_size\": 1611, \"img_code_preview\": \"[CODE_TRUNCATED]\", \"stdout_length\": 69601, \"img_marker_found\": true, \"stdout_tail\": \"[TRUNCATED]\", \"images_found\": 1, \"s3_upload_success\": true, \"uploaded_count\": 1, \"s3_bucket\": \"your-bucket-name\"}}"
}
],
"id": "tooluse_aRyqmVz3QACGTM84c5ze2A"
},
"attributes": {
"event.name": "gen_ai.tool.message",
"gen_ai.system": "aws.bedrock"
},
"flags": 1,
"traceId": "68ac5d7a68f728c70ed47fb358a02c4a",
"spanId": "f45341102e1c7f13"
}
```
ちなみにプロンプトが微妙なのか何度か実行すると成功と失敗を繰り返し、たまーにURLパラメータがカットされた署名付きではないURLも共有したりもありました。
2枚書いてくれる時もありました。2枚目を見ると、成長率がよりわかりやすかったです。
出力が安定しないのはこの辺のツールの使い方やプロンプト力なんですかね・・・もっと腕を磨きたい気持ちになりました。
おわりに
Amazon Bedrock AgentCore Code Interpreterを使ったStrands Agentの実装を試してみましたが、生成コードの実行を隔離できていいですね。また実行方法もStrands Agentなら@tool
デコレータで呼び出すだけなのでシンプルでした。
実行は簡単で便利なのですが、生成したコードによって作成されたもの(今回なら可視化画像)をどう連携するかをうまく工夫する必要があるなと気になりました。今回ならS3に連携部分とかですね。ここはもっとうまいやり方が分かりましたらまたブログ化してみたいと思います!
後は、S3ファイルをコード実行するやり方など今後試してみたいですね!
本記事が少しでも参考になりましたら幸いです。最後までご覧いただきありがとうございました!