mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-11 00:27:53 +02:00
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package openai
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/bytedance/sonic"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// SummarizationRequestHeader marks chat/completion requests issued by Eino summarization
|
|
// middleware (via model.WithExtraHeader). The diagnostic transport logs empty-choices bodies
|
|
// only for these requests so main-agent traffic stays quiet.
|
|
const SummarizationRequestHeader = "X-CyberStrike-Summarization"
|
|
|
|
const summarizationDiagBodyMaxBytes = 8192
|
|
|
|
// AttachSummarizationDiagTransport wraps client.Transport to log raw API bodies when
|
|
// summarization receives HTTP 200 with an empty choices array.
|
|
func AttachSummarizationDiagTransport(client *http.Client, logger *zap.Logger) {
|
|
if client == nil || logger == nil {
|
|
return
|
|
}
|
|
base := client.Transport
|
|
if base == nil {
|
|
base = http.DefaultTransport
|
|
}
|
|
client.Transport = &summarizationDiagRoundTripper{base: base, logger: logger}
|
|
}
|
|
|
|
type summarizationDiagRoundTripper struct {
|
|
base http.RoundTripper
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (rt *summarizationDiagRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
resp, err := rt.base.RoundTrip(req)
|
|
if err != nil || resp == nil || resp.Body == nil {
|
|
return resp, err
|
|
}
|
|
if !isSummarizationRequest(req) || !strings.Contains(strings.ToLower(resp.Header.Get("Content-Type")), "json") {
|
|
return resp, err
|
|
}
|
|
|
|
body, readErr := io.ReadAll(resp.Body)
|
|
_ = resp.Body.Close()
|
|
if readErr != nil {
|
|
resp.Body = io.NopCloser(bytes.NewReader(nil))
|
|
return resp, err
|
|
}
|
|
resp.Body = io.NopCloser(bytes.NewReader(body))
|
|
resp.ContentLength = int64(len(body))
|
|
|
|
if rt.logger != nil && summarizationResponseEmptyChoices(body) {
|
|
rt.logger.Warn("eino summarization: API returned empty choices",
|
|
zap.Int("status", resp.StatusCode),
|
|
zap.Int("response_bytes", len(body)),
|
|
zap.String("raw_body", truncateForLog(string(body), summarizationDiagBodyMaxBytes)),
|
|
)
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
func isSummarizationRequest(req *http.Request) bool {
|
|
if req == nil {
|
|
return false
|
|
}
|
|
return strings.TrimSpace(req.Header.Get(SummarizationRequestHeader)) == "1"
|
|
}
|
|
|
|
func summarizationResponseEmptyChoices(body []byte) bool {
|
|
var parsed struct {
|
|
Choices []any `json:"choices"`
|
|
}
|
|
if err := sonic.Unmarshal(body, &parsed); err != nil {
|
|
return false
|
|
}
|
|
return len(parsed.Choices) == 0
|
|
}
|
|
|
|
func truncateForLog(s string, maxBytes int) string {
|
|
if maxBytes <= 0 || len(s) <= maxBytes {
|
|
return s
|
|
}
|
|
return s[:maxBytes] + "…(truncated)"
|
|
}
|