次の方法で共有


Amazon Bedrock パススルー言語モデル API をインポートする

適用対象: すべての API Management レベル

この記事では、Amazon Bedrock 言語モデル API をパススルー API として API Management インスタンスにインポートします。 これは、Azure AI サービス以外の推論プロバイダーでホストされているモデルの例です。 API Management で AI ゲートウェイ ポリシーやその他の機能を使用して、統合を簡素化し、監視性を向上させ、モデル エンドポイントの制御を強化します。

API Management での AI API の管理の詳細については、以下を参照してください。

Amazon Bedrock の詳細については、以下をご覧ください。

[前提条件]

IAM ユーザー アクセス キーを作成する

Amazon API Gateway に対して API Management インスタンスを認証するには、AWS IAM ユーザーのアクセスキーが必要です。

AWS マネジメントコンソールを使用して必要なアクセスキー ID と秘密鍵を生成するには、AWS ドキュメントの「自分用のアクセス キーを作成する」を参照してください。

安全な場所にアクセス キーを保存します。 次の手順では、名前付き値として格納します。

注意事項

アクセス キーは長期的な資格情報であり、パスワードと同じように安全に管理する必要があります。 アカウント キーの保護に関する詳細情報

IAM ユーザー アクセス キーを名前付き値として格納する

次の表で推奨される構成を使用して、2 つの IAM ユーザー アクセス キーをシークレット の名前付き値 として Azure API Management インスタンスに安全に格納します。

AWS シークレット 名前 シークレット値
アクセス キー accesskey AWS から取得したアクセス キー ID
シークレット アクセス キー secretkey AWS から取得したシークレット アクセス キー

ポータルを使用して Bedrock API をインポートする

Amazon Bedrock API を API Management にインポートするには:

  1. Azure portal で、API Management インスタンスに移動します。

  2. 左側のメニューの [API] で、[API]>[+ API の追加] を選択します。

  3. [ 新しい API の定義] で、[ 言語モデル API] を選択します。

    ポータルでパススルー言語モデル API を作成するスクリーンショット。

  4. [ API の構成 ] タブで、次の手順を実行します。

    1. API の表示名と、必要に応じて説明を入力します。

    2. 既定の Amazon Bedrock エンドポイントの URL としてhttps://bedrock-runtime.<aws-region>.amazonaws.comを入力します。

      例: https://bedrock-runtime.us-east-1.amazonaws.com

    3. 必要に応じて、API に関連付ける 1 つ以上の 製品 を選択します。

    4. [パス] に、API Management インスタンスが LLM API エンドポイントへのアクセスに使用するパスを追加します。

    5. [種類] で、[パススルー API の作成] を選択します。

    6. Access キーの値は空白のままにします。

    ポータルでの言語モデル API の構成のスクリーンショット。

  5. 残りのタブで、必要に応じて、トークンの使用、セマンティック キャッシュ、AI コンテンツの安全性を管理するポリシーを構成します。 詳細については、「 言語モデル API のインポート」を参照してください。

  6. [レビュー] を選択します。

  7. 設定が検証されたら、[作成] を選択します。

API Management は、API の監視と管理に役立つ API と (必要に応じて) ポリシーを作成します。

Amazon Bedrock API に対する要求を認証するポリシーを構成する

Amazon Bedrock API に要求に署名するように API Management ポリシーを構成します。 AWS API 要求への署名の詳細

