8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Strands Agents TypeScript版の使い所とは?CDKデプロイとAPI Gatewayストリーミング応答の実践ガイド

Last updated at Posted at 2025-12-06

本記事は2025年12月6日時点の情報をもとに作成しています。

前書き

BB677115-24A8-4DD9-8B3D-8D7C72B1D1C9_4_5005_c.jpeg

12月3日のre:Inventイベントで、Strands AgentsのTypeScript版が公開されました。

興味があったので、早速試してみて、中身も確認しました。現時点での知見を共有したいと思います。
6E93BF9E-0967-4621-9D99-CD3DE1EAF0D8.jpeg

AgentCoreランタイムにデプロイ

まずは一通りサーバーを立てて、AgentCoreランタイムにデプロイしてみます。

ちょうど先日BunがAnthropic社に買収されたこともあり、今回はBunを使ってStrandsプロジェクトを初期化します。

Strandsのtypescript版には専用の初期化CLIコマンドが用意されていないため、npmを使う場合は少し手間がかかります。一方、BunならTypeScriptをそのまま実行できるため、開発体験が格段に向上します。

mkdir strands-bun-cdk && cd strands-bun-cdk
cdk init --language typescript
bun init agent
cd agent && bun add @strands-agents/sdk

Bunでプロジェクトを初期化すると、CLAUDE.mdも自動で生成されて感動しました。
プロジェクトの詳細は省略しますが、リポジトリを公開しておきますので、ぜひご参考ください。

下記のagent.tsファイルでAIエージェントと使用するツールを初期化します。
コードは、Strands Agents TypeScript版のリポジトリにあるサンプルコードを修正したものです。

agent.ts
import { Agent, BedrockModel, tool } from "@strands-agents/sdk";
import { z } from "zod";

const weatherTool = tool({
  name: "get_weather",
  description: "Get the current weather for a specific location.",
  inputSchema: z.object({
    location: z
      .string()
      .describe("The city and state, e.g., San Francisco, CA"),
  }),
  callback: (input) => {
    const fakeWeatherData = {
      temperature: "72°F",
      conditions: "sunny",
    };

    return `The weather in ${input.location} is ${fakeWeatherData.temperature} and ${fakeWeatherData.conditions}.`;
  },
});

const model = new BedrockModel({
  region: "ap-northeast-1",
  modelId: "jp.anthropic.claude-sonnet-4-5-20250929-v1:0",
});

export const agent = new Agent({
  systemPrompt:
    "You are a helpful assistant that provides weather information using the get_weather tool.",
  model,
  tools: [weatherTool],
  printer: false,
});

次はサーバー側を作ります。今回はHonoを使います。

bun add hono

構成としては、Honoサーバーでリクエストを受け取り、パラメータを先ほど作ったagent.tsに渡します。

index.ts
import { agent } from "./agent";
import { Hono } from "hono";

const PORT = process.env.PORT || 8080;

const app = new Hono();

app.get("/ping", (c) =>
  c.json({
    status: "Healthy",
    time_of_last_update: Math.floor(Date.now() / 1000),
  })
);

app.post("/invocations", async (c) => {
  try {
    const prompt = await c.req.text();
    console.log(`Received prompt: ${prompt}`);

    const result = await agent.invoke(prompt);
    console.log(`Response stopReason: ${result.stopReason}`);

    return c.json({ response: result });
  } catch (err) {
    console.error("Error processing request:", err);
    return c.json({ error: "Internal server error" }, 500);
  }
});

export default {
  fetch: app.fetch,
  port: Number(PORT),
};

console.log(`AgentCore Runtime server listening on port ${PORT}`);

同じディレクト内に、Dockerfileも作ります。

Dockerfile
FROM --platform=linux/arm64 oven/bun:latest

WORKDIR /app

# Copy source code
COPY . ./

# Install dependencies
RUN bun install

# Build TypeScript
RUN bun run build

# Expose port
EXPOSE 8080

# Start the application
CMD ["bun", "run", "start"]

package.jsonにbuildstartのスクリプトを追加します。

package.json
  "scripts": {
+    "build": "bun build ./index.ts --outdir ./dist --target bun",
+    "start": "bun run dist/index.js",
    "dev": "bun run index.ts"
  }

次はCDK側の構成を修正します。cd ..でディレクトリ直下に戻り、agentcore用のライブラリー入れます。

bun add @aws-cdk/aws-bedrock-agentcore-alpha

strands-bun-cdk-stack.tsを下記のように修正します。

lib/strands-bun-cdk-stack.ts
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import * as iam from "aws-cdk-lib/aws-iam";
import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha";
import { Construct } from "constructs";

