Amplify Gen2のAI Kitで本日時点で未対応のClaude 4を使う方法

Claude 4出ましたね。！なにか作りたくなりますよね！Amplify Gen2ですよね！

前にやったことがあるからすんなり行くと思ったら、、

**見事ハマりました**

Oh no...

備忘録でございます。

## ドキュメントどおりに最速で下準備

1. Viteでプロジェクトを作る
    
    ```shell
    npm create vite@latest
    ```
    ```
    │
    ◇  Project name:
    │  amplify-ai-kit-app
    │
    ◇  Select a framework:
    │  React
    │
    ◇  Select a variant:
    │  TypeScript + SWC
    │
    ◇  Scaffolding project in /workspaces/workspace/amplify-ai/amplify-ai-kit-app...
    │
    └  Done. Now run:
    
      cd amplify-ai-kit-app
      npm install
      npm run dev
    ```

1. ライブラリーインストール

    ```shell
    cd amplify-ai-kit-app
    npm i
    ```

1. Amplify Gen2追加

    ```shell
    npm create amplify@latest
    ```
    ```
    ✔ Where should we create your project? .
    ```

1. サンドボックス起動
    
    ```shell    
    npx ampx sandbox
    ```
    
1. バックエンドにAI機能追加

    :::note
    Amazon Nova Liteを使用します。
   :::

    ```diff_typescript:amplify/data/resource.ts
    ...
      const schema = a.schema({
        Todo: a
          .model({
            content: a.string(),
          })
          .authorization((allow) => [allow.guest()]),
      
    +   chat: a
    +     .conversation({
    +       aiModel: a.ai.model("Amazon Nova Lite"),
    +       systemPrompt: "You are a helpful assistant",
    +     })
    +     .authorization((allow) => allow.owner()),
    ```

1. クライアントライブラリーをインストール
    
    ```shell
    npm add aws-amplify @aws-amplify/ui-react @aws-amplify/ui-react-ai
    ```

1. `src/main.tsx`でクライアントライブラリー読み込み
    
    ```tsx:src/main.tsx
    import { Amplify } from 'aws-amplify';
    import '@aws-amplify/ui-react/styles.css';
    import outputs from '../amplify_outputs.json';
    
    Amplify.configure(outputs);
    ```

1. データクライアントを作成

    ```typescript:src/client.ts
    import { generateClient } from "aws-amplify/api";
    import { Schema } from "../amplify/data/resource";
    import { createAIHooks } from "@aws-amplify/ui-react-ai";
    
    export const client = generateClient<Schema>({ authMode: "userPool" });
    export const { useAIConversation, useAIGeneration } = createAIHooks(client);
    ```

1. `App.tsx`を修正（全置き換え）

    ```tsx:src/App.tsx
    import { Authenticator } from "@aws-amplify/ui-react";
    import { AIConversation } from '@aws-amplify/ui-react-ai';
    import { useAIConversation } from './client';
    
    export default function App() {
      const [
        {
          data: { messages },
          isLoading,
        },
        handleSendMessage,
      ] = useAIConversation('chat');
      // 'chat' is based on the key for the conversation route in your schema.
    
      return (
        <Authenticator>
          <AIConversation
            messages={messages}
            isLoading={isLoading}
            handleSendMessage={handleSendMessage}
          />
        </Authenticator>
      );
    }
    ```

1. レイアウトがずれるので`src/index.css`と`src/App.css`をファイルを残しつつソースを全削除

はいできた。

1. 起動する

