SemanticKernel経由でAWS Bedrockを利用してみる。

## はじめに

Semantic Kernelを利用すると、C#やJava,Pythonから様々なAIモデルを共通的な方法で利用できるようになります。Web上のサンプルはAzureOpenAIを利用したものが多いですが、AWSのBedrockに含まれるAIモデルを利用することもできます（ただしまだプレビューです）。

https://devblogs.microsoft.com/semantic-kernel/introducing-aws-bedrock-with-semantic-kernel/

今回はC#からSemantic Kernelを通じてBedrockのclaude-3-5-sonnet-20240620を利用する方法を見ていきます。

## Bedrockのモデルを利用可能にする

まずはAWS側でAIモデルをリクエストして利用可能な状態にする必要があります。
Bedrockのメニューから、モデルアクセスを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/b032dc12-1e88-4e1b-a34a-c23a5824b728.png)

利用したいモデルを選択してリクエストすると、数分で利用可能になります。
（下記の図ではすでにリクエストが許可された状態になっています。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/7daf8474-a1d4-4a87-8da4-3ccfee38c3c3.png)

## モデルIDを確認する

Badrockで利用するモデルの名前はモデルカタログで確認できます。
例えば、`Claude 3.5 Sonnet`を利用する場合は `anthropic.claude-3-5-sonnet-20240620-v1:0` になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/2ee27403-f6b3-46a8-8b66-376c1038dbf1.png)

また、一部の[クロスリージョン推論](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/inference-profiles-support.html
)で動作するモデルは、呼び出すリージョンによってモデルのIDが異なるので注意が必要です。例えば東京リージョン（ap-northeast-1）で`Claude 3.5 Sonnet v2`を利用したい場合は、`apac.anthropic.claude-3-5-sonnet-20241022-v2:0`を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/472e4111-efe2-410d-9f36-0ccb8da9f94c.png)

ただし、現時点（Semantic Kernel 1.40.1-alpha）では[クロスリージョン推論に対応してい](https://github.com/microsoft/semantic-kernel/issues/10738)ないため、クロスリージョン推論が必要なモデルを通常のモデルIDで呼び出すと次のエラーが表示され、

> Error: Amazon.BedrockRuntime.Model.ValidationException: Invocation of model ID anthropic.claude-3-5-sonnet-20241022-v2:0 with on-demand throughput isn’t supported. Retry your request with the ID or ARN of an inference profile that contains this model.

クロスリージョンのモデルID（Interface Profile ID）を指定すると次のエラーメッセージが表示されます。

> Error: Microsoft.SemanticKernel.KernelException: An error occurred while initializing the BedrockChatCompletionService: Unsupported model provider: apac


## プロジェクトを作成してライブラリを参照する

コンソールプロジェクトを作成して、必要なライブラリをNuGetで参照します。

```
dotnet new console
dotnet add package Microsoft.SemanticKernel --version 1.40.1
dotnet add package Microsoft.SemanticKernel.Connectors.Amazon --version 1.40.1-alpha
```

Bedrock用のライブラリは現時点ではアルファ版のみがリリースされているため、Visual Studioから参照する場合はプレリリースを含めるにチェックを入れる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/579a4567-bdf3-4862-a3ed-601099691794.png)

## Kernelを作る

SemanticKernelを作っていきます。

```Program.cs
using Amazon;
using Amazon.BedrockRuntime;
using Amazon.Runtime.CredentialManagement;
using Microsoft.SemanticKernel;

// Anthropic claude 3.5 Sonnet
var modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0";

var builder = Kernel.CreateBuilder();
// 現時点ではSemanticKernelのBeadrock実装は実験的な実装であるため、SKEXP0070警告を抑制する。
#pragma warning disable SKEXP0070
builder.AddBedrockChatCompletionService(modelId);
#pragma warning restore SKEXP0070
var kernel = builder.Build();
```

AWSプロファイルを直接指定する場合はこんな感じにします。

```Program.cs
var chain = new CredentialProfileStoreChain();
chain.TryGetAWSCredentials("myProfile", out var credentials);
var bedrockRuntime = new AmazonBedrockRuntimeClient(credentials, RegionEndpoint.APNortheast1);

var builder = Kernel.CreateBuilder();
#pragma warning disable SKEXP0070
builder.AddBedrockChatCompletionService(modelId, bedrockRuntime);
#pragma warning restore SKEXP0070
var kernel = builder.Build();
```

