新しく公開されたAnthropic Quickstartsがサンプルの域を超えている

AnthropicのAlexさんのポストを発見！
新しくクイックスタートをGitHubで公開したよとのことです。

https://x.com/alexalbert__/status/1831022348784542090

GitHubのリポジトリはこちらです。

https://github.com/anthropics/anthropic-quickstarts/

早速試してみました。

## 含まれるもの

2024/9/4時点では、「Customer Support Agent」プロジェクトが一つだけ用意されています。

:::note
ナンバリングされているので今後も増えていくようです
:::

「Customer Support Agent」は、カスタマーサポートを想定したチャットアプリです。

まず、見た目がいい！


![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d6c5d477-c07e-a0ca-05e7-ee71a7680e52.png)


READMEからの和訳ですが、主な特徴は以下のとおりです

* Anthropic の Claude モデルを使用した AI 搭載チャット
* コンテキスト ナレッジ検索のための Amazong Bedrock 統合
* リアルタイム思考とデバッグ情報の表示
* ナレッジ ベース ソースの視覚化
* ユーザーの気分検出と適切なエージェント リダイレクト
* shadcn/ui コンポーネントを使用した高度にカスタマイズ可能な UI

これをベースにカスタマイズして、独自のチャットUIも作れそうです。


## やってみた。

最短ルートの場合は、以下の手順です。

* Gitクローン
* `.env.local`を作成
* `npm install`
* `npm run dev`

超簡単！

.env.localの中身は、Anthropic APIのキーと、AWSの認証情報のみです。

```text
ANTHROPIC_API_KEY=your_anthropic_api_key
BAWS_ACCESS_KEY_ID=your_aws_access_key
BAWS_SECRET_ACCESS_KEY=your_aws_secret_key
```

せっかくなので（？）、すべてBedrockだけで動作するようにし、更に認証情報は`aws configure`で指定したものを使用するようにカスタマイズしてみます。

### Bedrockだけを使うように手直し

Anthropic APIの呼び出しは、Anthropic TypeScript API Library([@anthropic-ai/sdk](https://www.npmjs.com/package/@ai-sdk/anthropic))を使用しています。これをBedrock版のAnthropic Bedrock TypeScript API Library([@anthropic-ai/bedrock-sdk](https://www.npmjs.com/package/@anthropic-ai/bedrock-sdk))に変更します。

* 使用ライブラリーの変更

    ```diff_json:package.json
    ...
      },
      "dependencies": {
        "@ai-sdk/anthropic": "^0.0.34",
    -   "@anthropic-ai/sdk": "^0.27.1",
    +   "@anthropic-ai/bedrock-sdk": "^0.10.2",
        "@aws-sdk/client-bedrock-agent-runtime": "^3.621.0",
    ...
    ```

* 呼び出し部分の変更
    Anthropic TypeScript API Libraryを使用しているのは一箇所だけでした。
    
    ```diff_typescript:route.ts
      import Anthropic from "@anthropic-ai/sdk";
    + import AnthropicBedrock from '@anthropic-ai/bedrock-sdk';
      import { z } from "zod";
      import { retrieveContext, RAGSource } from "@/app/lib/utils";
      import crypto from "crypto";
      import customerSupportCategories from "@/app/lib/customer_support_categories.json";
      
    - const anthropic = new Anthropic({
    -   apiKey: process.env.ANTHROPIC_API_KEY,
    - });
    + const anthropic = new AnthropicBedrock();
    ```

* モデル名を変更
    
    Anthropic APIとBedrockではモデル名が異なるので、モデル名をBedrockのものに変更します。
    
    ```diff_typescript:ChatArea.tsx
    ...
      function ChatArea() {
        const [messages, setMessages] = useState<Message[]>([]);
        const [input, setInput] = useState("");
        const [isLoading, setIsLoading] = useState(false);
        const [showHeader, setShowHeader] = useState(false);
    -   const [selectedModel, setSelectedModel] = useState("claude-3-haiku-20240307");
    +   const [selectedModel, setSelectedModel] = useState("us.anthropic.claude-3-haiku-20240307-v1:0");
        const [showAvatar, setShowAvatar] = useState(false);
      
        const messagesEndRef = useRef<HTMLDivElement>(null);
        const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState(
          "your-knowledge-base-id",
        );
      
        const knowledgeBases: KnowledgeBase[] = [
          { id: "your-knowledge-base-id", name: "Your KB Name" },
          // Add more knowledge bases as needed
        ];
      
        const models: Model[] = [
    -     { id: "claude-3-haiku-20240307", name: "Claude 3 Haiku" },
    -     { id: "claude-3-5-sonnet-20240620", name: "Claude 3.5 Sonnet" },
    +     { id: "us.anthropic.claude-3-haiku-20240307-v1:0", name: "Claude 3 Haiku" },
    +     { id: "us.anthropic.claude-3-5-sonnet-20240620-v1:0", name: "Claude 3.5 Sonnet" },
        ];
    ...
    ```

    :::note
    出たばかりのクロスリージョン推論を指定しました。
    https://aws.amazon.com/jp/about-aws/whats-new/2024/08/amazon-bedrock-cross-region-inference/
  :::
    
    :::note
    Knowledge bases for Amazon Bedrockの設定も行ってください。
    
    ```typescript
      const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState(
        "your-knowledge-base-id",
      );
    
      const knowledgeBases: KnowledgeBase[] = [
        { id: "your-knowledge-base-id", name: "Your KB Name" },
        // Add more knowledge bases as needed
      ];
    ```
    
  :::

* Knowledge bases for Amazon Bedrock呼び出し部分の修正

    アクセスキーとシークレットキーを明示的に指定している部分を削除します。
    
    ```diff_typescript:utils.ts
      const bedrockClient = new BedrockAgentRuntimeClient({
        region: "us-east-1", // Make sure this matches your Bedrock region
    -   credentials: {
    -     accessKeyId: process.env.BAWS_ACCESS_KEY_ID!,
    -     secretAccessKey: process.env.BAWS_SECRET_ACCESS_KEY!,
    -   },
      });
    ```

これで出来上がりです。

あとは、`npm install`と`npm run dev`をすると起動します。

## 使ってみた

Knowledge bases for Amazon Bedrockに、Bedrockのユーザーガイド（英語）を登録して使ってみました。

`http://localhost:3000`にアクセスします。
矢印のところで使用するモデルとナレッジベースを選択します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/c65968e7-25c9-afdd-50fe-b70bcc7114f0.png)

とりあえず聞いてみます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/e731c392-016d-6ee8-9965-c6c93a040f9a.png)

