如何在 AgentCore 部署首個支援智能體服務
Your First Agent as a Service: Deploying a Support Agent on AgentCore
文章示範如何在 AgentCore 部署支援智能體,並以實際呼叫和 X-Ray trace 驗證工具執行及可觀測性,最後拆除部署。
A support team wants its first automation. This article is the walkthrough: one folder, one command to deploy, one invoke to prove it works, one trace to prove you can see it, one command to make it all go away. Every number below was captured while writing this.
The use case
A three-person support desk answers the same questions all day: refund totals, return windows, policy lines. They want a first automation, and nobody on the team has ever put a service in production. The ask is not intelligence, it is a loop they can trust: write something small, make it callable from anywhere, see what it did afterwards, and be able to delete it the same day.
What AgentCore does here
Two of the nine AgentCore capabilities carry this use case.
- Runtime: hosts
app/aia_uc01/as a managed endpoint. The build is CodeZip, so nothing is containerized; each session gets its own microVM; the protocol is plain HTTP over/pingand/invocations. - Observability: every invoke emits OpenTelemetry spans into X-Ray, readable through CloudWatch, so the tool call is visible as its own span and not just as text in a log.
The other seven stay off. No Memory, so the agent keeps nothing between turns. No Gateway and no Identity, because the desk has no external systems to reach yet. The deployed runtime is version 1 behind the DEFAULT endpoint; changing the JSON and redeploying produces version 2 of the same endpoint, which is the whole versioning story at this level.
Requirements
- Node.js 20 or newer (22.22.2 here), the AgentCore CLI 0.31.1, AWS CLI 2.x, uv 0.11.9
- Python 3.10 to 3.14 on the host; the runtime target here is PYTHON_3_13, set in the contract, not your interpreter
- An AWS account with Bedrock model access; region us-east-1 for everything
- Model: deepseek.v3.2, passed to the code through the
MODEL_IDenvironment variable
agentcore --version # 0.31.1
aws configure get region # us-east-1
aws sts get-caller-identity --query Account --output text
# 123456789012
The code
https://github.com/pinxiong/bedrock-agentcore-in-action/tree/main/usecases/01-first-agent
01-first-agent/
├── run.py # every operation; the cross-platform entry point
├── Makefile # forwards to run.py, holds no logic
├── agentcore/
│ ├── agentcore.json # the contract: what gets deployed
│ └── aws-targets.json # account 123456789012, region us-east-1
├── app/aia_uc01/
│ ├── main.py # the agent: prompt, tool, sanitizer, entrypoint
│ ├── model/load.py # MODEL_ID -> BedrockModel
│ ├── model/sanitize.py # strips chat-template scaffolding from the stream
│ └── pyproject.toml # dependencies packaged into the zip
└── tests/
├── smoke.py # the four assertions, run against the deployment
└── doctor.py # toolchain, credentials and folder self-check
The folder is self-contained by assertion: make check copies it to a temporary directory and runs the checks again inside the copy.
How it works
The whole agent surface fits in one screen of main.py:
SYSTEM_PROMPT = """You are the first-line assistant for the Northwind Gear
support desk. Answer in one short sentence.
When a question involves arithmetic, call the add_numbers tool instead of
working it out yourself, then state the result in one short sentence.
You have no order database, no ticket history and no account lookup. When a
question needs one of those, say you cannot see it instead of guessing."""
@tool
def add_numbers(a: int, b: int) -> int:
"""Return the sum of two numbers."""
return a + b
@app.entrypoint
async def invoke(payload: Any, context: Any):
session_id = getattr(context, "session_id", "default-session")
agent = get_or_create_agent(session_id)
scrub = StreamSanitizer()
async for event in agent.stream_async(extract_prompt(payload)):
... # every text delta passes through scrub.feed() before it is yielded
The prompt is the product. The tool exists so the trace has something to show; an agent that never calls a tool produces a single span and proves little. The entrypoint is an async generator, which is why streaming is the default shape here rather than an add-on.
The part worth copying is the filter around the stream. DeepSeek models sometimes write fragments of their own chat-template markers, such as <|DSML|function_calls, into the ordinary text channel. The leak is intermittent: in the four local invokes and two cloud invokes captured for this article it never appeared, and in the previous walkthrough of this series it appeared on the first try. The sanitizer holds back a short suffix that could still grow into a marker, resets once per assistant message, and flushes what is left when the message ends:
if "messageStart" in inner:
scrub = StreamSanitizer() # one clean filter per assistant message
block_index = 0
yield event
continue
...
clean = scrub.feed(delta["text"]) # tool-use deltas pass through untouched
if clean:
yield _text_delta(clean, block_index)
It is cheap insurance with its own offline tests: make unit runs six cases against the recorded marker bytes, no AWS involved.
Model selection is one function, because the default matters:
def load_model() -> BedrockModel:
return BedrockModel(model_id=os.environ.get("MODEL_ID", DEFAULT_MODEL_ID))
If MODEL_ID is missing, an SDK default can pick a model your region refuses; passing the id explicitly is what keeps the first deploy from failing on geography.
Deploy it
From the use case folder:
make doctor
toolchain
PASS python 3.14.3 (inside the CodeZip 3.10 to 3.14 range)
PASS node v22.22.2
PASS aws cli aws-cli/2.15.6
PASS uv 0.11.9 (builds the app environment)
agentcore cli
PASS agentcore 0.31.1
this folder
PASS codeLocation 'app/aia_uc01' exists
...
PASS tests/copy_check.py present
aws environment
PASS credentials work, account 123456789012
PASS region is us-east-1
PASS model deepseek.v3.2 is available in us-east-1
15 passed, 0 warned, 0 failed
next: make deploy
The fifteen checks come in those four groups: four for the toolchain, one for the CLI, seven for the folder itself and three for the AWS environment.
make dev runs the same code locally on port 8080 with a real model and no deployment; the local invoke answered through the tool in about two seconds. Deploy is one command:
make deploy
Deploy to AWS ..................... 1 m 27 s
Total duration: 1 m 51 s
Deployed to 'default' (stack: AgentCore-aiauc01-default)
Outputs:
ApplicationAgentAiaUc01RuntimeArnOutput3227EF65:
arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/aiauc01_aia_uc01-R3MXCi9yld
Synthesizing CloudFormation took 15.4 s; the remaining 1 m 27 s was stack creation. The stack this contract produces is four resources: an AWS::BedrockAgentCore::Runtime, the AWS::IAM::Role it runs as, that role's AWS::IAM::Policy, and AWS::CDK::Metadata. That is the whole footprint; the resource types are recorded in evidence/23-stack-resources.txt. Status and one real invoke:
make status
make invoke
aia_uc01: Deployed - Runtime: READY
arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/aiauc01_aia_uc01-R3MXCi9yld
The refund total you should quote is $42.
Session: a99e785c-18e2-4b18-abef-368271019e7e
The four assertions run against the deployment, not against mocks:
make test
runtime
aiauc01_aia_uc01 v1 · arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/aiauc01_aia_uc01-R3MXCi9yld
PASS assert_runtime_1: the runtime exists in the target region and reports status READY
session 771b1dec-1664-4e54-9e69-409233cfbd5d · 16 events · tools ['add_numbers']
reply 'The refund total should be $42.'
PASS assert_runtime_2: invoking it answers a tool-using prompt and the reply has no model scaffolding
observability
trace 1-6ad4087a-7220c9ed52be46e901c25cc9 · span 'execute_tool add_numbers'
PASS assert_observability_1: that same invoke left an X-Ray trace whose span tree contains an execute_tool span
PASS independence: no other use case's resources present
4 passed, 0 failed
Teardown is one make target that runs two commands, and the second one does the deleting. make status is what confirms it:
make down
make status
All schemas reset to empty state
✓ Tear down stack
✓ Deployed to 'default' (stack: AgentCore-aiauc01-default)
Total duration: 1 m 28 s
aia_uc01: Local only
down rewrites agentcore.json and empties aws-targets.json; restore both from git before deploying again.
What it produced
The invoke is unremarkable on purpose: one sentence, the right arithmetic, nothing else. The interesting artifact is the trace, and the tree reads like the agent loop drawn by the runtime itself:
invoke_agent Strands Agents 2.50 s tools=["add_numbers"]
execute_event_loop_cycle cycle 1
chat deepseek.v3.2 0.68 s 431 in / 60 out
execute_event_loop_cycle cycle 2
chat deepseek.v3.2 1.82 s 514 in / 11 out
execute_tool add_numbers success
The execute_tool span is the point. It is what separates "the model said 42" from "the tool computed 42 and the model reported it", and it only exists on the deployed runtime; local agentcore dev traces record the tool call in logs but not as a span.
Cost, measured on this run: the traced invoke used 945 input and 71 output tokens, which at deepseek.v3.2 prices in us-east-1, 0.62 and 1.85 USD per million tokens, is 0.00072 USD of model calls. Six invokes happened on this run, four of them against the local dev server. The runtime bills per use, so a deployed agent left idle costs nothing, and the teardown leaves nothing tagged to this use case behind.
Troubleshooting
-
make downdied on its first line in a non-interactive shell with "This command requires an interactive terminal". Any piped or CI run hits this. The fix is the-yflag onagentcore remove all, which is whatrun.pynow passes. -
agentcore remove allemptiesagentcore.jsonand also wipesaws-targets.json, so the next deploy has no target and answersNo resources match the given filters. Restore both files from git after a teardown if you plan to deploy again.
Takeaways
- The contract is two JSON files; everything else on the AWS side is generated, so the deploy is reproducible and the teardown is real.
- CodeZip means no Dockerfile: the platform zips
codeLocation, and the runtime version and endpoint come from the same contract. - An agent is not observable until a tool call has its own span; write that assertion before you need it.
- A stream sanitizer belongs in the entrypoint from day one, because scaffolding leaks are intermittent and a test-only filter rots.
- The full loop, measured: doctor in seconds, deploy 1 m 51 s, invoke 9 911 ms, down 1 m 28 s.
來源:DEV Community · dev.to