もしくは、AddDefaultAWSOptionsを使ってappsettings.jsonなどから設定情報を読み込んでもよいです。

```Program.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

var builder = Kernel.CreateBuilder();
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true)
    .Build();
builder.Services.AddDefaultAWSOptions(configuration.GetAWSOptions());
#pragma warning disable SKEXP0070
builder.AddBedrockChatCompletionService(modelId);
#pragma warning restore SKEXP0070
var kernel = builder.Build();
```

## 使ってみる

まぁ、そんな感じでkernelができあったので`IChatCompletionService`経由で、チャットを投げてみましょう。トークンの最大数の指定の仕方がディクショナリベースなのがちょっと残念なところですね。

```cs
ChatHistory history = [];
history.AddSystemMessage("あなたはフレンドリーなAIエージェントです。C#とAWSのスペシャリストです。");
history.AddUserMessage("C# と Semantic Kernel を利用して AWS Badrock の Anthropic claude 3.5 Sonnet モデルを利用する方法を教えてください。");

// カーネルからIChatCompletionServiceを取り出し
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var promptSettings = new PromptExecutionSettings
{
    ExtensionData = new Dictionary<string, object>
    {
        { "max_tokens_to_sample", 2048 }
    }
};

// メッセージを投げて結果を表示する
var response = await chatCompletionService.GetChatMessageContentsAsync(history, promptSettings, kernel: kernel);
foreach (var content in response)
{
    Console.WriteLine(content);
}
```

次のような結果が返ってきました。
<details><summary>チャットの応答</summary>

> はい、C#とSemantic Kernelを使ってAWS Badrock上のAnthropic claude 3.5 Sonnetモデルを利用する方法をご説明します。
>
> 1. **Semantic Kernel の設定**
>   - まず、Semantic Kernel のCライブラリをプロジェクトに追加します。
>   - Semantic Kernel の初期化を行い、AWS Badrock への接続情報を設定します。
>
>```csharp
>using SemanticKernel;
>using SemanticKernel.AI;
>using SemanticKernel.Orchestration;
>
>// Semantic Kernel の初期化
>var kernel = new KernelBuilder()
>    .WithLogger(new ConsoleLogger())
>    .Build();
>
>// AWS Badrock への接続情報を設定
>kernel.Config.SetCloudProvider(CloudProvider.AWS);
>kernel.Config.SetAzureEndpoint(endpoint: "https://your-aws-badrock-endpoint.amazonaws.com");
>kernel.Config.SetAzureApiKey("your-aws-badrock-api-key");
>```
>
>2. **Anthropic claude 3.5 Sonnet モデルの利用**
>   - Semantic Kernel では、AI機能をFunction Skillsとして定義します。
>   - 以下のコードでAnthropic claude 3.5 Sonnetモデルを利用するFunction Skillを作成します。
>
>```csharp
>// Anthropic claude 3.5 Sonnet モデルを利用するFunction Skill
>var sonnetSkill = kernel.CreateSemanticFunction(
>    "Sonnet",
>    "Generates a Shakespearean sonnet based on the provided prompt.",
>    inputs: new[]
>    {
>        new SemanticFunction.Param<string>("prompt", "The prompt to use for generating the sonnet.")
>    },
>    outputs: new[]
>    {
>        new SemanticFunction.Param<string>("sonnet", "The generated Shakespearean sonnet.")
>    },
>    executor: async (prompt, context) =>
>    {
>        // AWS Badrock 上のAnthropic claude 3.5 Sonnetモデルを呼び出す
>        var result = await context.AI.RunAsync(
>            provider: AIProvider.Anthropic,
>            model: "claude-v3.5-sonnet",
>            prompt: $"Generate a Shakespearean sonnet based on the following prompt: {prompt}");
>
>        return new { sonnet = result.Output };
>    });
>```
>
>3. **Sonnetスキルの利用**
>   - 作成したSonnetスキルを使ってソネットを生成することができます。
>
>```csharp
>// Sonnetスキルを使ってソネットを生成
>var prompt = "The roses bloom, their petals soft and fair,
>  Their fragrance fills the air with sweet delight.
>  A vision of nature's beauty, beyond compare,
>  A scene that captivates the senses' sight.";
>var result = await sonnetSkill.InvokeAsync(prompt);
>Console.WriteLine(result.sonnet);
>```
>
>この方法では、C#とSemantic Kernelを使ってAWS Badrock上のAnthropic claude 3.5 Sonnetモデルにアクセスし、ソネットを生成す ることができます。Semantic Kernelは、AIモデルへのアクセスを抽象化し、より簡単にAI機能を利用できるようにしてくれます。

