0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Bedrock × LangChainでChatGPT詩人を差し替えてみた【Claude 3 Haiku対応】

Last updated at Posted at 2025-05-23

はじめに

以前、LangChainとChatGPT APIを使って環境データから俳句を生成するアプリを作成した。今回はそのアーキテクチャを活かし、インターンで必要となったAmazon BedrockLangChainを使った詩人(LLM)へ差し替えてみたので、その手順と実装内容をまとめておく。

前回の記事

プロジェクトファイル

アプリケーション

きっかけ

インターンの要件でAmazon BedrockとLangChainの理解が必要になった。せっかくなら自分のアプリケーションに組み込んで、Claude 3の出力も試してみたいと思い、ChatGPTで実装していた部分をBedrock対応に差し替えることにした。

アーキテクチャ

このプロジェクトではクリーンアーキテクチャを採用しており、詩を生成する責務はPoetというインターフェースを通じて抽象化している。もともとはChatGptPoetというクラスでChatGPT APIを使っていたが、今回これをChatBedrockPoetに置き換えるだけで済む。

実装

ChatBedrockPoet.ts

// app/backend/infrastructure/poet/ChatBedrockPoet.ts

// LangChainでAmazon Bedrock(Claude 3)を使うためのモデル
import { ChatBedrock } from "@langchain/community/chat_models/bedrock";
// プロンプトテンプレートを作成するためのユーティリティ
import { PromptTemplate } from "@langchain/core/prompts";

// ドメイン層の詩人インターフェース
import { Poet } from "../../domain/poet";
// 生成された詩(俳句)を表すドメインオブジェクト
import { Poem } from "../../domain/poem";
// 環境データ(天気・気温など)を表すドメインオブジェクト
import { Environment } from "../../domain/environment";

// Claude 3を使って俳句を生成する詩人クラスの実装
export default class ChatBedrockPoet implements Poet {
  // 詩人の名前(UI表示などに利用可能)
  name = "Bedrock詩人(Claude 3対応)";

  // LangChainのChatBedrockモデルインスタンス(privateで内部利用)
  private llm: InstanceType<typeof ChatBedrock>;

  // コンストラクタでBedrockモデルの初期化
  constructor() {
    // 環境変数からAWS認証情報とリージョン・モデルIDを取得
    const accessKeyId = process.env.BEDROCK_AWS_ACCESS_KEY_ID;
    const secretAccessKey = process.env.BEDROCK_AWS_SECRET_ACCESS_KEY;
    const region = process.env.BEDROCK_AWS_REGION ?? "us-east-1";
    const model =
      process.env.BEDROCK_MODEL_ID ??
      "anthropic.claude-3-haiku-20240307-v1:0"; // Claude 3 Haiku モデルのデフォルト

    // 認証情報がなければエラー
    if (!accessKeyId || !secretAccessKey) {
      throw new Error("AWS credentials for Bedrock are not set in .env");
    }

    // LangChainのChatBedrockクライアントを初期化
    this.llm = new ChatBedrock({
      model,
      region,
      credentials: {
        accessKeyId,
        secretAccessKey,
      },
      temperature: 0.7, // 出力の多様性
      maxRetries: 2,    // API呼び出し失敗時の再試行回数
    });
  }

  // 環境情報から俳句を生成するメイン処理
  async composePoem(env: Environment): Promise<Poem> {
    // 俳句生成用のプロンプトテンプレートを定義
    const promptTemplate = PromptTemplate.fromTemplate(
      `以下の情報をもとに、とても可愛らしい俳句(17音以内、日本語)を1つ作ってください。\n\n場所: {location}\n気温: {temperature}℃\n湿度: {humidity}%\n天気: {weather}\n時間帯: {time}`
    );

    // 環境情報をテンプレートに埋め込んでプロンプトを生成
    const prompt = await promptTemplate.format({
      location: env.location,
      temperature: env.temperature.toString(),
      humidity: env.humidity.toString(),
      weather: env.weather,
      time: env.time.toLocaleTimeString("ja-JP"), // 日本語の時間表記
    });

    // デバッグ用にプロンプトを出力
    console.log("🔎 Claude 3 Prompt:\n", prompt);

    try {
      // Claude 3にプロンプトを送信し、応答を受け取る
      const response = await this.llm.invoke([
        { role: "user", content: prompt },
      ]);

      let resultText = "";

      // 応答が単一の文字列だった場合
      if (typeof response.content === "string") {
        resultText = response.content.trim();
      }
      // 応答が複数のテキストパーツだった場合(Claudeの形式に対応)
      else if (Array.isArray(response.content)) {
        const textParts = response.content
          .filter((part): part is { type: "text"; text: string } => part.type === "text")
          .map((part) => part.text)
          .join(" ");
        resultText = textParts.trim();
      }

      // 結果が空ならエラー
      if (!resultText) {
        throw new Error("Empty or invalid response from Claude 3 model");
      }

      // 生成されたテキストからPoemオブジェクトを生成して返す
      return new Poem(resultText);
    } catch (error) {
      // Claude 3呼び出しに失敗した場合はエラーをログ出力して再スロー
      console.error("❌ Claude 3 API Error:", error);
      throw new Error("Failed to generate poem via Claude 3 (Messages API)");
    }
  }
}

APIルートの変更

// app/api/GeneratePoem/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { GeneratePoemAction } from '@/app/backend/adapter/api/action/generate-poem';
import { NewGeneratePoemInteractor } from '@/app/backend/usecase/GeneratePoem';
import ChatBedrockPoet from '@/app/backend/infrastructure/poet/ChatBedrockPoet';
import { DefaultPoemPresenter } from '@/app/backend/adapter/presenter/GeneratePoemPresenter';
import { Environment } from '@/app/backend/domain/environment';

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();

    const environment: Environment = {
      location: body.location,
      temperature: parseFloat(body.temperature),
      humidity: parseFloat(body.humidity),
      weather: body.weather,
      time: new Date(body.time),
    };

    const usecase = NewGeneratePoemInteractor(
      new ChatBedrockPoet(),// (ここを差し替えるだけ!!!)
      new DefaultPoemPresenter()
    );

    const action = new GeneratePoemAction(usecase);
    const result = await action.execute({ environment });

    return NextResponse.json(result.data, { status: result.status });
  } catch (error) {
    return NextResponse.json(
      { error: 'Invalid input or server error' },
      { status: 500 }
    );
  }
}

試してみた結果

Claude 3 Haikuは名前の通り、日本語の俳句生成に非常に適していた。ChatGPTと比較してもより自然で情緒的な表現が多く、場合によってはこちらのほうが好まれる可能性があると感じた。

まとめ

クリーンアーキテクチャの利点を活かすことで、ChatGPTからAmazon Bedrock(Claude 3)への差し替えを最小限の変更で実現できた。LangChainがBedrock対応していたおかげで、インフラ層の実装もシンプルに保てた。

今後は用途に応じて、生成タスクごとにモデルを使い分けたり、複数詩人を切り替える機能も実装していきたい。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?