最新资讯
reddit.com OpenAI 称 Astra 仅满足 10% 安全任务标准 dev.to FreeToken:在自有 GPU 上运行前沿 MoE 模型及其内存需求 dev.to 当 AI Agent 说“完成”时,究竟完成了什么? dev.to 部署 Underdog Bark 3.0 Pro:逐字转写、自定义后端与分块设置 dev.to 为艰难对话构建 AI:五个 Agent、一条安全正则与一位盲评监督者 techcrunch.com Palo Alto Networks 洽谈以 5 亿美元收购 AI Agent 安全初创公司 Clutch theinformation.com 黄仁勋被要求就涉华间谍指控向国会作简报 www.infoq.com Edera 推出 Object Capability Bounded Authority,试图将凭证与 AI Agent 工作负载隔离 wechat 这波杀疯了!旗舰级模型不限量、1个月视频额度免费领 bloomberg.com AI 抛售蔓延至信用市场,软银领跌亚洲美元债 dev.to 如何在专用 GPU 服务器上用 OpenAI 兼容 API 部署 LLM dev.to 审计 153 份 Claude Code 与 Codex 会话记录:User-Agent 与 URL 中的邮箱泄露
多元动态
登录 注册
智能体 dev.to · @Davide De Sio

使用 AgentCore Runtime 和 Amplify Gen 2 部署实时语音 Agent

原文 我的收藏

文章延续此前基于 Amazon DynamoDB Vector Search 的语义商品搜索工作,将本地运行的语音 agent(由 Amazon Nova Sonic 驱动的 Strands BidiAgent)部署到 AWS。目标是让语音 agent 运行在 AWS 上,复用应用已有用户认证,并作为同一 Amplify Gen 2 后端的一部分,无需独立项目、独立认证或独立部署命令,方案基于 Amazon Bedrock AgentCore Runtime。

AI推荐理由 给出将本地实时语音 agent 部署到 AWS 的具体方案,结合 Amazon Bedrock AgentCore Runtime 与 Amplify Gen 2,复用既有用户认证与后端,涉及 Strands BidiAgent 和 Amazon Nova Sonic。属于有明确技术栈和部署路径的实践教程,对 agent 落地有参考价值。

72

正文

