Skip to content

Commit d89b651

Browse files
committed
refactor(examples): use BedrockAgentCoreApp for AgentCore example
Rewrites the AgentCore zero-code example to run as a proper AgentCore server using BedrockAgentCoreApp from the bedrock-agentcore SDK. The handler is now an async generator registered with @app.entrypoint and the server starts with app.run(), matching how AgentCore expects agents to run. Updates the e2e tests to use subprocess.Popen instead of subprocess.run, wait for /ping health check, then POST to /invocations. Tests pass without AWS credentials because Strands exports OTLP spans even when BedrockModel raises NoCredentialsError.
1 parent 18b69d7 commit d89b651

4 files changed

Lines changed: 85 additions & 32 deletions

File tree

examples/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ python examples/zero-code-examples/ollama/run.py
222222
python examples/zero-code-examples/strands/run.py
223223
python examples/zero-code-examples/adk/run.py
224224
python examples/zero-code-examples/pydantic-ai/run.py
225-
python examples/zero-code-examples/agentcore/run.py
225+
226+
# AgentCore starts a server (AWS credentials required):
227+
python examples/zero-code-examples/agentcore/run.py &
228+
curl http://localhost:8080/invocations -d '{"prompt": "Roll a 20-sided die for me"}'
226229

227230
# SDK examples:
228231
python examples/sdk_example/context_manager_example.py

