sdk_generate/internal/pkg/call/callLLM.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]
}