183 lines
6.0 KiB
Go
183 lines
6.0 KiB
Go
package call
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
type LlmCallSet struct {
|
|
ApiKey string
|
|
BaseUrl string
|
|
ModelName string
|
|
}
|
|
|
|
type CallLLM struct {
|
|
ModelName string
|
|
Client *openai.Client
|
|
ApiKey string
|
|
}
|
|
|
|
func NewCallLLM(LlmCallSet *LlmCallSet) *CallLLM {
|
|
// Step 1: Record initial state with detailed debugging
|
|
log.Printf("NewCallLLM Step 1: LlmCallSet addr=%p, LlmCallSet.ApiKey='%s' (len=%d), prefix=%s",
|
|
LlmCallSet, LlmCallSet.ApiKey, len(LlmCallSet.ApiKey), prefix(LlmCallSet.ApiKey))
|
|
|
|
// Create a new string to force a copy (not just a reference)
|
|
apiKeySnapshot := string([]byte(LlmCallSet.ApiKey))
|
|
log.Printf("NewCallLLM Step 1b: apiKeySnapshot created, value='%s', prefix=%s",
|
|
apiKeySnapshot, prefix(apiKeySnapshot))
|
|
|
|
clientConfig := openai.DefaultConfig(apiKeySnapshot)
|
|
log.Printf("NewCallLLM Step 2: After DefaultConfig, LlmCallSet.ApiKey prefix=%s, snapshot prefix=%s",
|
|
prefix(LlmCallSet.ApiKey), prefix(apiKeySnapshot))
|
|
|
|
clientConfig.BaseURL = LlmCallSet.BaseUrl
|
|
transport := &http.Transport{}
|
|
baseSnapshot := string([]byte(LlmCallSet.BaseUrl))
|
|
httpClient := &http.Client{Transport: &loggingRoundTripper{rt: transport, apiKey: apiKeySnapshot, base: baseSnapshot}}
|
|
log.Printf("NewCallLLM Step 3: Created loggingRoundTripper with apiKey prefix=%s",
|
|
prefix(apiKeySnapshot))
|
|
|
|
clientConfig.HTTPClient = httpClient
|
|
client := openai.NewClientWithConfig(clientConfig)
|
|
log.Printf("NewCallLLM Step 4: After NewClientWithConfig, snapshot prefix=%s",
|
|
prefix(apiKeySnapshot))
|
|
|
|
log.Printf("NewCallLLM: created client=%p baseURL=%s apiKeyPrefix=%s",
|
|
client, LlmCallSet.BaseUrl, prefix(apiKeySnapshot))
|
|
return &CallLLM{
|
|
Client: client,
|
|
ModelName: LlmCallSet.ModelName,
|
|
ApiKey: apiKeySnapshot,
|
|
}
|
|
}
|
|
|
|
// loggingRoundTripper prints Authorization header prefix for outgoing requests
|
|
// and ensures the correct Authorization header is set (defense against middleware mutations)
|
|
type loggingRoundTripper struct {
|
|
rt http.RoundTripper
|
|
apiKey string // store the expected API key to enforce correct header
|
|
base string // base URL snapshot to fix missing scheme/host
|
|
}
|
|
|
|
func (l *loggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
// Log the URL details for debugging
|
|
if req != nil && req.URL != nil {
|
|
log.Printf("AuthRoundTripper: request URL before enforcement: scheme=%s host=%s path=%s full=%s", req.URL.Scheme, req.URL.Host, req.URL.Path, req.URL.String())
|
|
}
|
|
|
|
// If base is set and URL has missing scheme/host, resolve against base
|
|
if l.base != "" && req != nil && req.URL != nil {
|
|
if req.URL.Scheme == "" || req.URL.Host == "" {
|
|
if baseURL, err := url.Parse(l.base); err == nil {
|
|
newURL := baseURL.ResolveReference(req.URL)
|
|
log.Printf("AuthRoundTripper: fixing request URL: from=%s to=%s", req.URL.String(), newURL.String())
|
|
req.URL = newURL
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log incoming Authorization header (for debugging)
|
|
if v := req.Header.Get("Authorization"); v != "" {
|
|
parts := strings.SplitN(v, " ", 2)
|
|
if len(parts) == 2 {
|
|
tok := parts[1]
|
|
if len(tok) > 8 {
|
|
log.Printf("AuthRoundTripper: incoming Authorization token prefix: %s", tok[:8])
|
|
} else {
|
|
log.Printf("AuthRoundTripper: incoming Authorization token prefix: %s", tok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have an API key stored, ensure the Authorization header is set correctly
|
|
// This defends against middleware/proxies that may have mutated the header
|
|
if l.apiKey != "" {
|
|
expectedAuth := "Bearer " + l.apiKey
|
|
req.Header.Set("Authorization", expectedAuth)
|
|
log.Printf("AuthRoundTripper: enforced Authorization token prefix: %s", prefix(l.apiKey))
|
|
}
|
|
|
|
// Log outgoing Authorization header and URL details
|
|
if req != nil && req.URL != nil {
|
|
log.Printf("AuthRoundTripper: request URL after enforcement: scheme=%s host=%s path=%s full=%s", req.URL.Scheme, req.URL.Host, req.URL.Path, req.URL.String())
|
|
}
|
|
if v := req.Header.Get("Authorization"); v != "" {
|
|
parts := strings.SplitN(v, " ", 2)
|
|
if len(parts) == 2 {
|
|
tok := parts[1]
|
|
if len(tok) > 8 {
|
|
log.Printf("AuthRoundTripper: outgoing Authorization token prefix: %s", tok[:8])
|
|
} else {
|
|
log.Printf("AuthRoundTripper: outgoing Authorization token prefix: %s", tok)
|
|
}
|
|
}
|
|
}
|
|
return l.rt.RoundTrip(req)
|
|
}
|
|
|
|
func (s *CallLLM) Do(ctx context.Context, RequestOption openai.ChatCompletionRequest, modelName ...string) (string, openai.Usage, error) {
|
|
var useAges openai.Usage
|
|
if modelName == nil {
|
|
modelName = append(modelName, s.ModelName)
|
|
}
|
|
if modelName[0] == "" {
|
|
return "", useAges, fmt.Errorf("model name is nil")
|
|
}
|
|
|
|
RequestOption.Model = modelName[0]
|
|
// Debug: log api key prefix and client pointer before request to detect mutation/race
|
|
log.Printf("CallLLM.Do: client=%p model=%s apiKeyPrefix=%s", s.Client, RequestOption.Model, prefix(s.ApiKey))
|
|
resp, err := s.Client.CreateChatCompletion(ctx, RequestOption)
|
|
if err != nil {
|
|
// Log error and current stored apiKey prefix for diagnosis
|
|
log.Printf("CallLLM.Do: CreateChatCompletion error: %v; storedApiKeyPrefix=%s", err, prefix(s.ApiKey))
|
|
return "", useAges, err
|
|
}
|
|
|
|
if len(resp.Choices) == 0 {
|
|
return "", useAges, fmt.Errorf("模型未返回任何内容")
|
|
}
|
|
content := resp.Choices[0].Message.Content
|
|
//content := test.Res
|
|
return content, resp.Usage, nil
|
|
}
|
|
|
|
func (s *CallLLM) DoWithAll(ctx context.Context, RequestOption openai.ChatCompletionRequest, modelName ...string) (openai.ChatCompletionResponse, error) {
|
|
if modelName == nil {
|
|
modelName = append(modelName, s.ModelName)
|
|
}
|
|
var resp openai.ChatCompletionResponse
|
|
if modelName[0] == "" {
|
|
return resp, fmt.Errorf("model name is nil")
|
|
}
|
|
RequestOption.Model = modelName[0]
|
|
resp, err := s.Client.CreateChatCompletion(ctx, RequestOption)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
if len(resp.Choices) == 0 {
|
|
return resp, fmt.Errorf("模型未返回任何内容")
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// prefix returns a short prefix of the key for safe logging
|
|
func prefix(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
if len(s) <= 8 {
|
|
return s
|
|
}
|
|
return s[:8]
|
|
}
|