
CopilotKit × Strands Agents on Bedrock AgentCore構成を試してみた
はじめに
Strands AgentsがAG-UIに対応しました。これによりCopilotKitからStrandsを呼び出すことが可能です。
CopilotKitとAWS Strandsを使ったQuickStartがあったので、こちらをベースに少しカスタマイズしつつ、サーバー側だけホスティングしてみたいと思います。
構成
構成は以下の通りです。

CopilotKitはNext.jsを使っており、App Router の Route Handler(API Route)を使っているため静的ホストできないため、今回はローカルとし対象外とします。サクッとデプロイするならCopilot CloudやVercelなどのホスティングサービスを利用すると良いと思います。
Amazon API Gatewayは先月(25年11月末)に発表されたAPI Gatewayのストリーミング時間が15分に拡張されたため、LLMの長いストリーミングに対応できるようになりました。
また、API Gatewayの認証、認可の幅広い機能やWAFなどをAgentCoreの前段に設定することが可能です。
CopilotKitのQuickStartから始まりますが、途中で大きく構成を変更するため、以下のリポジトリを参照するのが良いと思います。
構築
CopilotkitのStrands(Python)のQuickStart
CopilotKitのStrandsのQuick Startを利用します。適宜読み替えていますので、最終的にはリポジトリのソースを確認するのが良いと思います。
まずテンプレートを初期化します。
$ pnpm dlx copilotkit@latest create -f aws-strands-py
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠙⣿⡛⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⠈⢿⡄⠀⠀⠀⠈⠉⠙⣻⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠈⢿⡄⠀⢀⣠⣴⠾⠋⢸⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⢀⣀⣀⣀⣀⣤⣤⡾⢿⡟⠛⠉⠀⠀⠀⠀⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡛⠛⠛⠛⠉⠉⠉⠁⠀⢠⡿⣿⡀⠀⠀⠀⠀⠀⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⣰⡟⠀⠸⣧⠀⠀⠀⠀⢠⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⢀⣼⠏⠀⠀⠀⣿⡀⠀⠀⠀⢸⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠂⣠⡿⠁⠀⠀⠀⠀⢸⡇⠀⠀⠀⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⣡⣾⣿⣄⠀⠀⠀⠀⠀⢸⡇⠀⠀⢰⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⡟⠛⡿⠋⣡⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀⢸⡇⠀⠀⣿⣿⣿⣿
⣿⣿⣿⣿⡿⠿⣿⠷⠂⡀⠘⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⢸⡇⠀⣼⣿⣿⣿⣿
⣿⣿⠻⢿⡷⠀⠁⠴⣿⣷⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⣾⠇⣴⣿⣿⣿⣿⣿
⡿⠛⠀⠀⢴⣾⣷⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣤⣿⣾⣿⣿⣿⣿⣿⣿
⣷⣾⣿⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
~ Welcome to CopilotKit! ~
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
? What is your project named? copilotkit-strands-agentcore
依存関係をインストールします。
pnpm install
動作確認するためOpenAIのAPIキーを利用します。この後Bedrockに書き換えるので、APIキーがない場合はこちらの手順はスキップで問題ありません。
export OPENAI_API_KEY='your_openai_api_key'
perl -i -pe "s/your_openai_api_key/${OPENAI_API_KEY}/" agent/.env
以下のコマンドで、Webアプリが http://localhost:3000に Webサーバー(Strands)が http://localhost:8000 に立ち上がります。
pnpm run dev
http://localhost:3000 に Webアプリが起動していることを確認します。赤枠のGenerative UIを押下します。

UIがBotから返却されれば、正しく動作していることがわかります。