次の例では、AWS アクセス キーとシークレット キーに対して以前に作成したアクセスキーとシークレットキーの名前付き値を使用します。 region変数を Amazon Bedrock API の適切な値に設定します。 この例では、リージョンに us-east-1 を使用します。

  1. Azure portal で、API Management インスタンスに移動します。

  2. 左側のメニューの [API] で、[API] を選択します。

  3. 前のセクションで作成した API を選択します。

  4. 左側のメニューの [ デザイン] で、[ すべての操作] を選択します。

  5. [ 受信処理 ] タブを選択します。

  6. 受信処理ポリシー エディターで、</> を選択してポリシー エディターを開きます。

  7. 次のポリシーを構成します。

    <policies>
    <inbound>
        <base />
        <set-variable name="now" value="@(DateTime.UtcNow)" />
        <set-header name="X-Amz-Date" exists-action="override">
            <value>@(((DateTime)context.Variables["now"]).ToString("yyyyMMddTHHmmssZ"))</value>
        </set-header>
        <set-header name="X-Amz-Content-Sha256" exists-action="override">
            <value>@{
                var body = context.Request.Body.As<string>(preserveContent: true);
                using (var sha256 = System.Security.Cryptography.SHA256.Create())
                {
                    var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(body));
                    return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                }
            }</value>
        </set-header>
        <set-header name="Authorization" exists-action="override">
            <value>@{
                var accessKey = "{{accesskey}}";
                var secretKey = "{{secretkey}}";
                var region = "us-east-1";
                var service = "bedrock";
    
                var method = context.Request.Method;
                var uri = context.Request.Url;
                var host = uri.Host;
    
                // Create canonical path
                var path = uri.Path;
                var modelSplit = path.Split(new[] { "model/" }, 2, StringSplitOptions.None);
                var afterModel = modelSplit.Length > 1 ? modelSplit[1] : "";
                var parts = afterModel.Split(new[] { '/' }, 2);
                var model = System.Uri.EscapeDataString(parts[0]);
                var remainder = parts.Length > 1 ? parts[1] : "";
                var canonicalPath = $"/model/{model}/{remainder}";
    
                var amzDate = ((DateTime)context.Variables["now"]).ToString("yyyyMMddTHHmmssZ");
                var dateStamp = ((DateTime)context.Variables["now"]).ToString("yyyyMMdd");
    
                // Hash the payload
                var body = context.Request.Body.As<string>(preserveContent: true);
                string hashedPayload;
                using (var sha256 = System.Security.Cryptography.SHA256.Create())
                {
                    var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(body));
                    hashedPayload = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                }
    
                // Create canonical query string
                var queryDict = context.Request.Url.Query;
                var canonicalQueryString = "";
                if (queryDict != null && queryDict.Count > 0)
                {
                    var encodedParams = new List<string>();
                    foreach (var kvp in queryDict)
                    {
                        var encodedKey = System.Uri.EscapeDataString(kvp.Key);
                        var encodedValue = System.Uri.EscapeDataString(kvp.Value.First() ?? "");
                        encodedParams.Add($"{encodedKey}={encodedValue}");
                    }
                    canonicalQueryString = string.Join("&", encodedParams.OrderBy(p => p));
                }
    
                // Create signed headers and canonical headers
                var headers = context.Request.Headers;
                var canonicalHeaderList = new List<string[]>();
    
                // Add content-type if present
                var contentType = headers.GetValueOrDefault("Content-Type", "").ToLowerInvariant();
                if (!string.IsNullOrEmpty(contentType))
                {
                    canonicalHeaderList.Add(new[] { "content-type", contentType });
                }
    
                // Always add host
                canonicalHeaderList.Add(new[] { "host", host });
    
                // Add x-amz-* headers (excluding x-amz-date, x-amz-content-sha256)
                foreach (var header in headers)
                {
                    var name = header.Key.ToLowerInvariant();
                    if (string.Equals(name, "x-amz-content-sha256", StringComparison.OrdinalIgnoreCase) || 
                        string.Equals(name, "x-amz-date", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }
    
                    if (name.StartsWith("x-amz-"))
                    {
                        var value = header.Value.First()?.Trim();
                        canonicalHeaderList.Add(new[] { name, value });
                    }
                }
                canonicalHeaderList.Add(new[] { "x-amz-content-sha256", hashedPayload });
                canonicalHeaderList.Add(new[] { "x-amz-date", amzDate });
                var canonicalHeadersOrdered = canonicalHeaderList.OrderBy(h => h[0]);
                var canonicalHeaders = string.Join("\n", canonicalHeadersOrdered.Select(h => $"{h[0]}:{h[1].Trim()}")) + "\n";
                var signedHeaders = string.Join(";", canonicalHeadersOrdered.Select(h => h[0]));
    
                // Create and hash the canonical request
                var canonicalRequest = $"{method}\n{canonicalPath}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{hashedPayload}";
                string hashedCanonicalRequest = "";
                using (var sha256 = System.Security.Cryptography.SHA256.Create())
                {
                    var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(canonicalRequest));
                    hashedCanonicalRequest = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                }
    
                // Build string to sign
                var credentialScope = $"{dateStamp}/{region}/{service}/aws4_request";
                var stringToSign = $"AWS4-HMAC-SHA256\n{amzDate}\n{credentialScope}\n{hashedCanonicalRequest}";
    
                // Sign it using secret key
                byte[] kSecret = System.Text.Encoding.UTF8.GetBytes("AWS4" + secretKey);
                byte[] kDate, kRegion, kService, kSigning;
                using (var h1 = new System.Security.Cryptography.HMACSHA256(kSecret))
                {
                    kDate = h1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(dateStamp));
                }
                using (var h2 = new System.Security.Cryptography.HMACSHA256(kDate))
                {
                    kRegion = h2.ComputeHash(System.Text.Encoding.UTF8.GetBytes(region));
                }
                using (var h3 = new System.Security.Cryptography.HMACSHA256(kRegion))
                {
                    kService = h3.ComputeHash(System.Text.Encoding.UTF8.GetBytes(service));
                }
                using (var h4 = new System.Security.Cryptography.HMACSHA256(kService))
                {
                    kSigning = h4.ComputeHash(System.Text.Encoding.UTF8.GetBytes("aws4_request"));
                }
    
                // Auth header
                string signature; 
                using (var hmac = new System.Security.Cryptography.HMACSHA256(kSigning))
                {
                    var sigBytes = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringToSign));
                    signature = BitConverter.ToString(sigBytes).Replace("-", "").ToLowerInvariant();
                }
    
                return $"AWS4-HMAC-SHA256 Credential={accessKey}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}";
            }</value>
        </set-header>
        <set-header name="Host" exists-action="override">
            <value>@(context.Request.Url.Host)</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
    </policies>
    