export class StrandsBunCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromAsset(
      path.join(__dirname, "../agent")
    );

    const runtime = new agentcore.Runtime(this, "StrandsAgentsRuntime", {
      runtimeName: "StrandsAgentsTS",
      agentRuntimeArtifact: agentRuntimeArtifact,
      description: "Strands Agents for TypeScript",
    });

    runtime.addToRolePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          "bedrock:InvokeModel",
          "bedrock:InvokeModelWithResponseStream",
        ],
        resources: [
          "arn:aws:bedrock:*::foundation-model/*",
          `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/*`,
        ],
      })
    );

    new cdk.CfnOutput(this, "RuntimeName", {
      value: "StrandsAgentsTS",
      description: "Name of the AgentCore Runtime",
    });

    new cdk.CfnOutput(this, "RuntimeArn", {
      value: runtime.agentRuntimeArn,
      description: "ARN of the AgentCore Runtime",
      exportName: "AgentRuntimeArn",
    });

    new cdk.CfnOutput(this, "RuntimeId", {
      value: runtime.agentRuntimeId,
      description: "ID of the AgentCore Runtime",
      exportName: "AgentRuntimeId",
    });
  }
}

デプロイを実行します。

bunx cdk deploy

デプロイが完了したら、AgentCoreランタイムから確認できます。

A517722F-1D4A-4859-811E-32A2F0AD9E32_4_5005_c.jpeg

エージェントサンドボックスからテストしてみると、正常に動作していることが確認できます。

B19B20BE-C92D-4021-9D9C-EE15E61DAD0D.jpeg

ストリーミングの有効化

ストリーミングからテキストを取り出す方法については公式ドキュメントに記載がありませんが、試してみたところ、下記の方法で実装できることが分かりました。

async function runStreaming(title: string, agent: Agent, prompt: string) {
    for await (const event of agent.stream(prompt)) {
        if (event.type === "modelContentBlockDeltaEvent" && event.delta.type === "textDelta") {
            console.log(event.delta.text)
        }
    }
}
 bun run index.ts

天
気情報をお調
べい
た
します。ど
ち
らの地
域の天気を
お知り
になりたいですか?

現時点では、公式ドキュメントが不親切というよりも、まだどのようにストリーミングを返すのが親切なのか検討中といった印象を受けます。

例えば、Mastraの場合、ストリーミングのレスポンスが必要な際には、textStreamまたはobjectStreamが使えるようになっており、型の変換もフレームワーク側で行ってくれます。

MastraのAIエージェントstream
const testAgent = mastra.getAgent("testAgent");

const stream = await testAgent.stream([
  { role: "user", content: "Help me organize my day" },
]);

for await (const chunk of stream.textStream) {
  process.stdout.write(chunk);
}

まだプレビュー版ということもあり、現時点ではそういった便利なメソッドが用意されていません。
このままシンプルな実装でいくのか、それともMastraのようにstream.textStreamstream.objectStreamのようなメソッドを提供した方がいいのか
——これはv1.0リリースで予定されている構造化出力にも関わる問題なので、ぜひ利用者の意見も聞きたいところです。:wink:

index.tsのストリーミング対応を行います。

index.ts
app.post("/invocations", async (c) => {
  try {
    const prompt = await c.req.text();
    console.log(`Received prompt: ${prompt}`)

+    const stream = new ReadableStream({
+      async start(controller) {
+        for await (const event of agent.stream(prompt)) {
+          if (
+            event.type === "modelContentBlockDeltaEvent" &&
+            event.delta.type === "textDelta"
+          ) {
+            const data = `data: ${JSON.stringify({
+              text: event.delta.text,
+            })}\n\n`;
+            controller.enqueue(new TextEncoder().encode(data));
+          }
+        }
+        controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
+        controller.close();
      },
    });

    return new Response(stream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        Connection: "keep-alive",
      },
    });
  } catch (err) {
    console.error("Error processing request:", err);
    return c.json({ error: "Internal server error" }, 500);
  }
});

これでbunx cdk deployすれば動作しますが、AgentCoreのエージェントサンドボックスはストリーミング時に何も表示されないため😞、API Gatewayのストリーミングを使います。

リソースの構築は酒井貴央さんの記事を参考にさせていただいています。

agentフォルダと同じ階層にlambdaフォルダを作成し、その配下にagentcore-proxyフォルダを作ります。

agentcore-proxyフォルダ配下の構造は、下記のリンクから確認できます。

strands-bun-cdk-stack.tsにLambda用のリソースを追加します。