examples/zero-code-examples/agentcore/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
bedrock-agentcore>=1.8.0
12
strands-agents>=1.35.0
23
boto3>=1.38.0
34
opentelemetry-sdk>=1.36.0
Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
"""AWS AgentCore zero-code OTLP example. Needs AWS credentials and Nova Pro model access.
2-
Run: python examples/zero-code-examples/agentcore/run.py
1+
"""AWS AgentCore zero-code OTLP example -- no agentevals SDK.
2+
3+
Demonstrates zero-code integration: wraps a Strands agent with BedrockAgentCoreApp
4+
so it runs on the AgentCore runtime. The only agentevals setup is a standard
5+
OTLPSpanExporter pointing at the receiver.
6+
7+
Prerequisites:
8+
1. pip install -r examples/zero-code-examples/agentcore/requirements.txt
9+
2. agentevals serve --dev
10+
3. export AWS_DEFAULT_REGION=us-east-1 (Nova Pro model access required)
11+
12+
Usage (direct):
13+
python examples/zero-code-examples/agentcore/run.py
14+
curl http://localhost:8080/invocations -d '{"prompt": "Roll a 20-sided die"}'
15+
16+
Usage (via AgentCore CLI, installed with npm install -g @aws/agentcore):
17+
agentcore dev
318
"""
419

520
import os
621
import random
722

23+
from bedrock_agentcore import BedrockAgentCoreApp
824
from dotenv import load_dotenv
925
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
1026
from opentelemetry.sdk.trace.export import BatchSpanProcessor
@@ -14,6 +30,13 @@
1430

1531
load_dotenv(override=True)
1632
os.environ.setdefault("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental")
33+
os.environ.setdefault("OTEL_RESOURCE_ATTRIBUTES",
34+
"agentevals.eval_set_id=agentcore_eval,agentevals.session_name=agentcore-zero-code")
35+
36+
_telemetry = StrandsTelemetry()
37+
_telemetry.tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(), schedule_delay_millis=1000))
38+
39+
app = BedrockAgentCoreApp()
1740

1841

1942
@tool
@@ -29,26 +52,17 @@ def check_prime(n: int) -> bool:
2952
return n >= 2 and all(n % i for i in range(2, int(n**0.5) + 1))
3053

3154

32-
def main():
33-
if not os.getenv("AWS_DEFAULT_REGION"):
34-
print("AWS_DEFAULT_REGION not set.")
35-
return
36-
os.environ.setdefault("OTEL_RESOURCE_ATTRIBUTES",
37-
"agentevals.eval_set_id=agentcore_eval,agentevals.session_name=agentcore-zero-code")
38-
telemetry = StrandsTelemetry()
39-
telemetry.tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(), schedule_delay_millis=1000))
55+
@app.entrypoint
56+
async def handler(payload):
57+
prompt = payload.get("prompt", "Hello!")
4058
agent = Agent(
4159
model=BedrockModel(model_id="us.amazon.nova-pro-v1:0"),
4260
tools=[roll_die, check_prime],
43-
system_prompt="You are a helpful assistant. Use roll_die when asked to roll dice. Use check_prime when asked about prime numbers.",
61+
system_prompt="Use roll_die when asked to roll dice. Use check_prime when asked about prime numbers.",
4462
name="dice_agent",
4563
)
46-
queries = ["Hi! Can you help me?", "Roll a 20-sided die for me", "Is the number you rolled prime?"]
47-
for i, query in enumerate(queries, 1):
48-
print(f"\n[{i}/{len(queries)}] User: {query}\n Agent: {agent(query)}")
49-
telemetry.tracer_provider.force_flush()
50-
print("All traces flushed.")
64+
async for event in agent.stream_async(prompt):
65+
yield event
5166

5267

53-
if __name__ == "__main__":
54-
main()
68+
app.run()

tests/integration/test_live_agents.py

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
from __future__ import annotations
1717

18+
import contextlib
1819
import os
1920
import subprocess
2021
import sys
22+
import time
2123

2224
import httpx
2325
import pytest
@@ -38,9 +40,6 @@
3840
reason="GOOGLE_API_KEY not set",
3941
)
4042

41-
_skip_no_aws = pytest.mark.skipif(not os.environ.get("AWS_DEFAULT_REGION"), reason="AWS_DEFAULT_REGION not set")
42-
43-
4443
def _run_agent(
4544
script: str,
4645
otlp_http_port: int,
@@ -66,6 +65,46 @@ def _run_agent(
6665
)
6766

6867

68+
_AGENTCORE_ENV = {"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental"}
69+
_AGENTCORE_SCRIPT = "examples/zero-code-examples/agentcore/run.py"
70+
71+
72+
@contextlib.contextmanager
73+
def _agentcore_server(otlp_http_port: int, session_name: str, extra_env: dict | None = None):
74+
env = {
75+
**os.environ,
76+
"OTEL_EXPORTER_OTLP_ENDPOINT": f"http://127.0.0.1:{otlp_http_port}",
77+
"OTEL_RESOURCE_ATTRIBUTES": f"agentevals.eval_set_id=e2e-test,agentevals.session_name={session_name}",
78+
**(extra_env or {}),
79+
}
80+
proc = subprocess.Popen(
81+
[sys.executable, os.path.join(REPO_ROOT, _AGENTCORE_SCRIPT)],
82+
env=env,
83+
cwd=REPO_ROOT,
84+
)
85+
try:
86+
for _ in range(20):
87+
if proc.poll() is not None:
88+
raise RuntimeError(f"agentcore server exited early (code {proc.returncode})")
89+
try:
90+
httpx.get("http://127.0.0.1:8080/ping", timeout=1)
91+
break
92+
except Exception:
93+
time.sleep(0.5)
94+
else:
95+
proc.kill()
96+
raise RuntimeError("agentcore server did not start within 10s")
97+
yield proc
98+
finally:
99+
time.sleep(2)
100+
proc.terminate()
101+
try:
102+
proc.wait(timeout=5)
103+
except subprocess.TimeoutExpired:
104+
proc.kill()
105+
proc.wait()
106+
107+
69108
@_skip_no_openai
70109
class TestLangchainZeroCode:
71110
"""Run the LangChain zero-code OTLP example and verify session grouping."""
@@ -367,34 +406,30 @@ def test_session_visible_via_api(self, live_servers):
367406
assert session_name in session_ids
368407

369408

370-
_AGENTCORE_ENV = {"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental"}
371-
_AGENTCORE_SCRIPT = "examples/zero-code-examples/agentcore/run.py"
372-
373409

374-
@_skip_no_aws
375410
class TestAgentCoreZeroCode:
376411
def test_session_created_spans_only(self, live_servers):
377412
main_port, otlp_http_port, mgr = live_servers
378413
session_name = "e2e-agentcore"
379-
result = _run_agent(_AGENTCORE_SCRIPT, otlp_http_port, session_name, extra_env=_AGENTCORE_ENV, timeout=60)
380-
assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
414+
with _agentcore_server(otlp_http_port, session_name, extra_env=_AGENTCORE_ENV):
415+
httpx.post("http://127.0.0.1:8080/invocations", json={"prompt": "Roll a 20-sided die"}, timeout=60)
381416
wait_for_session_complete_sync(mgr, session_name, timeout=60)
382417
s = mgr.sessions[session_name]
383418
assert s.is_complete and s.source == "otlp" and len(s.spans) > 0
384419

385420
def test_invocations_extracted(self, live_servers):
386421
main_port, otlp_http_port, mgr = live_servers
387422
session_name = "e2e-agentcore-inv"
388-
result = _run_agent(_AGENTCORE_SCRIPT, otlp_http_port, session_name, extra_env=_AGENTCORE_ENV, timeout=60)
389-
assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
423+
with _agentcore_server(otlp_http_port, session_name, extra_env=_AGENTCORE_ENV):
424+
httpx.post("http://127.0.0.1:8080/invocations", json={"prompt": "Is 17 prime?"}, timeout=60)
390425
wait_for_session_complete_sync(mgr, session_name, timeout=60)
391426
assert len(mgr.sessions[session_name].invocations) > 0
392427

393428
def test_session_visible_via_api(self, live_servers):
394429
main_port, otlp_http_port, mgr = live_servers
395430
session_name = "e2e-agentcore-api"
396-
result = _run_agent(_AGENTCORE_SCRIPT, otlp_http_port, session_name, extra_env=_AGENTCORE_ENV, timeout=60)
397-
assert result.returncode == 0
431+
with _agentcore_server(otlp_http_port, session_name, extra_env=_AGENTCORE_ENV):
432+
httpx.post("http://127.0.0.1:8080/invocations", json={"prompt": "Hello!"}, timeout=60)
398433
wait_for_session_complete_sync(mgr, session_name, timeout=60)
399434
data = httpx.get(f"http://127.0.0.1:{main_port}/api/streaming/sessions").json()["data"]
400435
assert session_name in [s["sessionId"] for s in data]

0 commit comments

Comments
 (0)