🏃 TL;DR In the previous article I built a semantic product search on top of Amazon DynamoDB Vector Search, and then gave that capability to an AI agent as a tool. One of the things I explored at the end was a voice agent: a Strands BidiAgent powered by Amazon Nova Sonic that could search the catalog by voice. That voice agent ran locally. A Python server on my laptop, a WebSocket, my microphone. Great for a demo, but it lives on my machine. So the question for this article is: how do I actually deploy it? I want the voice agent to run on AWS, I want it authenticated with the same users my app already has, and I want it to be part of the same Amplify Gen 2 backend as everything else. No separate project, no separate auth, no separate deploy command. It turns out this fits together really nicely with Amazon Bedrock AgentCore Runtime. Let me walk through it. davide-desio-eleva / dynamodbvector An example application using Dynamo Db Vector Search as an AI tool Semantic Search & Voice Agents on AWS: Amazon DynamoDB Vector Search, Amplify Gen 2, Amazon AgentCore & Amazon Nova Sonic Companion posts: Your database is an AI tool: semantic search with Amazon DynamoDB Vector Search Deploying a real-time voice agent with AgentCore Runtime and Amplify Gen 2 original post on dev.to A sample application that shows how to use Amazon DynamoDB native vector search to build semantic search over application data, how to expose that capability to AI agents as a tool, and how to deploy a real-time voice agent for it on Amazon Bedrock AgentCore Runtime — all inside a single AWS Amplify Gen 2 backend. It demonstrates the same idea through three interfaces: Search — type a natural language query and get semantically matched products Chat — a conversational AI assistant (Amplify AI Kit) that calls the search as a tool Voice — a real-time voice agent (Strands… View on GitHub 🎙️ Where we left off Quick recap of the voice agent: it's a Python app: a FastAPI server that exposes a WebSocket on /ws, and a Strands BidiAgent wired to Amazon Nova Sonic with our search_products tool. from fastapi import FastAPI, WebSocket from strands.experimental.bidi import BidiAgent from strands.experimental.bidi.models import BidiNovaSonicModel from strands import tool @tool def search_products(query: str) -> str: """Search the product catalog using natural language.""" # Nova Micro parses the query, Titan embeds it, # DynamoDB SearchVectors finds the matches, we filter by price. ... sonic_model = BidiNovaSonicModel(model_id="amazon.nova-2-sonic-v1:0", ...) app = FastAPI() @app.websocket("/ws") async def voice_chat(websocket: WebSocket): agent = BidiAgent(model=sonic_model, tools=[search_products], ...) await websocket.accept() await agent.run( inputs=[websocket.receive_json], outputs=[websocket.send_json], ) Enter fullscreen mode Exit fullscreen mode Locally I ran this with uvicorn, the browser connected to ws://127.0.0.1:8080/ws, and everything worked. Now I want the exact same code running on AWS. 🧩 What is Amazon Bedrock AgentCore Runtime, and why it fits Amazon Bedrock AgentCore Runtime is a serverless runtime purpose-built for hosting AI agents. It's framework-agnostic (Strands, LangGraph, CrewAI, whatever) and, importantly for us, it supports bidirectional streaming over WebSocket, which is exactly what a real-time voice agent needs. Amazon Bedrock AgentCore Runtime contract is simple, you give it a container that listens on port 8080 and exposes a WebSocket at /ws, plus a /ping health check. That's already how our agent is written. Amazon Bedrock AgentCore handles the rest: session isolation, scaling, authentication, and the public WebSocket endpoint. So the plan is: Package the voice agent as a docker container. Deploy it to AgentCore Runtime. Authenticate it with the Cognito user pool Amplify already created. Connect the browser to it. And because Amplify Gen 2 is CDK under the hood, I can do all of this inside the same amplify/backend.ts I already have, without a second project or a specific CDK/CloudFormation/Terraform project. 📦 Step 1: containerize the agent Amazon Bedrock AgentCore Runtime runs ARM64 containers. The Dockerfile is minimal: FROM --platform=linux/arm64 python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY agent.py . ENV CONTAINER_ENV=true EXPOSE 8080 CMD ["python", "agent.py"] Enter fullscreen mode Exit fullscreen mode The only change to the agent itself is binding to 0.0.0.0 when running in the container (locally it stayed on 127.0.0.1): host = "0.0.0.0" if os.getenv("CONTAINER_ENV") else "127.0.0.1" uvicorn.run(app, host=host, port=8080) Enter fullscreen mode Exit fullscreen mode 🏗️ Step 2: deploy it from the Amplify backend Here's the part I like: again, since Amplify Gen 2 backends are CDK constructs, I can build the image and create the runtime right in my backend.ts. First, we should build the ARM64 image and push it to Amazon ECR. The CDK DockerImageAsset does all of that during deployment, no manual docker build or docker push: import * as ecrAssets from "aws-cdk-lib/aws-ecr-assets"; const voiceImage = new ecrAssets.DockerImageAsset(voiceStack, "VoiceAgentImage", { directory: path.join(__dirname, "..", "voice-agent"), platform: ecrAssets.Platform.LINUX_ARM64, }); Enter fullscreen mode Exit fullscreen mode Then we need an execution role for the runtime: it needs to pull the image, call Amazon Bedrock (Amazon Nova Sonic 2 for voice, plus Amazon Nova Micro and Amazon Titan Embeddings for the search tool), and run SearchVectors on the DynamoDB table: const voiceRuntimeRole = new iam.Role(voiceStack, "VoiceAgentRuntimeRole", { assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com", { conditions: { StringEquals: { "aws:SourceAccount": account } }, }), }); voiceImage.repository.grantPull(voiceRuntimeRole); voiceRuntimeRole.addToPolicy(new iam.PolicyStatement({ actions: ["ecr:GetAuthorizationToken"], resources: ["*"], })); voiceRuntimeRole.addToPolicy(new iam.PolicyStatement({ actions: [ "bedrock:InvokeModel", "bedrock:InvokeModelWithBidirectionalStream", ], resources: [ "arn:aws:bedrock:*::foundation-model/amazon.nova-2-sonic-v1:0", "arn:aws:bedrock:*::foundation-model/amazon.titan-embed-text-v2:0", "arn:aws:bedrock:*::foundation-model/amazon.nova-micro-v1:0", `arn:aws:bedrock:*:${account}:inference-profile/eu.amazon.nova-micro-v1:0`, ], })); voiceRuntimeRole.addToPolicy(new iam.PolicyStatement({ actions: ["dynamodb:SearchVectors", "dynamodb:GetItem"], resources: [productsTable.tableArn, `${productsTable.tableArn}/index/*`], })); Enter fullscreen mode Exit fullscreen mode And finally we need the runtime itself. Here I deliberately reach for the L1 CfnRuntime construct. Level 1 (L1) constructs map directly one-to-one to raw CloudFormation resources, while Level 2 (L2) constructs provide higher-level, object-oriented abstractions with built-in security best practices and helper methods. For a service this new I prefer the L1: it maps straight onto the CloudFormation resource, so what I write is exactly what gets deployed, with no abstraction deciding things for me. import { CfnRuntime } from "aws-cdk-lib/aws-bedrockagentcore"; const voiceRuntime = new CfnRuntime(voiceStack, "VoiceAgentRuntime", { agentRuntimeName: "voiceShoppingAgent", agentRuntimeArtifact: { containerConfiguration: { containerUri: voiceImage.imageUri }, }, networkConfiguration: { networkMode: "PUBLIC" }, protocolConfiguration: "HTTP", roleArn: voiceRuntimeRole.roleArn, environmentVariables: { CONTAINER_ENV: "true", TABLE_NAME: productsTable.tableName, BEDROCK_REGION: "eu-north-1", // ...model ids }, authorizerConfiguration: { customJwtAuthorizer: { discoveryUrl, allowedClients: [userPoolClient.userPoolClientId], }, }, }); Enter fullscreen mode Exit fullscreen mode That protocolConfiguration: "HTTP" is worth a note. The valid protocol values are HTTP, A2A, AGUI and MCP, there is no WEBSOCKET value. Bidirectional streaming over WebSocket runs on top of the HTTP server protocol: the container exposes /ws, AgentCore speaks WebSocket to the client, but as far as the runtime configuration is concerned, the protocol is HTTP. One detail on the execution role: alongside grantPull() (which covers BatchGetImage and GetDownloadUrlForLayer on the repository) the role also needs ecr:GetAuthorizationToken, and that action requires a "*" resource. Both are in the role above so the runtime can pull the image. 🔐 Step 3: reuse the Amplify Cognito user pool This is where the "same backend" idea pays off as ny app already has authentication: Amplify created a Amazon Cognito user pool and users sign in to use the chat and search. I don't want a second identity system for the voice agent. AgentCore Runtime supports JWT inbound authorization. You point it at an OIDC discovery URL and a list of allowed clients. An Amazon Cognito user pool is also an OIDC provider, so I can wire the runtime straight to it: const userPool = backend.auth.resources.userPool; const userPoolClient = backend.auth.resources.userPoolClient; const discoveryUrl = `https://cognito-idp.${region}.amazonaws.com/` + `${userPool.userPoolId}/.well-known/openid-configuration`; // ...passed into the runtime's authorizerConfiguration.customJwtAuthorizer Enter fullscreen mode Exit fullscreen mode Now the same user who is signed into the app can authenticate to the voice agent, with no extra setup. The token they already have is the token the runtime accepts. 🔄 Granting the user permission to invoke the runtime The signed-in user connects to the runtime, so the Amazon Cognito authenticated role needs permission to invoke it. There's a nice detail in how you wire this up: the voice stack already depends on the auth stack (it reads the user pool), so you want the dependency to stay one-directional. The clean way is to define the policy inside the voice stack and attach it to the existing auth role by reference: new iam.Policy(voiceStack, "VoiceAgentInvokePolicy", { roles: [backend.auth.resources.authenticatedUserIamRole], statements: [ new iam.PolicyStatement({ actions: [ "bedrock-agentcore:InvokeAgentRuntime", "bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStream", ], resources: [ voiceRuntime.attrAgentRuntimeArn, `${voiceRuntime.attrAgentRuntimeArn}/*`, ], }), ], }); Enter fullscreen mode Exit fullscreen mode The policy is created in the voice stack, which is allowed to reference the auth role, while the auth stack never needs to know about the voice stack. The dependency flows in a single direction. 🌐 Step 4: connect the browser The last piece is the frontend. Locally the browser connected to ws://127.0.0.1:8080/ws. Deployed, it connects to the AgentCore endpoint: wss://bedrock-agentcore..amazonaws.com/runtimes//ws Enter fullscreen mode Exit fullscreen mode The interesting question is authentication. AgentCore accepts SigV4 (signed headers or a presigned URL) or an OAuth bearer token. From a browser, SigV4 on a WebSocket is awkward, because the browser's WebSocket API doesn't let you set custom headers on the handshake. AWS documents a clean workaround for exactly this case: pass the bearer token through the Sec-WebSocket-Protocol header. The token is base64url-encoded and sent as a subprotocol, alongside a sentinel subprotocol: import { fetchAuthSession } from "aws-amplify/auth"; const session = await fetchAuthSession(); const token = session.tokens?.accessToken?.toString(); const base64url = (s: string) => btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); const url = `wss://bedrock-agentcore.${region}.amazonaws.com` + `/runtimes/${encodeURIComponent(runtimeArn)}/ws` + `?qualifier=DEFAULT&X-Amzn-Bedrock-AgentCore-Runtime-Session-Id=${sessionId}`; const protocols = [ `base64UrlBearerAuthorization.${base64url(token)}`, "base64UrlBearerAuthorization", ]; const ws = new WebSocket(url, protocols); Enter fullscreen mode Exit fullscreen mode That Amazon Cognito access token is exactly what the runtime's JWT authorizer validates. The user's client_id claim has to match the allowedClients we configured, which it does, because it's the same user pool client Amplify gave us. From here on, the rest of the frontend doesn't change at all. The same code that captured microphone audio, streamed PCM frames, and played back the agent's voice against the local server now works against AgentCore. Only the URL and the auth changed. 🗺️ The whole picture Everything lives in one Amplify Gen 2 backend and deploys with a single npx ampx sandbox: The browser signs in once with Amazon Cognito. That identity gets it into the app, into the search API, into the chat, and now into the voice agent too. 🧠 What I take away from this project A couple of things stood out while building this. The first is how little the agent code changed between local and deployed. The same FastAPI + Strands BidiAgent server ran on my laptop and, unchanged, inside Amazon Bedrock AgentCore. The container contract (port 8080, /ws, /ping) is simple enough that "make it a container" was the only real step. The second is the value of keeping it all in one backend. Because Amplify Gen 2 is CDK, the runtime, its image, its IAM, and its wiring to Amazon Cognito are all just constructs next to my data and auth definitions. The voice agent isn't a separate system I have to operate, it's another resource in the same deploy, sharing the same users. The voice agent that used to live on my laptop now runs on AWS, authenticated with the users my app already had, deployed with the same command as everything else. Your Amplify Gen 2 deployed Amazon DynamoDb database was already an AI tool. Now the agent that talks to it is serverless and, thanks to CDK, it's wired to Amplify Gen 2 deployments too. davide-desio-eleva / dynamodbvector An example application using Dynamo Db Vector Search as an AI tool Semantic Search & Voice Agents on AWS: Amazon DynamoDB Vector Search, Amplify Gen 2, Amazon AgentCore & Amazon Nova Sonic Companion posts: Your database is an AI tool: semantic search with Amazon DynamoDB Vector Search Deploying a real-time voice agent with AgentCore Runtime and Amplify Gen 2 original post on dev.to A sample application that shows how to use Amazon DynamoDB native vector search to build semantic search over application data, how to expose that capability to AI agents as a tool, and how to deploy a real-time voice agent for it on Amazon Bedrock AgentCore Runtime — all inside a single AWS Amplify Gen 2 backend. It demonstrates the same idea through three interfaces: Search — type a natural language query and get semantically matched products Chat — a conversational AI assistant (Amplify AI Kit) that calls the search as a tool Voice — a real-time voice agent (Strands… View on GitHub 🙋 Who am I I'm D. De Sio and I work as a Head of Software Engineering in Eleva. As of September 2026, I’m an AWS Certified Solution Architect Professional and AWS Certified DevOps Engineer Professional, but also a User Group Leader (in Pavia), an AWS Community Builder and, last but not least, a #serverless enthusiast. 🎉 AWS Community Day Italy The full agenda for AWS Community Day Italy is out! If you'd love to hear what the community has been working on, what they've learned, and what they want to share, come join us in Rome on October 2nd.

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容