lib/strands-bun-cdk-stack.ts
    const lambdaLogGroup = new logs.LogGroup(this, "LambdaLogGroup", {
      logGroupName: "/aws/lambda/agentcore-proxy",
      retention: logs.RetentionDays.ONE_WEEK,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const proxyFunction = new nodejs.NodejsFunction(
      this,
      "AgentCoreProxyFunction",
      {
        functionName: "agentcore-proxy",
        entry: path.join(__dirname, "../lambda/agentcore-proxy/index.ts"),
        handler: "handler",
        runtime: lambda.Runtime.NODEJS_24_X,
        timeout: cdk.Duration.minutes(15),
        memorySize: 512,
        environment: {
          AGENT_ARN: runtime.agentRuntimeArn,
        },
        logGroup: lambdaLogGroup,
      }
    );

    proxyFunction.addToRolePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ["bedrock-agentcore:InvokeAgentRuntime"],
        resources: [
          runtime.agentRuntimeArn,
          `${runtime.agentRuntimeArn}/runtime-endpoint/*`,
        ],
      })
    );

変更完了後、再度bunx cdk deployを実行します。

デプロイ完了後、手動でAPI Gatewayを作成します。

FEE67474-D88E-42B0-BE15-1E19C0AA7252.jpeg

APIを作成選んでから、REST APIを構築します。

23B3BB62-8AD7-4913-9EE5-5F8B5118064D_4_5005_c.jpeg

APIの詳細から新しいAPIを選び、API名は好きな文字を入れて、Security policyはSecurityPolicy_TLS13_1_2_2021_06を選んで、API作成します。

B60D06AE-A906-4803-8952-EF74C7EED402.jpeg

次はリソースを作成します。

4C53496D-5E68-44AC-9559-F9B2D6A7ED31_4_5005_c.jpeg

リソース名を好きな文字を入れて、リソースを作成します。

E0DCB224-C373-4708-8571-C897359B233F.jpeg

できたリソースを選んで、メソッドを作成します

F1BE82D7-74F7-4676-B068-7B4C60D3AE0D_4_5005_c.jpeg

メソッドタイプはPOST、統合タイプはLambda 関数Lambdaプロキシ統合を有効化にして、レスポンス転送モードをストリームにする、Lambda関数は先デプロイしたlambdaを選びます、統合タイムアウトは900000にします。

CA222664-FC59-4C44-851F-A847680A94EE.jpeg

最後はAPIをデプロイをします、新しいステージを選び、好きなステージ名を入力してデプロイします

56279992-79FE-48B2-8F96-36C5F77DBC91.jpeg

デプロイが完了すると、APIを呼び出すための下記のような形式のURLが表示されます。

url/ステージ名/リソース名

このURLを使って、AgentCoreランタイムにデプロイしたStrands Agentsを呼び出します。

curl --no-buffer -X POST https://xxx123xxx.xxxxxxx-api.ap-northeast-1.amazonaws.com/test/invoke \
  -H "Content-Type: application/json" \
  -d '{"prompt":"こんにちは、東京の天気わかりますか?"}'

A8372067-2A98-4B10-9570-D5535923D755.jpeg

ストリーミングでも無事に動作しましたね。

Strands Agents TypeScript版使うべきか?

現在(2025年12月06日)、プレビュー版の本番利用はおすすめではありません。

Strands Agents TypeScript の 1.0 正式版リリースの要件を見ると

  • ガードレールのサポート
  • トレース取得の利便性向上
  • HITL
  • 構造化出力
  • メモリの統合
  • Anthropic Model Provider の追加

などが含まれています。

もし正式版の進捗が Python 版と同じペースであれば、3ヶ月後にはそれらの機能が実現できるでしょう。
つまり、3ヶ月後には現在の Python 版 Strands Agents とほぼ同じ機能が揃うことになります。

ただ、issueの中には Python 版に合わせるという文言が多く存在しており、また AWS 社内では Python エンジニアの数のほうが断然多いことから、TypeScript ファーストのフレームワークになることはないでしょう。

また、Strands Agents だけでなく、Python 版の使いやすさは AgentCore の Python 版 SDK があってこその部分もあるため、この間に TypeScript 版の AgentCore SDK がどの程度便利になるのかはまだ不明です。

そのため、ドキュメント側もまだ整った形になっていない現時点では、Strands Agents TypeScript 版をプロダクションで利用することはあまりお勧めしません。

E55E4B59-AD91-4E49-8A1A-ED044E924036.jpeg

Strands Agentsの良さは、AgentCoreとBedrockの機能を簡単に利用できる点ですが、Strands Agents TypeScript 版のエコシステムは現時点では整っていません。

したがって、正式版になってから、かつ AgentCore の機能をフル活用する前提であれば、ようやく選択肢の上位に上がるでしょう。

現時点では厳しい評価ですが、私も自分なりにできることをしていく予定です。

参考資料

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?