```shell
npm run dev -- --host
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/8ec31957-8200-4b75-904b-09b72f974505.png)

ログインしたらチャットができました

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/61fc5f30-496a-4154-b0ce-f26643df3993.png)


## 未対応モデルを使用する

さて、ここからが本題です。

おさらいですが、使用するAIモデルは`amplify/data/resource.ts`で指定する仕組みです。

```typescript:amplify/data/resource.ts
const schema = a.schema({
  chat: a
    .conversation({
      aiModel: a.ai.model("Amazon Nova Lite"),
      systemPrompt: "You are a helpful assistant",
    })
    .authorization((allow) => allow.owner()),
```

`a.ai.model`の引数ですが、任意の文字列が指定できるわけではなく`supportedModelsLookup`として定義されているものだけが選択できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/6845e14d-fea7-445c-a325-d9884d393f81.png)

ここにないモデル（例えばJamba 1.5 Largeなど）を使用したい場合は以下のように指定します。

```diff_typescript
  chat: a
    .conversation({
+      aiModel: { resourcePath: "ai21.jamba-1-5-large-v1:0" },
-      aiModel: a.ai.model("Amazon Nova Lite"),
      systemPrompt: "You are a helpful assistant",
    })
    .authorization((allow) => allow.owner()),
```

この指定方法で動作しました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/396cd727-64e0-4b32-8a7f-1c0cdb27a8a5.png)

## クロスリージョン推論する

なるほど、と。全部理解した。

クロスリージョン推論はどうするのって？そんなときはこうです。

```
aiModel: { resourcePath: "us.amazon.nova-premier-v1:0" }
```

と思ったあなた。これでは**残念ながら駄目です**🥲

裏側でよしなに作られるLambdaに付与されるIAMロールが変な感じになります。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9596d3a7-9951-4ba8-9d19-7f164e2878a2.png)

正しくは、foundation-modelは`us`がつかないamazon.nova-premier-v1:0でかつ、推論プロファイルへのアクセス権限も必要です。

この部分がよしなにはやってくれないので、自前でやります。

1. 追加ライブラリーをインストール

    ```shell
    npm install @aws-amplify/backend-ai
    ```

1. `amplify/data/resource.ts`を修正

    handlerを追加で指定します。
    
    ```diff_typescript:amplify/data/resource.ts
      import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
    + import { defineConversationHandlerFunction } from "@aws-amplify/backend-ai/conversation";
      
    + export const model = "anthropic.claude-sonnet-4-20250514-v1:0";
    + export const crossRegionModel = `us.${model}`;
      
    + export const conversationHandler = defineConversationHandlerFunction({
    +   entry: "./conversationHandler.ts",
    +   name: "conversationHandler",
    +   models: [{ modelId: crossRegionModel }],
    + });
      
      const schema = a.schema({
        chat: a
          .conversation({
    +       aiModel: { resourcePath: crossRegionModel },
            systemPrompt: "You are a helpful assistant",
    +       handler: conversationHandler,
          })
          .authorization((allow) => allow.owner()),
      });
      
      export type Schema = ClientSchema<typeof schema>;
      
      export const data = defineData({
        schema,
        authorizationModes: {
          defaultAuthorizationMode: "identityPool",
        },
      });
    
    ```

1. `amplify/data/conversationHandler.ts`を作成

    ハンドラーを作ります。何をしてるのかはよくわかりません。

    ```typescript:amplify/data/conversationHandler.ts
    import { handleConversationTurnEvent } from "@aws-amplify/backend-ai/conversation/runtime";
    
    export const handler = handleConversationTurnEvent;
    ```

1. `amplify/backend.ts`を修正

    IAMロールを愚直に指定します。（リージョンが変わると変わると思うので注意）

    ```diff_typescript:amplify/backend.ts
      import { defineBackend } from "@aws-amplify/backend";
      import { PolicyStatement } from "aws-cdk-lib/aws-iam";
      import { auth } from "./auth/resource";
      import {
    +   conversationHandler,
    +   crossRegionModel,
        data,
    +   model,
      } from "./data/resource";
      
      /**
       * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
       */
      const backend = defineBackend({
        auth,
        data,
    +   conversationHandler,
      });
      
    +  backend.conversationHandler.resources.lambda.addToRolePolicy(
    +    new PolicyStatement({
    +      resources: [
    +        `arn:aws:bedrock:us-east-1:${backend.stack.account}:inference-profile/${crossRegionModel}`,
    +        `arn:aws:bedrock:us-east-1::foundation-model/${model}`,
    +        `arn:aws:bedrock:us-east-2::foundation-model/${model}`,
    +        `arn:aws:bedrock:us-west-2::foundation-model/${model}`,
    +      ],
    +      actions: ["bedrock:InvokeModelWithResponseStream"],
    +    })
    +  );
    
    ```

これでClaude 4が使えましたよ！

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/7cf63949-3f34-4bc6-8bca-60c3cbb44852.png)


## 参考

https://github.com/aws-amplify/docs/issues/8121