英語で返ってきたので、日本語で再回答してもらいました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/ad305bca-f7dc-310d-001e-86da0cb1a4f1.png)

日本語で返ってきました。チャットのやり取りも考慮されて動作しています。

左側は「AIがどう考えたか」が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f86af83f-bdb3-f4a2-a37e-6cb11e91c998.png)

右側には参照したナレッジの情報が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9615ec3d-4646-d9bf-5d5e-29592d5322f4.png)

右側のナレッジに関する情報は、Knowledge bases for Amazon BedrockのAPIのレスポンスを使用していると思われます。
左側の「AIがどう考えたか」については、生成AIが回答を生成する際に、同時に生成させるシステムプロンプトが使用されていました。

## システムプロンプトはどんな感じ？

システムプロンプトは「route.ts」にありました。

:::note
コメントで「あなたのユースケースに合わせて修正してね」的なことも書かれています。
:::

```typescript
  // Prepare categories context for the system prompt
  const USE_CATEGORIES = true;
  const categoryListString = customerSupportCategories.categories
    .map((c) => c.id)
    .join(", ");

  const categoriesContext = USE_CATEGORIES
    ? `
    To help with our internal classification of inquries, we would like you to categorize inquiries in addition to answering the. We have provided you with ${customerSupportCategories.categories.length} customer support categories.
    Check if your response fits into any category and include the category IDs in your "matched_categories" array.
    The available categories are: ${categoryListString}
    If multiple categories match, include multiple category IDs. If no categories match, return an empty array.
  `
    : "";

  // Change the system prompt company for your use case
  const systemPrompt = `You are acting as an Anthropic customer support assistant chatbot inside a chat window on a website. You are chatting with a human user who is asking for help about Anthropic's products and services. When responding to the user, aim to provide concise and helpful responses while maintaining a polite and professional tone.

  To help you answer the user's question, we have retrieved the following information for you. It may or may not be relevant (we are using a RAG pipeline to retrieve this information):
  ${isRagWorking ? `${retrievedContext}` : "No information found for this query."}

  Please provide responses that only use the information you have been given. If no information is available or if the information is not relevant for answering the question, you can redirect the user to a human agent for further assistance.

  ${categoriesContext}

  If the question is unrelated to Anthropic's products and services, you should redirect the user to a human agent.

  You are the first point of contact for the user and should try to resolve their issue or provide relevant information. If you are unable to help the user or if the user explicitly asks to talk to a human, you can redirect them to a human agent for further assistance.
  
  To display your responses correctly, you must format your entire response as a valid JSON object with the following structure:
  {
      "thinking": "Brief explanation of your reasoning for how you should address the user's query",
      "response": "Your concise response to the user",
      "user_mood": "positive|neutral|negative|curious|frustrated|confused",
      "suggested_questions": ["Question 1?", "Question 2?", "Question 3?"],
      "debug": {
        "context_used": true|false
      },
      ${USE_CATEGORIES ? '"matched_categories": ["category_id1", "category_id2"],' : ""}
      "redirect_to_agent": {
        "should_redirect": boolean,
        "reason": "Reason for redirection (optional, include only if should_redirect is true)"
      }
    }

  Here are a few examples of how your response should look like:

  Example of a response without redirection to a human agent:
  {
    "thinking": "Providing relevant information from the knowledge base",
    "response": "Here's the information you requested...",
    "user_mood": "curious",
    "suggested_questions": ["How do I update my account?", "What are the payment options?"],
    "debug": {
      "context_used": true
    },
    "matched_categories": ["account_management", "billing"],
    "redirect_to_agent": {
      "should_redirect": false
    }
  }

  Example of a response with redirection to a human agent:
  {
    "thinking": "User request requires human intervention",
    "response": "I understand this is a complex issue. Let me connect you with a human agent who can assist you better.",
    "user_mood": "frustrated",
    "suggested_questions": [],
    "debug": {
      "context_used": false
    },
    "matched_categories": ["technical_support"],
    "redirect_to_agent": {
      "should_redirect": true,
      "reason": "Complex technical issue requiring human expertise"
    }
  }
  `
```

以下の形式のJSONで返却するように例示されていることがわかります。

```json
{
    "thinking": "Providing relevant information from the knowledge base",
    "response": "Here's the information you requested...",
    "user_mood": "curious",
    "suggested_questions": ["How do I update my account?", "What are the payment options?"],
    "debug": {
      "context_used": true
    },
    "matched_categories": ["account_management", "billing"],
    "redirect_to_agent": {
      "should_redirect": false
    }
}
```

:::note
Tool useではなく、JSONで返却させるようです。
:::


---

すごい