Search code examples
azureazure-api-managementazure-openai

How to call an Azure OpenAI service which is behind an API Management instance?


I followed this video walkthrough to set up an Azure OpenAI api management service. Then, using Azure.AI.OpenAI 2.0.0-beta.2, I'm trying to call the chat completion endpoint of a gpt-4o deployment which is behind an API Management instance - but can't.

Initially, I followed the example appearing in the AzureOpenAI-with-APIM documentation (note that the Nuget version over there is 1.0.0-beta.5, earlier then mine), which says the url should be in the following format:

var url = $"{apim_url}/deployments/gpt-4o/chat/completions?api-version=2024-02-01";

But this results in 404 Not Found error. Looking at my APIM logs, I see that the actual url used was wrong - the chat/completion part is duplicated by the client.

So I changed it to the format in the following code, which managed to stop the 404 error, but I now get a 401 error:

// apim_url is taken from my apim overview page 
var apim_url = "redacted";
    
// subscription_key is taken from the subscriptions key page
var subscription_key = "redacted";
    
var url = $"{apim_url}/deployments/gpt-4o?api-version=2024-02-01"; 
    
var openAIClient = new OpenAIClient(
    credential: new System.ClientModel.ApiKeyCredential(subscription_key),
    options: new OpenAIClientOptions() { Endpoint = new Uri(url) }
);

var chatClient = openAIClient.GetChatClient("gpt-4o");

// this results in 401 error
var completion = chatClient.CompleteChat(new ChatMessage[] {
    new SystemChatMessage("You are a helpful assistant that talks like a pirate."),
    new UserChatMessage("Hi, can you help me?"),
});

So it seems like the subscription_key is not being used. I know it should be passed as an api-key header, but how do I do that?


Solution

  • [self-answer]

    There are two ways to add the subscription key as an api-key header:

    Solution no. 1 - manually add api-key header

    // apim_url is taken from my apim overview page 
    var apim_url = "redacted";
        
    // note this is not the "official" url mentioned in the documentation
    var url = $"{apim_url}/deployments/gpt-4o?api-version=2024-02-01";
        
    // subscription_key is taken from the subscriptions key page
    var subscription_key = "redacted";
        
    // create an httpClient and set subscription_key in an 'api-key' header
    var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Add("api-key", subscription_key);
    var clientOptions = new OpenAIClientOptions
    {
        Endpoint = new Uri(url),
        Transport = 
            // For Azure.AI.OpenAI 2.0.0, use:
            new System.ClientModel.Primitives.HttpClientPipelineTransport(httpClient)
            // For Azure.AI.OpenAI 1.0.0, use this instead:
            // new Azure.Core.Pipeline.HttpClientTransport(httpClient)
    };
        
    var openAIClient = new OpenAIClient(
        credential: new System.ClientModel.ApiKeyCredential(subscription_key), // still required!
        options: clientOptions
    );
    var chatClient = openAIClient.GetChatClient("gpt-4o");
    var completion = chatClient.CompleteChat(new ChatMessage[] {
        new SystemChatMessage("You are a helpful assistant that talks like a pirate."),
        new UserChatMessage("Hi, can you help me?"),
    });
    

    Solution no. 2 - implement a "policy" class, and set the api-key header in it

    • For Azure.AI.OpenAI 2.0.0:

    Implement a PipelinePolicy:

    public class ApiKeyPolicy : PipelinePolicy
    {
        private readonly string _apiKey;
    
        public ApiKeyPolicy(string apiKey)
        {
            _apiKey = apiKey;
        }
    
        public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
        {
            message.Request.Headers.Add("api-key", _apiKey);
            ProcessNext(message, pipeline, currentIndex);
        }
    
        public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
        {
            message.Request.Headers.Add("api-key", _apiKey);
            return ProcessNextAsync(message, pipeline, currentIndex);
        }
    }
    

    Then:

    // apim_url is taken from my apim overview page 
    var apim_url = "redacted";
    
    // note this is not the "official" url mentioned in the documentation
    var url = $"{apim_url}/deployments/gpt-4o?api-version=2024-02-01";
    
    // subscription_key is taken from the subscriptions key page
    var subscription_key = "redacted";
    
    // create an apiKeyPolicy and set clientOptions with it
    var apiKeyPolicy = new ApiKeyPolicy(subscription_key);
    var clientOptions = new OpenAIClientOptions 
    { 
        Endpoint = new Uri(url), 
        Transport = new System.ClientModel.Primitives.HttpClientPipelineTransport(new HttpClient()) 
    };
    clientOptions.AddPolicy(apiKeyPolicy, PipelinePosition.PerCall);
    
    var openAIClient = new OpenAIClient(
        credential: new System.ClientModel.ApiKeyCredential(subscription_key), // still required!
        options: clientOptions
    );
    var chatClient = openAIClient.GetChatClient("gpt-4o");
    var completion = chatClient.CompleteChat(new ChatMessage[] {
        new SystemChatMessage("You are a helpful assistant that talks like a pirate."),
        new UserChatMessage("Hi, can you help me?"),
    });
    
    • For Azure.AI.OpenAI 1.0.0:

    Implement an HttpPipelinePolicy:

    public class ApiKeyPolicy : HttpPipelinePolicy
    {
        private readonly string _apiKey;
    
        public ApiKeyPolicy(string apiKey)
        {
            _apiKey = apiKey;
        }
    
        public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
        {
            message.Request.Headers.Add("api-key", _apiKey);
            ProcessNext(message, pipeline);
         }
    
         public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
         {
             message.Request.Headers.Add("api-key", _apiKey);
             return ProcessNextAsync(message, pipeline);
         }
    }
    

    Then:

    // ... same code as above ...
    var clientOptions = new OpenAIClientOptions 
    { 
        Endpoint = new Uri(url), 
        Transport = new Azure.Core.Pipeline.HttpClientTransport(new HttpClient()) 
    };
    clientOptions.AddPolicy(apiKeyPolicy, HttpPipelinePosition.PerCall);
    // ... same code as above ...