Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions constant/context_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,23 @@ const (
ContextKeyTokenCrossGroupRetry ContextKey = "token_cross_group_retry"

/* channel related keys */
ContextKeyChannelId ContextKey = "channel_id"
ContextKeyChannelName ContextKey = "channel_name"
ContextKeyChannelCreateTime ContextKey = "channel_create_time"
ContextKeyChannelBaseUrl ContextKey = "base_url"
ContextKeyChannelType ContextKey = "channel_type"
ContextKeyChannelSetting ContextKey = "channel_setting"
ContextKeyChannelOtherSetting ContextKey = "channel_other_setting"
ContextKeyChannelParamOverride ContextKey = "param_override"
ContextKeyChannelHeaderOverride ContextKey = "header_override"
ContextKeyChannelOrganization ContextKey = "channel_organization"
ContextKeyChannelAutoBan ContextKey = "auto_ban"
ContextKeyChannelModelMapping ContextKey = "model_mapping"
ContextKeyChannelStatusCodeMapping ContextKey = "status_code_mapping"
ContextKeyChannelIsMultiKey ContextKey = "channel_is_multi_key"
ContextKeyChannelMultiKeyIndex ContextKey = "channel_multi_key_index"
ContextKeyChannelKey ContextKey = "channel_key"
ContextKeyChannelId ContextKey = "channel_id"
ContextKeyChannelName ContextKey = "channel_name"
ContextKeyChannelCreateTime ContextKey = "channel_create_time"
ContextKeyChannelBaseUrl ContextKey = "base_url"
ContextKeyChannelType ContextKey = "channel_type"
ContextKeyChannelSetting ContextKey = "channel_setting"
ContextKeyChannelOtherSetting ContextKey = "channel_other_setting"
ContextKeyChannelParamOverride ContextKey = "param_override"
ContextKeyChannelParamOverrideContext ContextKey = "param_override_context"
ContextKeyChannelHeaderOverride ContextKey = "header_override"
ContextKeyChannelOrganization ContextKey = "channel_organization"
ContextKeyChannelAutoBan ContextKey = "auto_ban"
ContextKeyChannelModelMapping ContextKey = "model_mapping"
ContextKeyChannelStatusCodeMapping ContextKey = "status_code_mapping"
ContextKeyChannelIsMultiKey ContextKey = "channel_is_multi_key"
ContextKeyChannelMultiKeyIndex ContextKey = "channel_multi_key_index"
ContextKeyChannelKey ContextKey = "channel_key"

ContextKeyAutoGroup ContextKey = "auto_group"
ContextKeyAutoGroupIndex ContextKey = "auto_group_index"
Expand Down
3 changes: 3 additions & 0 deletions relay/channel/codex/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo
}
// codex: store must be false
request.Store = json.RawMessage("false")
// codex backend rejects OpenAI Responses metadata even though some clients
// include it on compatibility paths.
request.Metadata = nil
// rm max_output_tokens
request.MaxOutputTokens = nil
request.Temperature = nil
Expand Down
21 changes: 21 additions & 0 deletions relay/chat_completions_via_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ import (
"github.com/gin-gonic/gin"
)

func ensureRuntimeHeaderOverride(info *relaycommon.RelayInfo, headerName string, value string) {
if info == nil {
return
}
headerName = strings.TrimSpace(strings.ToLower(headerName))
value = strings.TrimSpace(value)
if headerName == "" || value == "" {
return
}

current := relaycommon.GetEffectiveHeaderOverride(info)
if existing := strings.TrimSpace(common.Interface2String(current[headerName])); existing != "" {
return
}

current[headerName] = value
info.RuntimeHeadersOverride = current
info.UseRuntimeHeadersOverride = true
}

func applySystemPromptIfNeeded(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) {
if info == nil || request == nil {
return
Expand Down Expand Up @@ -96,6 +116,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad
if err != nil {
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
}
ensureRuntimeHeaderOverride(info, "session_id", overriddenChatReq.PromptCacheKey)
info.AppendRequestConversion(types.RelayFormatOpenAIResponses)

savedRelayMode := info.RelayMode
Expand Down
70 changes: 70 additions & 0 deletions relay/common/override.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,11 +1343,30 @@ func parseSyncTarget(spec string) (syncTarget, error) {
kind: "header",
key: key,
}, nil
case "context":
return syncTarget{
kind: "context",
key: key,
}, nil
default:
return syncTarget{}, fmt.Errorf("sync_fields target prefix is invalid: %s", raw)
}
}

func snapshotContextMap(context map[string]interface{}) map[string]interface{} {
if len(context) == 0 {
return map[string]interface{}{}
}
snapshot := make(map[string]interface{}, len(context))
for key, value := range context {
if key == paramOverrideContextAuditRecorder {
continue
}
snapshot[key] = value
}
return snapshot
}