ここからテンプレートを変更して冒頭の構成になるようにコードを書き換えます。最終的な差分はこちらを参考にしてください
主な変更点は以下の通りです
- テンプレートだとnpmで取り回しが難しい部分があるので、pnpm workspaceとturboでプロジェクト構成へ変更
- 自動生成されたテンプレートのpyproject.tomlに利用していないライブラリも含まれており、削除
- APIGatewayから呼ばれるLambdaの実装を追加
- AgentCoreの要件に合うようにAgentのソースを修正
- インフラ(iacコード)定義の追加
Amazon API Gatewayから呼び出されるLambda実装
LambdaはそのままAgentCoreにリクエストをプロキシします。
Amazon API Gatewayから呼び出されストリーミングをする際に、awslambda.streamifyResponseを使うパターンもありますが、Honoの場合streamHandleを使えば、Lambda特有のコードを書かずにすみます。
AgentCoreのアプリケーション実装
先ほどのテンプレートからAgentCoreにデプロイできるように変更をしていきます。
AgentCoreへのデプロイ要件への対応
及び、OpenAIモデルからBedrockモデルへ変更します。該当箇所を示します。
/invocations パスの実装。ag_ui_strands の パスを置き換えて対応します。
関連してこちらのag-ui-strandsのAgent定義が、AG-UIの処理をラップしたStrandsのAgent実装と理解しています。(違っていた場合、フィードバックお願いします🙇)
Webアプリ側も同様に変更が必要です。
/ping パスの実装
8080ポートでLISTENする
Bedrockの実装に差し替えます。
Dockerfileを定義します。
インフラ実装
AgentCoreを実装します。Dockerfileを指定してCDK側でビルド、デプロイしています。
.dockerignore で /agent 以外のソースをdocker contextに送らないように注意してください。(データサイズが大きいとCDKがクラッシュします)
Lambdaの設定とAPIGatewayのストリーミングの設定を行います。これによりSSEで29秒のタイムアウトの壁を超えることができます。
CDKをデプロイします
$ cd iac
$ pnpm run deploy
> iac@0.1.0 deploy /Users/shuntaka/repos/github.com/shuntaka9576/copilotkit-strands-agentcore/iac
> cdk deploy
(中略)
Outputs:
CopilotKitStrandsStack.RestApiEndpoint0551178A = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1/
CopilotKitStrandsStack.RestApiUrl = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1/
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:622455551446:stack/CopilotKitStrandsStack/181cdbd0-d24b-11f0-a0ad-0ec93f2d35bd
✨ Total time: 3.9s
(中略)
出力RestApiUrlのURLを控えておきます。
実行してみる
現在ソースコードは、pnpm dev をするとWebアプリ(CopilotKit)のNext.js App Router の Route Handler(API Route)から http://localhost:8080/invocations を実行しています。これはLambdaを通さずに直接ローカルのStrandsのuvicornのサーバーを叩いています。このURLをAPIGatewayのものに置換し、応答が変えることを確認します。
$ export API_GATEWAY_ENDPOINT="https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com"
$ perl -pi -e "s|http://localhost:8080/invocations|${API_GATEWAY_ENDPOINT}/v1/api|" web/src/app/api/copilotkit/route.ts
# AWS資格情報取得(bedrockリクエストのため)
$ pnpm dev
動作することが確認できました。


