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] }