func readSyncTargetValue(jsonStr string, context map[string]interface{}, target syncTarget) (interface{}, bool, error) {
switch target.kind {
case "json":
Expand All @@ -1366,6 +1385,22 @@ func readSyncTargetValue(jsonStr string, context map[string]interface{}, target
return nil, false, nil
}
return value, true, nil
case "context":
contextJSON, err := marshalContextJSON(snapshotContextMap(context))
if err != nil {
return nil, false, err
}
if contextJSON == "" {
return nil, false, nil
}
value := gjson.Get(contextJSON, target.key)
if !value.Exists() || value.Type == gjson.Null {
return nil, false, nil
}
if value.Type == gjson.String && strings.TrimSpace(value.String()) == "" {
return nil, false, nil
}
return value.Value(), true, nil
default:
return nil, false, fmt.Errorf("unsupported sync_fields target kind: %s", target.kind)
}
Expand All @@ -1385,6 +1420,33 @@ func writeSyncTargetValue(jsonStr string, context map[string]interface{}, target
return "", err
}
return jsonStr, nil
case "context":
contextJSON, err := marshalContextJSON(snapshotContextMap(context))
if err != nil {
return "", err
}
if contextJSON == "" {
contextJSON = "{}"
}
nextJSON, err := sjson.Set(contextJSON, target.key, value)
if err != nil {
return "", err
}
nextContext := make(map[string]interface{})
if err := common.UnmarshalJsonStr(nextJSON, &nextContext); err != nil {
return "", err
}
recorder, _ := context[paramOverrideContextAuditRecorder]
for key := range context {
delete(context, key)
}
for key, item := range nextContext {
context[key] = item
}
if recorder != nil {
context[paramOverrideContextAuditRecorder] = recorder
}
return jsonStr, nil
default:
return "", fmt.Errorf("unsupported sync_fields target kind: %s", target.kind)
}
Expand Down Expand Up @@ -2025,6 +2087,14 @@ func BuildParamOverrideContext(info *RelayInfo) map[string]interface{} {

headerOverrideSource := GetEffectiveHeaderOverride(info)
ctx[paramOverrideContextHeaderOverride] = sanitizeHeaderOverrideMap(headerOverrideSource)
if info.ChannelMeta != nil && len(info.ChannelMeta.ParamOverrideContext) > 0 {
for key, value := range info.ChannelMeta.ParamOverrideContext {
if _, exists := ctx[key]; exists {
continue
}
ctx[key] = value
}
}

ctx["retry_index"] = info.RetryIndex
ctx["is_retry"] = info.RetryIndex > 0
Expand Down
37 changes: 37 additions & 0 deletions relay/common/override_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,43 @@ func TestApplyParamOverrideSyncFieldsJSONToHeader(t *testing.T) {
}
}

func TestApplyParamOverrideSyncFieldsContextToJSONAndHeader(t *testing.T) {
input := []byte(`{"model":"gpt-4"}`)
override := map[string]interface{}{
"operations": []interface{}{
map[string]interface{}{
"mode": "sync_fields",
"from": "context:channel_affinity.key",
"to": "json:prompt_cache_key",
},
map[string]interface{}{
"mode": "sync_fields",
"from": "context:channel_affinity.key",
"to": "header:session_id",
},
},
}
ctx := map[string]interface{}{
"channel_affinity": map[string]interface{}{
"key": "affinity-session",
},
}

out, err := ApplyParamOverride(input, override, ctx)
if err != nil {
t.Fatalf("ApplyParamOverride returned error: %v", err)
}
assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"affinity-session"}`, string(out))

headers, ok := ctx["header_override"].(map[string]interface{})
if !ok {
t.Fatalf("expected header_override context map")
}
if headers["session_id"] != "affinity-session" {
t.Fatalf("expected session_id to be synced from context, got: %v", headers["session_id"])
}
}

func TestApplyParamOverrideSyncFieldsNoChangeWhenBothExist(t *testing.T) {
input := []byte(`{"model":"gpt-4","prompt_cache_key":"cache-body"}`)
override := map[string]interface{}{
Expand Down
2 changes: 2 additions & 0 deletions relay/common/relay_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type ChannelMeta struct {
Organization string
ChannelCreateTime int64
ParamOverride map[string]interface{}
ParamOverrideContext map[string]interface{}
HeadersOverride map[string]interface{}
ChannelSetting dto.ChannelSettings
ChannelOtherSettings dto.ChannelOtherSettings
Expand Down Expand Up @@ -190,6 +191,7 @@ func (info *RelayInfo) InitChannelMeta(c *gin.Context) {
Organization: c.GetString("channel_organization"),
ChannelCreateTime: c.GetInt64("channel_create_time"),
ParamOverride: paramOverride,
ParamOverrideContext: common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverrideContext),
HeadersOverride: headerOverride,
UpstreamModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
IsModelMapped: false,
Expand Down
Loading