feat: add runner failover rules and cache affinity

This commit is contained in:
2026-06-28 20:50:23 +08:00
parent 41bb3a6525
commit 0e675e259c
31 changed files with 2939 additions and 470 deletions
+58 -7
View File
@@ -259,6 +259,9 @@ func NormalizeChatCompletionResult(result map[string]any) map[string]any {
if result == nil {
return nil
}
if usage, ok := result["usage"].(map[string]any); ok && len(usage) > 0 {
result["usage"] = NormalizeChatCompletionUsage(usage)
}
choices, _ := result["choices"].([]any)
for _, rawChoice := range choices {
choice, _ := rawChoice.(map[string]any)
@@ -280,6 +283,9 @@ func NormalizeChatCompletionStreamEvent(event map[string]any) map[string]any {
if event == nil {
return nil
}
if usage, ok := event["usage"].(map[string]any); ok && len(usage) > 0 {
event["usage"] = NormalizeChatCompletionUsage(usage)
}
choices, _ := event["choices"].([]any)
for _, rawChoice := range choices {
choice, _ := rawChoice.(map[string]any)
@@ -295,6 +301,33 @@ func NormalizeChatCompletionStreamEvent(event map[string]any) map[string]any {
return event
}
func NormalizeChatCompletionUsage(usage map[string]any) map[string]any {
if len(usage) == 0 {
return usage
}
normalized := usageFromOpenAIUsage(usage)
out := cloneMapAny(usage)
if normalized.InputTokens > 0 && intFromAny(out["prompt_tokens"]) == 0 {
out["prompt_tokens"] = normalized.InputTokens
}
if normalized.OutputTokens > 0 && intFromAny(out["completion_tokens"]) == 0 {
out["completion_tokens"] = normalized.OutputTokens
}
if normalized.TotalTokens > 0 && intFromAny(out["total_tokens"]) == 0 {
out["total_tokens"] = normalized.TotalTokens
}
if normalized.CachedInputTokens > 0 {
promptDetails, _ := firstPresent(out["prompt_tokens_details"], out["promptTokensDetails"]).(map[string]any)
normalizedPromptDetails := cloneMapAny(promptDetails)
if normalizedPromptDetails == nil {
normalizedPromptDetails = map[string]any{}
}
normalizedPromptDetails["cached_tokens"] = normalized.CachedInputTokens
out["prompt_tokens_details"] = normalizedPromptDetails
}
return out
}
func normalizeToolCallsContainer(container map[string]any, stream bool) {
if container == nil {
return
@@ -877,27 +910,36 @@ func sortedStreamToolCalls(toolCalls map[int]map[string]any) []any {
func usageFromOpenAI(result map[string]any) Usage {
usage, _ := result["usage"].(map[string]any)
return usageFromOpenAIUsage(usage)
}
func usageFromOpenAIUsage(usage map[string]any) Usage {
input := intFromAny(firstPresent(usage["prompt_tokens"], usage["input_tokens"]))
output := intFromAny(firstPresent(usage["completion_tokens"], usage["output_tokens"]))
total := intFromAny(usage["total_tokens"])
if total == 0 {
total = input + output
}
cachedInput := cachedInputTokensFromOpenAIUsage(usage)
cachedInput, cachedInputKnown := cachedInputTokensValueFromOpenAIUsage(usage)
if cachedInput > input && input > 0 {
cachedInput = input
}
return Usage{InputTokens: input, OutputTokens: output, CachedInputTokens: cachedInput, TotalTokens: total}
return Usage{InputTokens: input, OutputTokens: output, CachedInputTokens: cachedInput, CachedInputTokensKnown: cachedInputKnown, TotalTokens: total}
}
func cachedInputTokensFromOpenAIUsage(usage map[string]any) int {
cachedInput, _ := cachedInputTokensValueFromOpenAIUsage(usage)
return cachedInput
}
func cachedInputTokensValueFromOpenAIUsage(usage map[string]any) (int, bool) {
if len(usage) == 0 {
return 0
return 0, false
}
promptDetails, _ := firstPresent(usage["prompt_tokens_details"], usage["promptTokensDetails"]).(map[string]any)
inputDetails, _ := firstPresent(usage["input_tokens_details"], usage["inputTokensDetails"]).(map[string]any)
usageMetadata, _ := usage["usageMetadata"].(map[string]any)
return intFromAny(firstPresent(
value, ok := firstPresentValue(
promptDetails["cached_tokens"],
promptDetails["cachedTokens"],
promptDetails["cache_read_input_tokens"],
@@ -918,7 +960,11 @@ func cachedInputTokensFromOpenAIUsage(usage map[string]any) int {
usage["cachedContentTokenCount"],
usageMetadata["cached_content_token_count"],
usageMetadata["cachedContentTokenCount"],
))
)
if !ok {
return 0, false
}
return intFromAny(value), true
}
func requestIDFromHTTPResponse(resp *http.Response) string {
@@ -979,12 +1025,17 @@ func firstNonEmptyString(values ...any) string {
}
func firstPresent(values ...any) any {
value, _ := firstPresentValue(values...)
return value
}
func firstPresentValue(values ...any) (any, bool) {
for _, value := range values {
if value != nil {
return value
return value, true
}
}
return nil
return nil, false
}
func errorMessage(raw []byte, fallback string) string {