openai call
This commit is contained in:
parent
edc9d3d667
commit
1484b519d7
112
openai_client.go
112
openai_client.go
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
|
@ -90,12 +91,25 @@ func (llm *OpenAIClient) openAICompletion(ctx context.Context, prompt string, fo
|
|||
if apiURL == "" {
|
||||
apiURL = "https://api.openai.com/v1/chat/completions"
|
||||
}
|
||||
|
||||
isOpenAIStyle := strings.Contains(apiURL, "openrouter.ai") || strings.Contains(apiURL, "/v1/")
|
||||
|
||||
// Helper to stringify the expected JSON schema for instructions
|
||||
schemaDesc := func() string {
|
||||
b, _ := json.MarshalIndent(format, "", " ")
|
||||
return string(b)
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
|
||||
truncate := func(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "...<truncated>"
|
||||
}
|
||||
|
||||
buildBody := func() map[string]interface{} {
|
||||
if isOpenAIStyle {
|
||||
return map[string]interface{}{
|
||||
"model": llm.Model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "system", "content": "You are a strict JSON generator. ONLY output valid JSON matching this schema: " + schemaDesc() + " Do not add explanations."},
|
||||
|
|
@ -103,7 +117,30 @@ func (llm *OpenAIClient) openAICompletion(ctx context.Context, prompt string, fo
|
|||
},
|
||||
"response_format": map[string]interface{}{"type": "json_object"},
|
||||
}
|
||||
}
|
||||
// This should never be reached in OpenAI client but keeping for safety
|
||||
return map[string]interface{}{
|
||||
"model": llm.Model,
|
||||
"messages": []map[string]string{{"role": "user", "content": prompt}},
|
||||
"stream": false,
|
||||
"format": format,
|
||||
}
|
||||
}
|
||||
|
||||
body := buildBody()
|
||||
|
||||
// Enhanced logging similar to the unified client
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
bodySize := len(jsonBody)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_request",
|
||||
"api_url": apiURL,
|
||||
"model": llm.Model,
|
||||
"is_openai_style": isOpenAIStyle,
|
||||
"prompt_len": len(prompt),
|
||||
"body_size": bodySize,
|
||||
}).Info("[LLM] sending request")
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewBuffer(jsonBody))
|
||||
if llm.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+llm.APIKey)
|
||||
|
|
@ -114,16 +151,39 @@ func (llm *OpenAIClient) openAICompletion(ctx context.Context, prompt string, fo
|
|||
req.Header.Set("Referer", "https://github.com/")
|
||||
req.Header.Set("X-Title", "vetrag-app")
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
dur := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_response",
|
||||
"status": 0,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"error": err,
|
||||
}).Error("[LLM] request failed")
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_raw_response",
|
||||
"status": resp.StatusCode,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"raw_trunc": truncate(string(raw), 600),
|
||||
"raw_len": len(raw),
|
||||
}).Debug("[LLM] raw response body")
|
||||
|
||||
parseVariant := "unknown"
|
||||
|
||||
// Attempt OpenAI/OpenRouter style parse first
|
||||
var openAI struct {
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
|
|
@ -137,18 +197,66 @@ func (llm *OpenAIClient) openAICompletion(ctx context.Context, prompt string, fo
|
|||
}
|
||||
if err := json.Unmarshal(raw, &openAI); err == nil {
|
||||
if openAI.Error != nil || resp.StatusCode >= 400 {
|
||||
parseVariant = "openai"
|
||||
var msg string
|
||||
if openAI.Error != nil {
|
||||
msg = openAI.Error.Message
|
||||
} else {
|
||||
msg = string(raw)
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_response",
|
||||
"status": resp.StatusCode,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"parse_variant": parseVariant,
|
||||
"error": msg,
|
||||
}).Error("[LLM] provider error")
|
||||
return "", fmt.Errorf("provider error: %s", msg)
|
||||
}
|
||||
if len(openAI.Choices) > 0 && openAI.Choices[0].Message.Content != "" {
|
||||
return openAI.Choices[0].Message.Content, nil
|
||||
parseVariant = "openai"
|
||||
content := openAI.Choices[0].Message.Content
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_response",
|
||||
"status": resp.StatusCode,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"parse_variant": parseVariant,
|
||||
"content_len": len(content),
|
||||
"content_snip": truncate(content, 300),
|
||||
}).Info("[LLM] parsed response")
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
|
||||
// As a fallback, attempt Ollama format parse
|
||||
var ollama struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &ollama); err == nil && ollama.Message.Content != "" {
|
||||
parseVariant = "ollama"
|
||||
content := ollama.Message.Content
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_response",
|
||||
"status": resp.StatusCode,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"parse_variant": parseVariant,
|
||||
"content_len": len(content),
|
||||
"content_snip": truncate(content, 300),
|
||||
}).Info("[LLM] parsed response")
|
||||
return content, nil
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event": "llm_response",
|
||||
"status": resp.StatusCode,
|
||||
"latency_ms": dur.Milliseconds(),
|
||||
"parse_variant": parseVariant,
|
||||
"raw_snip": truncate(string(raw), 300),
|
||||
}).Error("[LLM] unrecognized response format")
|
||||
|
||||
return "", fmt.Errorf("unrecognized LLM response format: %.200s", string(raw))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue