1 line
10 KiB
JSON
1 line
10 KiB
JSON
{"repo": "avicuna/ai-council", "files": {"internal/provider/openai.go": {"6b8005976a0d4af7c49b8c945552291e191c4143": {"t": 1785745627.344532, "n": 4765, "h": "d610454eba460e44", "c": "package provider\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\n// OpenAICompatProvider implements Provider for OpenAI-compatible APIs.\n// This includes GPT models, o3/o4 reasoning models, DeepSeek, and Grok.\ntype OpenAICompatProvider struct {\n\tname string\n\tclient openai.Client\n\tmodel string\n\tisReasoning bool\n}\n\n// NewOpenAICompatProvider creates a new OpenAI-compatible provider.\n// For reasoning models (o3, o4, DeepSeek R1), set isReasoning=true to:\n// - Convert system messages to user prefix\n// - Omit temperature parameter\n// - Use max_completion_tokens instead of max_tokens\nfunc NewOpenAICompatProvider(name, baseURL, apiKey, model string, isReasoning bool) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\treturn nil, fmt.Errorf(\"%s: API key is required\", name)\n\t}\n\n\topts := []option.RequestOption{\n\t\toption.WithAPIKey(apiKey),\n\t}\n\n\tif baseURL != \"\" {\n\t\topts = append(opts, option.WithBaseURL(baseURL))\n\t}\n\n\tclient := openai.NewClient(opts...)\n\n\treturn &OpenAICompatProvider{\n\t\tname: name,\n\t\tclient: client,\n\t\tmodel: model,\n\t\tisReasoning: isReasoning,\n\t}, nil\n}\n\n// NewGPTProvider creates a provider for OpenAI GPT models.\nfunc NewGPTProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"gpt\", \"\", apiKey, model, false)\n}\n\n// NewO3Provider creates a provider for OpenAI o3/o4 reasoning models.\nfunc NewO3Provider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"o3\", \"\", apiKey, model, true)\n}\n\n// NewDeepSeekProvider creates a provider for DeepSeek models.\nfunc NewDeepSeekProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"DEEPSEEK_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"deepseek\", \"https://api.deepseek.com/v1\", apiKey, model, true)\n}\n\n// NewGrokProvider creates a provider for X.AI Grok models.\nfunc NewGrokProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"XAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"grok\", \"https://api.x.ai/v1\", apiKey, model, false)\n}\n\n// Name returns the provider name.\nfunc (p *OpenAICompatProvider) Name() string {\n\treturn p.name\n}\n\n// Available checks if the provider is available.\n// Since the provider is created with a valid client in the constructor,\n// this always returns true.\nfunc (p *OpenAICompatProvider) Available() bool {\n\treturn true\n}\n\n// Query sends a request to the OpenAI-compatible API.\nfunc (p *OpenAICompatProvider) Query(ctx context.Context, req *Request) (*Response, error) {\n\tmessages := p.buildMessages(req)\n\n\tparams := openai.ChatCompletionNewParams{\n\t\tModel: p.model, // ChatModel is just a string alias\n\t\tMessages: messages,\n\t}\n\n\t// Reasoning models use max_completion_tokens and omit temperature\n\tif p.isReasoning {\n\t\tif req.MaxTokens > 0 {\n\t\t\tparams.MaxCompletionTokens = openai.Int(int64(req.MaxTokens))\n\t\t}\n\t} else {\n\t\t// Standard models use max_tokens and temperature\n\t\tif req.MaxTokens > 0 {\n\t\t\tparams.MaxTokens = openai.Int(int64(req.MaxTokens))\n\t\t}\n\t\tif req.Temperature != nil {\n\t\t\tparams.Temperature = openai.Float(*req.Temperature)\n\t\t}\n\t}\n\n\tcompletion, err := p.client.Chat.Completions.New(ctx, params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s query failed: %w\", p.name, err)\n\t}\n\n\tif len(completion.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"%s returned no choices\", p.name)\n\t}\n\n\treturn &Response{\n\t\tContent: completion.Choices[0].Message.Content,\n\t\tModel: completion.Model,\n\t\tName: p.name,\n\t\tInputTokens: int(completion.Usage.PromptTokens),\n\t\tOutputTokens: int(completion.Usage.CompletionTokens),\n\t}, nil\n}\n\n// buildMessages constructs the messages array for the API request.\n// For reasoning models, system messages are converted to a user message prefix.\nfunc (p *OpenAICompatProvider) buildMessages(req *Request) []openai.ChatCompletionMessageParamUnion {\n\tif p.isReasoning {\n\t\t// Reasoning models: combine system and user prompts into a single user message\n\t\tuserContent := req.UserPrompt\n\t\tif req.SystemPrompt != \"\" {\n\t\t\tuserContent = fmt.Sprintf(\"[System instructions]\\n%s\\n\\n%s\", req.SystemPrompt, req.UserPrompt)\n\t\t}\n\t\treturn []openai.ChatCompletionMessageParamUnion{\n\t\t\topenai.UserMessage(userContent),\n\t\t}\n\t}\n\n\t// Standard models: separate system and user messages\n\tmessages := make([]openai.ChatCompletionMessageParamUnion, 0, 2)\n\tif req.SystemPrompt != \"\" {\n\t\tmessages = append(messages, openai.SystemMessage(req.SystemPrompt))\n\t}\n\tmessages = append(messages, openai.UserMessage(req.UserPrompt))\n\treturn messages\n}\n"}, "8645bd0eb84d3ab503c33d75ac8185485d136475": {"t": 1785755016.6104658, "n": 4765, "h": "d610454eba460e44", "c": "package provider\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\n// OpenAICompatProvider implements Provider for OpenAI-compatible APIs.\n// This includes GPT models, o3/o4 reasoning models, DeepSeek, and Grok.\ntype OpenAICompatProvider struct {\n\tname string\n\tclient openai.Client\n\tmodel string\n\tisReasoning bool\n}\n\n// NewOpenAICompatProvider creates a new OpenAI-compatible provider.\n// For reasoning models (o3, o4, DeepSeek R1), set isReasoning=true to:\n// - Convert system messages to user prefix\n// - Omit temperature parameter\n// - Use max_completion_tokens instead of max_tokens\nfunc NewOpenAICompatProvider(name, baseURL, apiKey, model string, isReasoning bool) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\treturn nil, fmt.Errorf(\"%s: API key is required\", name)\n\t}\n\n\topts := []option.RequestOption{\n\t\toption.WithAPIKey(apiKey),\n\t}\n\n\tif baseURL != \"\" {\n\t\topts = append(opts, option.WithBaseURL(baseURL))\n\t}\n\n\tclient := openai.NewClient(opts...)\n\n\treturn &OpenAICompatProvider{\n\t\tname: name,\n\t\tclient: client,\n\t\tmodel: model,\n\t\tisReasoning: isReasoning,\n\t}, nil\n}\n\n// NewGPTProvider creates a provider for OpenAI GPT models.\nfunc NewGPTProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"gpt\", \"\", apiKey, model, false)\n}\n\n// NewO3Provider creates a provider for OpenAI o3/o4 reasoning models.\nfunc NewO3Provider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"o3\", \"\", apiKey, model, true)\n}\n\n// NewDeepSeekProvider creates a provider for DeepSeek models.\nfunc NewDeepSeekProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"DEEPSEEK_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"deepseek\", \"https://api.deepseek.com/v1\", apiKey, model, true)\n}\n\n// NewGrokProvider creates a provider for X.AI Grok models.\nfunc NewGrokProvider(apiKey, model string) (*OpenAICompatProvider, error) {\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"XAI_API_KEY\")\n\t}\n\treturn NewOpenAICompatProvider(\"grok\", \"https://api.x.ai/v1\", apiKey, model, false)\n}\n\n// Name returns the provider name.\nfunc (p *OpenAICompatProvider) Name() string {\n\treturn p.name\n}\n\n// Available checks if the provider is available.\n// Since the provider is created with a valid client in the constructor,\n// this always returns true.\nfunc (p *OpenAICompatProvider) Available() bool {\n\treturn true\n}\n\n// Query sends a request to the OpenAI-compatible API.\nfunc (p *OpenAICompatProvider) Query(ctx context.Context, req *Request) (*Response, error) {\n\tmessages := p.buildMessages(req)\n\n\tparams := openai.ChatCompletionNewParams{\n\t\tModel: p.model, // ChatModel is just a string alias\n\t\tMessages: messages,\n\t}\n\n\t// Reasoning models use max_completion_tokens and omit temperature\n\tif p.isReasoning {\n\t\tif req.MaxTokens > 0 {\n\t\t\tparams.MaxCompletionTokens = openai.Int(int64(req.MaxTokens))\n\t\t}\n\t} else {\n\t\t// Standard models use max_tokens and temperature\n\t\tif req.MaxTokens > 0 {\n\t\t\tparams.MaxTokens = openai.Int(int64(req.MaxTokens))\n\t\t}\n\t\tif req.Temperature != nil {\n\t\t\tparams.Temperature = openai.Float(*req.Temperature)\n\t\t}\n\t}\n\n\tcompletion, err := p.client.Chat.Completions.New(ctx, params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s query failed: %w\", p.name, err)\n\t}\n\n\tif len(completion.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"%s returned no choices\", p.name)\n\t}\n\n\treturn &Response{\n\t\tContent: completion.Choices[0].Message.Content,\n\t\tModel: completion.Model,\n\t\tName: p.name,\n\t\tInputTokens: int(completion.Usage.PromptTokens),\n\t\tOutputTokens: int(completion.Usage.CompletionTokens),\n\t}, nil\n}\n\n// buildMessages constructs the messages array for the API request.\n// For reasoning models, system messages are converted to a user message prefix.\nfunc (p *OpenAICompatProvider) buildMessages(req *Request) []openai.ChatCompletionMessageParamUnion {\n\tif p.isReasoning {\n\t\t// Reasoning models: combine system and user prompts into a single user message\n\t\tuserContent := req.UserPrompt\n\t\tif req.SystemPrompt != \"\" {\n\t\t\tuserContent = fmt.Sprintf(\"[System instructions]\\n%s\\n\\n%s\", req.SystemPrompt, req.UserPrompt)\n\t\t}\n\t\treturn []openai.ChatCompletionMessageParamUnion{\n\t\t\topenai.UserMessage(userContent),\n\t\t}\n\t}\n\n\t// Standard models: separate system and user messages\n\tmessages := make([]openai.ChatCompletionMessageParamUnion, 0, 2)\n\tif req.SystemPrompt != \"\" {\n\t\tmessages = append(messages, openai.SystemMessage(req.SystemPrompt))\n\t}\n\tmessages = append(messages, openai.UserMessage(req.UserPrompt))\n\treturn messages\n}\n"}}}} |