AgentCoreのCloudWatchのログも確認できました
timestamp,message,logStreamName
1765026312512,WARNING: Invalid HTTP request received.,2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026312536,"INFO: 127.0.0.1:43550 - ""POST /invocations HTTP/1.1"" 200 OK",2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026314069,Tool #1: get_weather,2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026314069,"The weather inINFO: 127.0.0.1:43564 - ""GET /ping HTTP/1.1"" 200 OK",2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026316070," San Francisco is currently **70 degrees**. It's a pleasant, mild day! 🌤️INFO: 127.0.0.1:43564 - ""GET /ping HTTP/1.1"" 200 OK",2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026318071,"INFO: 127.0.0.1:43564 - ""GET /ping HTTP/1.1"" 200 OK",2025/12/06/[runtime-logs]2e89abcd-a991-487f-af11-9361d04c17f3
1765026319523,INFO: Started server process [1],2025/12/06/[runtime-logs]841a6e7f-ca7a-4e56-b356-d7248f6fc9e3
1765026319523,INFO: Waiting for application startup.,2025/12/06/[runtime-logs]841a6e7f-ca7a-4e56-b356-d7248f6fc9e3
1765026319523,INFO: Application startup complete.,2025/12/06/[runtime-logs]841a6e7f-ca7a-4e56-b356-d7248f6fc9e3
動作的に逐次で文章生成をしている感じがしなかったのでcurlでも叩いてみます。(ヘッダー省略)
$ curl 'http://localhost:3000/api/copilotkit' \
--data-raw $'{"operationName":"generateCopilotResponse","query":"mutation generateCopilotResponse($data: GenerateCopilotResponseInput\u0021, $properties: JSONObject) {\\n generateCopilotResponse(data: $data, properties: $properties) {\\n threadId\\n runId\\n extensions {\\n openaiAssistantAPI {\\n runId\\n threadId\\n __typename\\n }\\n __typename\\n }\\n ... on CopilotResponse @defer {\\n status {\\n ... on BaseResponseStatus {\\n code\\n __typename\\n }\\n ... on FailedResponseStatus {\\n reason\\n details\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n messages @stream {\\n __typename\\n ... on BaseMessageOutput {\\n id\\n createdAt\\n __typename\\n }\\n ... on BaseMessageOutput @defer {\\n status {\\n ... on SuccessMessageStatus {\\n code\\n __typename\\n }\\n ... on FailedMessageStatus {\\n code\\n reason\\n __typename\\n }\\n ... on PendingMessageStatus {\\n code\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n ... on TextMessageOutput {\\n content @stream\\n role\\n parentMessageId\\n __typename\\n }\\n ... on ImageMessageOutput {\\n format\\n bytes\\n role\\n parentMessageId\\n __typename\\n }\\n ... on ActionExecutionMessageOutput {\\n name\\n arguments @stream\\n parentMessageId\\n __typename\\n }\\n ... on ResultMessageOutput {\\n result\\n actionExecutionId\\n actionName\\n __typename\\n }\\n ... on AgentStateMessageOutput {\\n threadId\\n state\\n running\\n agentName\\n nodeName\\n runId\\n active\\n role\\n __typename\\n }\\n }\\n metaEvents @stream {\\n ... on LangGraphInterruptEvent {\\n type\\n name\\n value\\n __typename\\n }\\n ... on CopilotKitLangGraphInterruptEvent {\\n type\\n name\\n data {\\n messages {\\n __typename\\n ... on BaseMessageOutput {\\n id\\n createdAt\\n __typename\\n }\\n ... on BaseMessageOutput @defer {\\n status {\\n ... on SuccessMessageStatus {\\n code\\n __typename\\n }\\n ... on FailedMessageStatus {\\n code\\n reason\\n __typename\\n }\\n ... on PendingMessageStatus {\\n code\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n ... on TextMessageOutput {\\n content\\n role\\n parentMessageId\\n __typename\\n }\\n ... on ActionExecutionMessageOutput {\\n name\\n arguments\\n parentMessageId\\n __typename\\n }\\n ... on ResultMessageOutput {\\n result\\n actionExecutionId\\n actionName\\n __typename\\n }\\n }\\n value\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n}","variables":{"data":{"agentSession":{"agentName":"strands_agent"},"agentStates":[{"agentName":"strands_agent","config":"{}","state":"{\\"proverbs\\":[\\"CopilotKit may be new, but its the best thing since sliced bread.\\"]}"}],"context":[],"extensions":{},"forwardedParameters":{},"frontend":{"actions":[{"available":"enabled","description":"","jsonSchema":"{\\"type\\":\\"object\\",\\"properties\\":{\\"theme_color\\":{\\"type\\":\\"string\\",\\"description\\":\\"The theme color to set. Make sure to pick nice colors.\\"}},\\"required\\":[\\"theme_color\\"]}","name":"set_theme_color"}],"url":"http://localhost:3000/"},"messages":[{"createdAt":"2025-12-06T13:11:06.705Z","id":"ck-95e26812-4372-4511-8b94-d874ae3b8b96","textMessage":{"content":"\\nPlease act as an efficient, competent, conscientious, and industrious professional assistant.\\n\\nHelp the user achieve their goals, and you do so in a way that is as efficient as possible, without unnecessary fluff, but also without sacrificing professionalism.\\nAlways be polite and respectful, and prefer brevity over verbosity.\\n\\nThe user has provided you with the following context:\\n```\\n\\n\\n\\n```\\n\\nThey have also provided you with functions you can call to initiate actions on their behalf, or functions you can call to receive more information.\\n\\nPlease assist them as best you can.\\n\\nYou can ask them for clarifying questions if needed, but don\'t be annoying about it. If you can reasonably \'fill in the blanks\' yourself, do so.\\n\\nIf you would like to call a function, call it without saying anything else.\\nIn case of a function error:\\n- If this error stems from incorrect function parameters or syntax, you may retry with corrected arguments.\\n- If the error\'s source is unclear or seems unrelated to your input, do not attempt further retries.\\n","role":"system"}},{"createdAt":"2025-12-06T13:11:06.704Z","id":"ck-85b8308c-44e7-471c-a09a-03301b904288","textMessage":{"content":"Add a proverb about AI.","role":"user"}}],"metaEvents":[],"metadata":{"requestType":"Chat"},"runId":null,"threadId":"ff930531-f0cf-4423-bd51-f443904bc063"},"properties":{}}}'
---
Content-Type: application/json; charset=utf-8
Content-Length: 195
{"data":{"generateCopilotResponse":{"threadId":"ff930531-f0cf-4423-bd51-f443904bc063","runId":null,"extensions":null,"__typename":"CopilotResponse","messages":[],"metaEvents":[]}},"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 503
{"incremental":[{"items":[{"__typename":"AgentStateMessageOutput","id":"ck-1a4f8dd8-3341-4993-84d7-88476ec24120","createdAt":"2025-12-06T13:13:04.630Z","threadId":"ff930531-f0cf-4423-bd51-f443904bc063","state":"{\"proverbs\":[\"CopilotKit may be new, but its the best thing since sliced bread.\"]}","running":true,"agentName":"strands_agent","nodeName":"","runId":"db8440e0-b87c-4625-8614-4da3cd661e2e","active":true,"role":"assistant"}],"path":["generateCopilotResponse","messages",0]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 2305
{"incremental":[{"items":[{"__typename":"AgentStateMessageOutput","id":"ck-ed5b9c44-46c0-4fc4-b3d7-4d9205317250","createdAt":"2025-12-06T13:13:04.630Z","threadId":"ff930531-f0cf-4423-bd51-f443904bc063","state":"{\"proverbs\":[\"CopilotKit may be new, but its the best thing since sliced bread.\",\"Artificial intelligence is a tool; wisdom is knowing how to use it.\"]}","running":true,"agentName":"strands_agent","nodeName":"","runId":"db8440e0-b87c-4625-8614-4da3cd661e2e","active":true,"role":"assistant"}],"path":["generateCopilotResponse","messages",1]},{"data":{"status":{"code":"Success","__typename":"SuccessMessageStatus"},"__typename":"AgentStateMessageOutput"},"path":["generateCopilotResponse","messages",0]},{"items":[{"__typename":"ActionExecutionMessageOutput","id":"tooluse_pnrf2viIRqKFqlh2NzI1qA","createdAt":"2025-12-06T13:13:04.631Z","name":"update_proverbs","parentMessageId":"9a5a9c91-0c5d-4381-9ba8-e5ef972902a7","arguments":[]}],"path":["generateCopilotResponse","messages",2]},{"data":{"status":{"code":"Success","__typename":"SuccessMessageStatus"},"__typename":"AgentStateMessageOutput"},"path":["generateCopilotResponse","messages",1]},{"items":[{"__typename":"ResultMessageOutput","id":"result-tooluse_pnrf2viIRqKFqlh2NzI1qA","createdAt":"2025-12-06T13:13:04.631Z","result":"\"Proverbs updated successfully\"","actionExecutionId":"tooluse_pnrf2viIRqKFqlh2NzI1qA","actionName":"update_proverbs"}],"path":["generateCopilotResponse","messages",3]},{"items":["{\"proverbs_list\": {\"proverbs\": [\"CopilotKit may be new, but its the best thing since sliced bread.\", \"Artificial intelligence is a tool; wisdom is knowing how to use it.\"]}}"],"path":["generateCopilotResponse","messages",2,"arguments",0]},{"data":{"__typename":"ActionExecutionMessageOutput","status":{"code":"Success","__typename":"SuccessMessageStatus"}},"path":["generateCopilotResponse","messages",2]},{"items":[{"__typename":"TextMessageOutput","id":"9a5a9c91-0c5d-4381-9ba8-e5ef972902a7","createdAt":"2025-12-06T13:13:04.631Z","role":"assistant","parentMessageId":null,"content":[]}],"path":["generateCopilotResponse","messages",4]},{"data":{"status":{"code":"Success","__typename":"SuccessMessageStatus"},"__typename":"ResultMessageOutput"},"path":["generateCopilotResponse","messages",3]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 1721
{"incremental":[{"items":[{"__typename":"AgentStateMessageOutput","id":"ck-342c2e14-16b6-4785-9472-8ce467b690f5","createdAt":"2025-12-06T13:13:04.631Z","threadId":"ff930531-f0cf-4423-bd51-f443904bc063","state":"{\"proverbs\":[\"CopilotKit may be new, but its the best thing since sliced bread.\",\"Artificial intelligence is a tool; wisdom is knowing how to use it.\"]}","running":true,"agentName":"strands_agent","nodeName":"","runId":"db8440e0-b87c-4625-8614-4da3cd661e2e","active":false,"role":"assistant"}],"path":["generateCopilotResponse","messages",5]},{"items":["Perfect"],"path":["generateCopilotResponse","messages",4,"content",0]},{"data":{"__typename":"TextMessageOutput","status":{"code":"Success","__typename":"SuccessMessageStatus"}},"path":["generateCopilotResponse","messages",4]},{"items":["! I've added a new proverb about AI to your"],"path":["generateCopilotResponse","messages",4,"content",1]},{"data":{"status":{"code":"Success","__typename":"SuccessMessageStatus"},"__typename":"AgentStateMessageOutput"},"path":["generateCopilotResponse","messages",5]},{"items":[" collection:\n\n**\"Artificial intelligence is a tool;"],"path":["generateCopilotResponse","messages",4,"content",2]},{"items":[" wisdom is knowing how to use it.\"**\n\nYour"],"path":["generateCopilotResponse","messages",4,"content",3]},{"items":[" proverbs list now contains:\n1. \""],"path":["generateCopilotResponse","messages",4,"content",4]},{"items":["CopilotKit may be new, but its the best thing since sl"],"path":["generateCopilotResponse","messages",4,"content",5]},{"items":["iced bread.\"\n2. \"Artificial intelligence is a tool; wisdom is"],"path":["generateCopilotResponse","messages",4,"content",6]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 132
{"incremental":[{"items":[" knowing how to use it.\""],"path":["generateCopilotResponse","messages",4,"content",7]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 174
{"incremental":[{"data":{"__typename":"CopilotResponse","status":{"code":"Success","__typename":"SuccessResponseStatus"}},"path":["generateCopilotResponse"]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 17
{"hasNext":false}
-----
SSEと思っていましたが、リクエストのGraphQLクエリに@streamが付与されていることから、CopilotKitは、GraphQL Incremental Deliveryを使ったMultipart HTTP Responseっぽいです。逐次ストリーミング出来ないか別機会に調べてみようと思います。
さいごに
CopilotKitとStrandsの連携について書きました。初めてCopilotKiを触りましたが、AIエージェントの表現の幅を広げることができ、今までのWebアプリやLLMチャットアプリとは違う体験を提供出来る点非常に面白いと思いました。Strandsは最近TypeScript版もリリースされたので、同じようなAG-UIサポートが待ち遠しいですね!
まだまだCopilotKit自体の理解が浅いので、色々触って発信したいと思います🙇
余談ですが、CopilotKitのQuickStartのドキュメントにミスがあったため修正しました!
参考資料