Bedrock API を呼び出す

API Management を使用して Bedrock API を呼び出すには、AWS Bedrock SDK を使用できます。 この例では .NET SDK を使用しますが、AWS Bedrock API をサポートする任意の言語を使用できます。

次の例では、付属のファイル BedrockHttpClientFactory.csで定義されているクラスをインスタンス化するカスタム HTTP クライアントを使用します。 カスタム HTTP クライアントは、要求を API Management エンドポイントにルーティングし、必要に応じて API Management サブスクリプション キーを要求ヘッダーに含めます。

using Amazon;
using Amazon.BedrockRuntime;
using Amazon.BedrockRuntime.Model;
using Amazon.Runtime;
using BedrockClient;

// Leave accessKey and secretKey values as empty strings. Authentication to AWS API is handled through policies in API Management.
var accessKey = "";
var secretKey = "";
var credentials = new BasicAWSCredentials(accessKey, secretKey);

// Create custom configuration to route requests through API Management
// apimUrl is the API Management endpoint, such as https://apim-hello-word.azure-api.net/bedrock
var apimUrl = "<api-management-endpoint">;
// Provide name and value for the API Management subscription key header.
var apimSubscriptionHeaderName = "api-key";
var apimSubscriptionKey = "<your-apim-subscription-key>";
var config = new AmazonBedrockRuntimeConfig()
{
    HttpClientFactory = new BedrockHttpClientFactory(apimUrl, apimSubscriptionHeaderName, apimSubscriptionKey),
    // Set the AWS region where your Bedrock model is hosted.
    RegionEndpoint = RegionEndpoint.USEast1
};

var client = new AmazonBedrockRuntimeClient(credentials, config);

// Set the model ID, e.g., Claude 3 Haiku. Find the supported models in Amazon Bedrock documentation: https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html.
var modelId = "us.anthropic.claude-3-5-haiku-20241022-v1:0";

// Define the user message.
var userMessage = "Describe the purpose of a 'hello world' program in one line.";

// Create a request with the model ID, the user message, and an inference configuration.
var request = new ConverseRequest
{
    ModelId = modelId,
    Messages = new List<Message>
    {
        new Message
        {
            Role = ConversationRole.User,
            Content = new List<ContentBlock> { new ContentBlock { Text = userMessage } }
        }
    },
    InferenceConfig = new InferenceConfiguration()
    {
        MaxTokens = 512,
        Temperature = 0.5F,
        TopP = 0.9F
    }
};

try
{
    // Send the request to the Bedrock runtime and wait for the result.
    var response = await client.ConverseAsync(request);

    // Extract and print the response text.
    string responseText = response?.Output?.Message?.Content?[0]?.Text ?? "";
    Console.WriteLine(responseText);
}
catch (AmazonBedrockRuntimeException e)
{
    Console.WriteLine($"ERROR: Can't invoke '{modelId}'. Reason: {e.Message}");
    throw;
}

BedrockHttpClientFactory.cs

次のコードでは、API Management を介して Bedrock API に要求をルーティングするカスタム HTTP クライアントを作成するクラスを実装します。これには、ヘッダーに API Management サブスクリプション キーが含まれます。

using Amazon.Runtime;

namespace BedrockClient
{
    public class BedrockHttpClientFactory : HttpClientFactory
    {
        readonly string subscriptionKey;
        readonly string subscriptionHeaderName;
        readonly string rerouteUrl;

        public BedrockHttpClientFactory(string rerouteUrl, string subscriptionHeaderName, string subscriptionKey)
        {
            this.rerouteUrl = rerouteUrl;
            this.subscriptionHeaderName = subscriptionHeaderName;
            this.subscriptionKey = subscriptionKey;
        }

        public override HttpClient CreateHttpClient(IClientConfig clientConfig)
        {
            var handler = new RerouteHandler(rerouteUrl)
            {
                InnerHandler = new HttpClientHandler()
            };

            var httpClient = new HttpClient(handler);
            httpClient.DefaultRequestHeaders.Add(this.subscriptionHeaderName, this.subscriptionKey);
            return httpClient;
        }
    }

    public class RerouteHandler : DelegatingHandler
    {
        readonly string rerouteUrl;
        readonly string host;

        public RerouteHandler(string rerouteUrl)
        {
            this.rerouteUrl = rerouteUrl;
            this.host = rerouteUrl.Split("/")[2].Split(":")[0];
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var originalUri = request.RequestUri;
            request.RequestUri = new Uri($"{this.rerouteUrl}{originalUri.PathAndQuery}");
            request.Headers.Host = this.host;
            return base.SendAsync(request, cancellationToken);
        }
    }
}