azure.go

  1// Package azure provides an implementation of the fantasy AI SDK for Azure's language models.
  2package azure
  3
  4import (
  5	"fmt"
  6	"regexp"
  7	"strings"
  8
  9	"charm.land/fantasy"
 10	"charm.land/fantasy/providers/openai"
 11	"github.com/openai/openai-go/v2/azure"
 12	"github.com/openai/openai-go/v2/option"
 13)
 14
 15type options struct {
 16	baseURL    string
 17	apiKey     string
 18	apiVersion string
 19
 20	openaiOptions []openai.Option
 21}
 22
 23const (
 24	// Name is the name of the Azure provider.
 25	Name = "azure"
 26	// defaultAPIVersion is the default API version for Azure.
 27	defaultAPIVersion = "2025-01-01-preview"
 28)
 29
 30// azureURLPattern matches Azure OpenAI endpoint URLs in various formats:
 31// * https://resource-id.openai.azure.com;
 32// * https://resource-id.openai.azure.com/;
 33// * https://resource-id.cognitiveservices.azure.com;
 34// * https://resource-id.services.ai.azure.com/api/projects/project-name;
 35// * resource-id.openai.azure.com.
 36var azureURLPattern = regexp.MustCompile(`^(?:https?://)?([a-zA-Z0-9-]+)\.(?:openai|cognitiveservices|services\.ai)\.azure\.com(?:/.*)?$`)
 37
 38// Option defines a function that configures Azure provider options.
 39type Option = func(*options)
 40
 41// New creates a new Azure provider with the given options.
 42func New(opts ...Option) (fantasy.Provider, error) {
 43	o := options{
 44		apiVersion: defaultAPIVersion,
 45	}
 46	for _, opt := range opts {
 47		opt(&o)
 48	}
 49	return openai.New(
 50		append(
 51			o.openaiOptions,
 52			openai.WithName(Name),
 53			openai.WithBaseURL(o.baseURL),
 54			openai.WithSDKOptions(
 55				azure.WithAPIKey(o.apiKey),
 56			),
 57		)...,
 58	)
 59}
 60
 61// WithBaseURL sets the base URL for the Azure provider.
 62func WithBaseURL(baseURL string) Option {
 63	return func(o *options) {
 64		o.baseURL = parseAzureURL(baseURL)
 65	}
 66}
 67
 68// parseAzureURL extracts the resource ID from various Azure URL formats
 69// and returns the standardized OpenAI-compatible endpoint URL.
 70// If the URL doesn't match known Azure patterns, it returns the original URL.
 71func parseAzureURL(baseURL string) string {
 72	matches := azureURLPattern.FindStringSubmatch(baseURL)
 73	if len(matches) >= 2 {
 74		resourceID := matches[1]
 75		return fmt.Sprintf("https://%s.openai.azure.com/openai/v1", resourceID)
 76	}
 77	// fallback to use the provided url
 78	if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
 79		return "https://" + baseURL
 80	}
 81	return baseURL
 82}
 83
 84// WithAPIKey sets the API key for the Azure provider.
 85func WithAPIKey(apiKey string) Option {
 86	return func(o *options) {
 87		o.apiKey = apiKey
 88	}
 89}
 90
 91// WithHeaders sets the headers for the Azure provider.
 92func WithHeaders(headers map[string]string) Option {
 93	return func(o *options) {
 94		o.openaiOptions = append(o.openaiOptions, openai.WithHeaders(headers))
 95	}
 96}
 97
 98// WithAPIVersion sets the API version for the Azure provider.
 99func WithAPIVersion(version string) Option {
100	return func(o *options) {
101		o.apiVersion = version
102	}
103}
104
105// WithHTTPClient sets the HTTP client for the Azure provider.
106func WithHTTPClient(client option.HTTPClient) Option {
107	return func(o *options) {
108		o.openaiOptions = append(o.openaiOptions, openai.WithHTTPClient(client))
109	}
110}
111
112// WithUseResponsesAPI configures the provider to use the responses API for models that support it.
113func WithUseResponsesAPI() Option {
114	return func(o *options) {
115		o.openaiOptions = append(o.openaiOptions, openai.WithUseResponsesAPI())
116	}
117}