Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
116 lines
4.3 KiB
Go
116 lines
4.3 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/QuantumNous/new-api/model"
|
|
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type RequestDetails struct {
|
|
SessionID string `json:"session_id,omitempty"`
|
|
SessionSource string `json:"session_source"`
|
|
CorrelationKey string `json:"correlation_key,omitempty"`
|
|
RequestedModel string `json:"requested_model,omitempty"`
|
|
UpstreamModel string `json:"upstream_model,omitempty"`
|
|
ReturnedModel string `json:"returned_model,omitempty"`
|
|
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
|
Status int `json:"status"`
|
|
ModelGuard *relaycommon.ResponseModelGuard `json:"model_guard,omitempty"`
|
|
}
|
|
|
|
func appendRequestDetails(c *gin.Context, info *relaycommon.RelayInfo, other *model.LogOther) {
|
|
d := RequestDetails{SessionSource: "none", RequestedModel: c.GetString("original_model"), Status: c.Writer.Status()}
|
|
if status, ok := other.Snapshot()["status_code"].(int); ok {
|
|
d.Status = status
|
|
}
|
|
if c.Request != nil {
|
|
for _, h := range []struct{ name, source string }{{"session_id", "session"}, {"Session-Id", "session"}} {
|
|
if value := strings.TrimSpace(c.GetHeader(h.name)); value != "" {
|
|
d.SessionID, d.SessionSource = value, h.source
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if d.SessionID == "" {
|
|
if value := strings.TrimSpace(c.GetString("request_session_id")); value != "" {
|
|
d.SessionID = value
|
|
d.SessionSource = "session_body"
|
|
}
|
|
}
|
|
if d.SessionID == "" && c.Request != nil {
|
|
for _, key := range []string{"thread_id", "Thread-Id"} {
|
|
if value := strings.TrimSpace(c.GetHeader(key)); value != "" {
|
|
d.SessionID = value
|
|
d.SessionSource = "thread"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if d.SessionID == "" {
|
|
if value := strings.TrimSpace(c.GetString("request_thread_id")); value != "" {
|
|
d.SessionID = value
|
|
d.SessionSource = "thread_body"
|
|
}
|
|
}
|
|
if d.SessionID == "" {
|
|
if meta, ok := getChannelAffinityMeta(c); ok && meta.KeyFingerprint != "" {
|
|
d.SessionID = meta.KeyFingerprint
|
|
d.SessionSource = "affinity"
|
|
}
|
|
}
|
|
if d.SessionID != "" {
|
|
d.CorrelationKey = fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d\x00%s\x00%s", c.GetInt("id"), d.SessionSource, d.SessionID))))
|
|
// Hash the complete identifier before limiting its display size.
|
|
if len(d.SessionID) > 256 {
|
|
d.SessionID = string([]rune(d.SessionID)[:min(256, len([]rune(d.SessionID)))]) + "…"
|
|
}
|
|
}
|
|
c.Set("log_correlation_key", d.CorrelationKey)
|
|
if info != nil {
|
|
d.ModelGuard = info.ResponseGuard
|
|
d.RequestedModel = info.OriginModelName
|
|
d.UpstreamModel = info.GetUpstreamModelName()
|
|
d.ReasoningEffort = info.ReasoningEffort
|
|
if info.ResponseModel != nil {
|
|
d.ReturnedModel = info.ResponseModel.ReturnedModel
|
|
}
|
|
}
|
|
other.SetAdmin("request_details", d)
|
|
}
|
|
|
|
func recordSelection(c *gin.Context, group string, trace *model.SelectionTrace, reason string) {
|
|
state := RequestPolicy(c)
|
|
remaining := max(0, 100-state.candidateCount)
|
|
items := trace.Candidates[:min(remaining, len(trace.Candidates))]
|
|
state.candidateCount += len(items)
|
|
channelID := trace.SelectedID
|
|
for _, item := range trace.Candidates {
|
|
if item.Reason == "selected" || item.Reason == "affinity_hit" || item.Reason == "pinned_channel" {
|
|
channelID = item.ChannelID
|
|
break
|
|
}
|
|
}
|
|
state.AddEvent(PolicyEvent{Attempt: state.Attempts + 1, ChannelID: channelID, Group: group, Candidates: items, CandidateTotal: trace.Total, Decision: PolicyDecision{Action: "selection", Reason: reason, Source: "routing"}})
|
|
}
|
|
|
|
func selectWithTrace(param *RetryParam, group string, retry int) (*model.Channel, error) {
|
|
trace := &model.SelectionTrace{}
|
|
channel, err := model.GetRandomSatisfiedChannel(group, param.ModelName, retry, GetChannelConstraints(param.Ctx).Filters, trace)
|
|
reason := "priority_weight_selection"
|
|
if channel == nil {
|
|
reason = "no_available_channel"
|
|
}
|
|
recordSelection(param.Ctx, group, trace, reason)
|
|
return channel, err
|
|
}
|
|
|
|
func recordDirectSelection(c *gin.Context, ch *model.Channel, id int, group, modelName, reason string) {
|
|
trace := &model.SelectionTrace{}
|
|
trace.Add(ch, id, group, modelName, reason)
|
|
recordSelection(c, group, trace, reason)
|
|
}
|