</details>

ストリーミングで結果を取得して表示したいのであれば `GetStreamingChatMessageContentsAsync` メソッドを使って、await foreachで待機します。表示のされ方は違いますが結果は同じなので省略します。

```cs
var response = chatCompletionService.GetStreamingChatMessageContentsAsync(
    history,
    promptSettings,
    kernel: kernel
);
await foreach (var chunk in response)
{
    Console.Write(chunk);
}
```

## Bedrockを直に使ってみる

Bedrockを直に使ったサンプルも見てみましょう。
若干冗長な感じはありますが、AWSの他のAPIと整合性が取れているので、これはこれでありですね。

```cs
var client = new AmazonBedrockRuntimeClient(credentials, RegionEndpoint.APNortheast1);
var request = new ConverseStreamRequest
{
    ModelId = modelId,
    Messages = [
        new Message
        {
            Role = ConversationRole.User,
            Content = [
                new ContentBlock { Text = userMessage }
            ]
        }
    ],
    InferenceConfig = new InferenceConfiguration
    {
        MaxTokens = 2048
    }
};

var response = await client.ConverseStreamAsync(request);
foreach (var chunk in response.Stream.AsEnumerable().OfType<ContentBlockDeltaEvent>())
{
    Console.Write(chunk.Delta.Text);
}
```

当たり前ですが、こちらはクロスリージョンなモデルであっても動作させることができます。
モデルIDを `apac.anthropic.claude-3-5-sonnet-20241022-v2:0` にして動作させた結果です。
。。。あれ？この内容を見るとこの記事の意義が、、、ｗ

<details><summary>チャットの応答</summary>

