Dela via


Importera ett API för Amazon Bedrock pass-through-språkmodell

GÄLLER FÖR: Alla API Management-nivåer

I den här artikeln importerar du ett API för Amazon Bedrock-språkmodell till DIN API Management-instans som ett API för genomströmning. Det här är ett exempel på en modell som finns på en annan slutsatsdragningsprovider än Azure AI-tjänster. Använd principer för AI-gatewayer och andra funktioner i API Management för att förenkla integrering, förbättra observerbarheten och förbättra kontrollen över modellsutgångspunkterna.

Läs mer om att hantera AI-API:er i API Management:

Läs mer om Amazon Bedrock:

Förutsättningar

  • En befintlig API Management-instans. Skapa en om du inte redan har gjort det.
  • Ett AWS-konto (Amazon Web Services) med åtkomst till Amazon Bedrock och tillgång till en eller flera Amazon Bedrock Foundation-modeller. Läs mer

Skapa IAM-användaråtkomstnycklar

För att autentisera din API Management-instans till Amazon API Gateway behöver du åtkomstnycklar för en AWS IAM-användare.

Information om hur du genererar det nödvändiga åtkomstnyckel-ID:t och den hemliga nyckeln med hjälp av AWS-hanteringskonsolen finns i Skapa en åtkomstnyckel för dig själv i AWS-dokumentationen.

Spara dina åtkomstnycklar på en säker plats. Du lagrar dem som namngivna värden i nästa steg.

Försiktighet

Åtkomstnycklar är långsiktiga autentiseringsuppgifter och du bör hantera dem lika säkert som ett lösenord. Läs mer om att skydda åtkomstnycklar

Lagra IAM-användaråtkomstnycklar som namngivna värden

Lagra de två IAM-användaråtkomstnycklarna på ett säkert sätt som hemliga namngivna värden i din Azure API Management-instans med hjälp av konfigurationen som rekommenderas i följande tabell.

AWS-hemlighet Namn Hemligt värde
Åtkomstnyckel accesskey Åtkomstnyckel-ID som hämtats från AWS
Hemlig åtkomstnyckel secretkey Hemlig åtkomstnyckel som hämtats från AWS

Importera ett Bedrock-API med hjälp av portalen

Så här importerar du ett Amazon Bedrock-API till API Management:

  1. I Azure Portal navigerar du till din API Management-instans.

  2. I den vänstra menyn går du till API:er och väljer API:er>+ Lägg till API.

  3. Under Definiera ett nytt API väljer du Språkmodell-API.

    Skärmbild av att skapa ett API för genomströmningsspråkmodell i portalen.

  4. På fliken Konfigurera API :

    1. Ange ett visningsnamn och en valfri beskrivning för API:et.

    2. Ange följande URL till standardslutpunkten för Amazon Bedrock: https://bedrock-runtime.<aws-region>.amazonaws.com.

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

    3. Du kan också välja en eller flera produkter som ska associeras med API:et.

    4. I Sökväg lägger du till en sökväg som din API Management-instans använder för att komma åt LLM API-slutpunkterna.

    5. I Typ väljer du Skapa ett API för genomströmning.

    6. Lämna värdena tomma i Åtkomstnyckeln .

    Skärmbild av API-konfigurationen för språkmodell i portalen.

  5. På de återstående flikarna kan du välja att konfigurera principer för att hantera tokenförbrukning, semantisk cachelagring och AI-innehållssäkerhet. Mer information finns i Importera ett språkmodell-API.

  6. Välj Granska.

  7. När inställningarna har verifierats väljer du Skapa.

API Management skapar API- och (valfritt) principer som hjälper dig att övervaka och hantera API:et.

Konfigurera principer för att autentisera begäranden till Amazon Bedrock API

Konfigurera API Management-principer för att signera begäranden till Amazon Bedrock-API:et. Läs mer om att signera AWS API-begäranden

I följande exempel används accesskey och secretkey med namngivna värden som du skapade tidigare för AWS-åtkomstnyckeln och hemlighetsnyckeln. Ange variabeln till lämpligt värde för amazon bedrock-API:et region . Exemplet använder us-east-1 för regionen.

  1. I Azure Portal navigerar du till din API Management-instans.

  2. I den vänstra menyn går du till API:er och väljer API:er.

  3. Välj det API som du skapade i föregående avsnitt.

  4. I den vänstra menyn under Design väljer du Alla åtgärder.

  5. Välj fliken Inkommande bearbetning .

  6. I redigeraren för inkommande bearbetningsprinciper väljer du </> för att öppna principredigeraren.

  7. Konfigurera följande principer:

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

Anropa Bedrock-API:et

Om du vill anropa Bedrock-API:et via API Management kan du använda AWS Bedrock SDK. I det här exemplet används .NET SDK, men du kan använda valfritt språk som stöder AWS Bedrock-API:et.

I följande exempel används en anpassad HTTP-klient som instansierar klasser som definierats i den medföljande filen BedrockHttpClientFactory.cs. Den anpassade HTTP-klienten dirigerar begäranden till API Management-slutpunkten och innehåller API Management-prenumerationsnyckeln (om det behövs) i begärandehuvudena.

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

Följande kod implementerar klasser för att skapa en anpassad HTTP-klient som dirigerar begäranden till Bedrock-API:et via API Management, inklusive en API Management-prenumerationsnyckel i rubrikerna.

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);
        }
    }
}