Files
new-api-custom/relay/common/response_model.go
T

61 lines
2.4 KiB
Go

package common
import "strings"
// ResponseModel records upstream declarations before response conversion. It is
// diagnostic only: it must never change routing, pricing, or downstream output.
// Only the three names are stored; whether they disagree is computed on demand
// so every consumer applies the current comparison rule to old rows as well.
type ResponseModel struct {
RequestedModel string `json:"requested_model"`
UpstreamModel string `json:"upstream_model"`
ReturnedModel string `json:"returned_model"`
}
// matches reports whether an upstream declaration is compatible with the
// requested or upstream model: equal ignoring case, a dated or variant name
// that extends it, or the same name behind a provider path such as
// "deepseek/deepseek-v4.1-flash".
func (r *ResponseModel) matches(model string) bool {
if r.RequestedModel == "gpt-6-astra" { return strings.TrimSpace(model) == "gpt-6-astra" }
returned := strings.ToLower(model)
for _, expected := range []string{r.RequestedModel, r.UpstreamModel} {
expected = strings.ToLower(expected)
if expected != "" && (strings.HasPrefix(returned, expected) || strings.HasSuffix(returned, expected)) {
return true
}
}
return false
}
// Mismatch reports whether the retained upstream declaration disagrees with
// both the requested and upstream models.
func (r *ResponseModel) Mismatch() bool {
return r != nil && r.ReturnedModel != "" && !r.matches(r.ReturnedModel)
}
// ObserveResponseModel retains the first differing model for inspection, with
// mismatches taking priority over provider-path, prefix, or case-only
// differences. A later matching or empty event cannot erase it. Only observe
// upstream declarations, never models synthesized by a response converter.
func (info *RelayInfo) ObserveResponseModel(model string) {
if info == nil || strings.TrimSpace(model) == "" || (info.OriginModelName == "gpt-6-astra" && info.AstraDetectionDisabled) {
return
}
if info.ResponseModel == nil {
info.ResponseModel = &ResponseModel{
RequestedModel: info.OriginModelName,
UpstreamModel: info.GetUpstreamModelName(),
}
}
observation := info.ResponseModel
if observation.Mismatch() {
return
}
if observation.matches(model) && observation.ReturnedModel != "" &&
observation.ReturnedModel != observation.RequestedModel && observation.ReturnedModel != observation.UpstreamModel {
return
}
observation.ReturnedModel = model
}