openaicompat.go

  1// Package openaicompat provides an implementation of the fantasy AI SDK for OpenAI-compatible APIs.
  2package openaicompat
  3
  4import (
  5	"charm.land/fantasy"
  6	"charm.land/fantasy/providers/openai"
  7	"github.com/openai/openai-go/v2/option"
  8)
  9
 10type options struct {
 11	openaiOptions        []openai.Option
 12	languageModelOptions []openai.LanguageModelOption
 13	sdkOptions           []option.RequestOption
 14	objectMode           fantasy.ObjectMode
 15}
 16
 17const (
 18	// Name is the name of the OpenAI-compatible provider.
 19	Name = "openai-compat"
 20)
 21
 22// Option defines a function that configures OpenAI-compatible provider options.
 23type Option = func(*options)
 24
 25// New creates a new OpenAI-compatible provider with the given options.
 26func New(opts ...Option) (fantasy.Provider, error) {
 27	providerOptions := options{
 28		openaiOptions: []openai.Option{
 29			openai.WithName(Name),
 30		},
 31		languageModelOptions: []openai.LanguageModelOption{
 32			openai.WithLanguageModelPrepareCallFunc(PrepareCallFunc),
 33			openai.WithLanguageModelStreamExtraFunc(StreamExtraFunc),
 34			openai.WithLanguageModelExtraContentFunc(ExtraContentFunc),
 35			openai.WithLanguageModelToPromptFunc(ToPromptFunc),
 36		},
 37		objectMode: fantasy.ObjectModeTool, // Default to tool mode for openai-compat
 38	}
 39	for _, o := range opts {
 40		o(&providerOptions)
 41	}
 42
 43	// Handle object mode: convert unsupported modes to tool
 44	// OpenAI-compat endpoints don't support native JSON mode, so we use tool or text
 45	objectMode := providerOptions.objectMode
 46	if objectMode == fantasy.ObjectModeAuto || objectMode == fantasy.ObjectModeJSON {
 47		objectMode = fantasy.ObjectModeTool
 48	}
 49
 50	providerOptions.openaiOptions = append(
 51		providerOptions.openaiOptions,
 52		openai.WithSDKOptions(providerOptions.sdkOptions...),
 53		openai.WithLanguageModelOptions(providerOptions.languageModelOptions...),
 54		openai.WithObjectMode(objectMode),
 55	)
 56	return openai.New(providerOptions.openaiOptions...)
 57}
 58
 59// WithBaseURL sets the base URL for the OpenAI-compatible provider.
 60func WithBaseURL(url string) Option {
 61	return func(o *options) {
 62		o.openaiOptions = append(o.openaiOptions, openai.WithBaseURL(url))
 63	}
 64}
 65
 66// WithAPIKey sets the API key for the OpenAI-compatible provider.
 67func WithAPIKey(apiKey string) Option {
 68	return func(o *options) {
 69		o.openaiOptions = append(o.openaiOptions, openai.WithAPIKey(apiKey))
 70	}
 71}
 72
 73// WithName sets the name for the OpenAI-compatible provider.
 74func WithName(name string) Option {
 75	return func(o *options) {
 76		o.openaiOptions = append(o.openaiOptions, openai.WithName(name))
 77	}
 78}
 79
 80// WithHeaders sets the headers for the OpenAI-compatible provider.
 81func WithHeaders(headers map[string]string) Option {
 82	return func(o *options) {
 83		o.openaiOptions = append(o.openaiOptions, openai.WithHeaders(headers))
 84	}
 85}
 86
 87// WithHTTPClient sets the HTTP client for the OpenAI-compatible provider.
 88func WithHTTPClient(client option.HTTPClient) Option {
 89	return func(o *options) {
 90		o.openaiOptions = append(o.openaiOptions, openai.WithHTTPClient(client))
 91	}
 92}
 93
 94// WithSDKOptions sets the SDK options for the OpenAI-compatible provider.
 95func WithSDKOptions(opts ...option.RequestOption) Option {
 96	return func(o *options) {
 97		o.sdkOptions = append(o.sdkOptions, opts...)
 98	}
 99}
100
101// WithObjectMode sets the object generation mode for the OpenAI-compatible provider.
102// Supported modes: ObjectModeTool, ObjectModeText.
103// ObjectModeAuto and ObjectModeJSON are automatically converted to ObjectModeTool
104// since OpenAI-compatible endpoints typically don't support native JSON mode.
105func WithObjectMode(om fantasy.ObjectMode) Option {
106	return func(o *options) {
107		o.objectMode = om
108	}
109}