> C#とSemantic Kernelを使用してAWS BedrockのClaude-3.5 Sonnetモデルを利用する方法を説明します。
> 
> 1. まず、必要なNuGetパッケージをインストールします：
> 
> ```bash
> dotnet add package Microsoft.SemanticKernel
> dotnet add package AWSSDK.BedrockRuntime
> ```
> 
> 2. 基本的な実装例：
> 
> ```csharp
> using Microsoft.SemanticKernel;
> using Amazon.BedrockRuntime;
> using Amazon.BedrockRuntime.Model;
> using Amazon;
> 
> public class BedrockClaudeExample
> {
>     private static async Task Main()
>     {
>         // AWS認証情報の設定
>         var bedrockClient = new AmazonBedrockRuntimeClient(
>             awsAccessKeyId: "YOUR_ACCESS_KEY",
>             awsSecretAccessKey: "YOUR_SECRET_KEY",
>             region: RegionEndpoint.USEast1 // または適切なリージョン
>         );
> 
>         // Kernelの設定
>         var builder = Kernel.CreateBuilder()
>             .AddAmazonBedrockChatCompletion(
>                 "anthropic.claude-3-sonnet-20240229-v1:0", // モデルID
>                 bedrockClient
>             );
> 
>         var kernel = builder.Build();
> 
>         // チャット機能の利用例
>         var prompt = "What are the main benefits of using Semantic Kernel?";
> 
>         var chatResult = await kernel.InvokePromptAsync(prompt);
>         Console.WriteLine(chatResult);
>     }
> }
> ```
> 
> 3. より詳細な実装例（チャット履歴を含む）：
> 
> ```csharp
> using Microsoft.SemanticKernel;
> using Microsoft.SemanticKernel.ChatCompletion;
> using Amazon.BedrockRuntime;
> using Amazon;
> 
> public class BedrockClaudeDetailedExample
> {
>     private static async Task Main()
>     {
>         // AWS認証情報の設定
>         var bedrockClient = new AmazonBedrockRuntimeClient(
>             awsAccessKeyId: "YOUR_ACCESS_KEY",
>             awsSecretAccessKey: "YOUR_SECRET_KEY",
>             region: RegionEndpoint.USEast1
>         );
> 
>         // Kernelの設定
>         var builder = Kernel.CreateBuilder()
>             .AddAmazonBedrockChatCompletion(
>                 "anthropic.claude-3-sonnet-20240229-v1:0",
>                 bedrockClient
>             );
> 
>         var kernel = builder.Build();
> 
>         // チャットの開始
>         var chatHistory = new ChatHistory();
> 
>         // システムメッセージの追加
>         chatHistory.AddSystemMessage("You are a helpful AI assistant.");
> 
>         // ユーザーメッセージの追加と応答の取得
>         chatHistory.AddUserMessage("What is Semantic Kernel?");
> 
>         var response = await kernel.GetRequiredService<IChatCompletionService>()
>             .GetChatMessageContentAsync(chatHistory);
> 
>         Console.WriteLine($"Assistant: {response}");
>         chatHistory.AddAssistantMessage(response.Content);
> 
>         // 続けて会話
>         chatHistory.AddUserMessage("Can you give me an example of how to use it?");
> 
>         response = await kernel.GetRequiredService<IChatCompletionService>()
>             .GetChatMessageContentAsync(chatHistory);
> 
>         Console.WriteLine($"Assistant: {response}");
>     }
> }
> ```
> 
> 4. エラーハンドリングを含む実装例：
> 
> ```csharp
> using Microsoft.SemanticKernel;
> using Amazon.BedrockRuntime;
> using Amazon;
> using Amazon.BedrockRuntime.Model;
> 
> public class BedrockClaudeWithErrorHandling
> {
>     private static async Task Main()
>     {
>         try
>         {
>             // AWS認証情報の設定
>             var bedrockClient = new AmazonBedrockRuntimeClient(
>                 awsAccessKeyId: "YOUR_ACCESS_KEY",
>                 awsSecretAccessKey: "YOUR_SECRET_KEY",
>                 region: RegionEndpoint.USEast1
>             );
> 
>             // Kernelの設定
>             var builder = Kernel.CreateBuilder()
>                 .AddAmazonBedrockChatCompletion(
>                     "anthropic.claude-3-sonnet-20240229-v1:0",
>                     bedrockClient
>                 );
> 
>             var kernel = builder.Build();
> 
>             // プロンプトの実行
>             try
>             {
>                 var result = await kernel.InvokePromptAsync(
>                     "Explain quantum computing in simple terms."
>                 );
>                 Console.WriteLine(result);
>             }
>             catch (ResourceNotFoundException ex)
>             {
>                 Console.WriteLine($"Model not found: {ex.Message}");
>             }
>             catch (ValidationException ex)
>             {
>                 Console.WriteLine($"Invalid request: {ex.Message}");
>             }
>             catch (ThrottlingException ex)
>             {
>                 Console.WriteLine($"Rate limit exceeded: {ex.Message}");
>             }
>         }
>         catch (Exception ex)
>         {
>             Console.WriteLine($"An error occurred: {ex.Message}");
>         }
>     }
> }
> ```
> 
> 注意点：
> 
> 1. AWS認証情報は適切に管理し、環境変数やAWS認証情報ファイルを使用することを推奨します。
> 
> 2. リージョンは、Bedrockサービスが利用可能なリージョンを指定してください。
> 
> 3. モデルIDは最新のものを使用してください。
> 
> 4. レート制限やクォータに注意してください。
> 
> 5. エラーハンドリングを適切に実装してください。
> 
> 6. 必要に応じて、応答のストリーミングやタイムアウト設定なども検討してください。
> 
> この実装を基に、必要に応じてカスタマイズを行ってください。

</details>

## おわりに

Bedrock自体AWSでホストされたAIモデルを抽象化している層なので、AWSでAIモデルを使うぞ！となった場合にSemanticKernelの皮をもう一枚かぶせてもそこまでうまみがあるわけではないですが、これまでSemanticKernelをベースになにか作ってきた場合はカーネルの作成部分を変えるだけで対応できるのはうれしいですね。