169 lines
6.3 KiB
Go
169 lines
6.3 KiB
Go
package store
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/jackc/pgx/v5"
|
||
)
|
||
|
||
const runnerPolicyColumns = `
|
||
id::text, policy_key, name, COALESCE(description, ''), failover_policy,
|
||
hard_stop_policy, priority_demote_policy, metadata, status, created_at, updated_at`
|
||
|
||
type RunnerPolicyInput struct {
|
||
PolicyKey string `json:"policyKey"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
FailoverPolicy map[string]any `json:"failoverPolicy"`
|
||
HardStopPolicy map[string]any `json:"hardStopPolicy"`
|
||
PriorityDemotePolicy map[string]any `json:"priorityDemotePolicy"`
|
||
Metadata map[string]any `json:"metadata"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
type runnerPolicyScanner interface {
|
||
Scan(dest ...any) error
|
||
}
|
||
|
||
func (s *Store) GetActiveRunnerPolicy(ctx context.Context) (RunnerPolicy, error) {
|
||
item, err := scanRunnerPolicy(s.pool.QueryRow(ctx, `
|
||
SELECT `+runnerPolicyColumns+`
|
||
FROM gateway_runner_policies
|
||
ORDER BY CASE WHEN policy_key = 'default-runner-v1' THEN 0 ELSE 1 END,
|
||
CASE WHEN status = 'active' THEN 0 ELSE 1 END,
|
||
updated_at DESC
|
||
LIMIT 1`))
|
||
if err != nil {
|
||
if err == pgx.ErrNoRows || IsUndefinedDatabaseObject(err) {
|
||
return defaultRunnerPolicy(), nil
|
||
}
|
||
return RunnerPolicy{}, err
|
||
}
|
||
return item, nil
|
||
}
|
||
|
||
func (s *Store) UpsertDefaultRunnerPolicy(ctx context.Context, input RunnerPolicyInput) (RunnerPolicy, error) {
|
||
input = normalizeRunnerPolicyInput(input)
|
||
failoverPolicy, _ := json.Marshal(emptyObjectIfNil(input.FailoverPolicy))
|
||
hardStopPolicy, _ := json.Marshal(emptyObjectIfNil(input.HardStopPolicy))
|
||
priorityDemotePolicy, _ := json.Marshal(emptyObjectIfNil(input.PriorityDemotePolicy))
|
||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||
return scanRunnerPolicy(s.pool.QueryRow(ctx, `
|
||
INSERT INTO gateway_runner_policies (
|
||
policy_key, name, description, failover_policy, hard_stop_policy, priority_demote_policy, metadata, status
|
||
)
|
||
VALUES ($1, $2, NULLIF($3, ''), $4, $5, $6, $7, $8)
|
||
ON CONFLICT (policy_key) DO UPDATE
|
||
SET name = EXCLUDED.name,
|
||
description = EXCLUDED.description,
|
||
failover_policy = EXCLUDED.failover_policy,
|
||
hard_stop_policy = EXCLUDED.hard_stop_policy,
|
||
priority_demote_policy = EXCLUDED.priority_demote_policy,
|
||
metadata = EXCLUDED.metadata,
|
||
status = EXCLUDED.status,
|
||
updated_at = now()
|
||
RETURNING `+runnerPolicyColumns,
|
||
input.PolicyKey, input.Name, input.Description, failoverPolicy, hardStopPolicy, priorityDemotePolicy, metadata, input.Status,
|
||
))
|
||
}
|
||
|
||
func scanRunnerPolicy(scanner runnerPolicyScanner) (RunnerPolicy, error) {
|
||
var item RunnerPolicy
|
||
var failoverPolicy []byte
|
||
var hardStopPolicy []byte
|
||
var priorityDemotePolicy []byte
|
||
var metadata []byte
|
||
if err := scanner.Scan(
|
||
&item.ID,
|
||
&item.PolicyKey,
|
||
&item.Name,
|
||
&item.Description,
|
||
&failoverPolicy,
|
||
&hardStopPolicy,
|
||
&priorityDemotePolicy,
|
||
&metadata,
|
||
&item.Status,
|
||
&item.CreatedAt,
|
||
&item.UpdatedAt,
|
||
); err != nil {
|
||
return RunnerPolicy{}, err
|
||
}
|
||
item.FailoverPolicy = decodeObject(failoverPolicy)
|
||
item.HardStopPolicy = decodeObject(hardStopPolicy)
|
||
item.PriorityDemotePolicy = decodeObject(priorityDemotePolicy)
|
||
item.Metadata = decodeObject(metadata)
|
||
return item, nil
|
||
}
|
||
|
||
func normalizeRunnerPolicyInput(input RunnerPolicyInput) RunnerPolicyInput {
|
||
input.PolicyKey = strings.TrimSpace(input.PolicyKey)
|
||
if input.PolicyKey == "" {
|
||
input.PolicyKey = "default-runner-v1"
|
||
}
|
||
input.Name = strings.TrimSpace(input.Name)
|
||
if input.Name == "" {
|
||
input.Name = "默认全局调度策略"
|
||
}
|
||
input.Description = strings.TrimSpace(input.Description)
|
||
input.Status = strings.TrimSpace(input.Status)
|
||
if input.Status == "" {
|
||
input.Status = "active"
|
||
}
|
||
return input
|
||
}
|
||
|
||
func defaultRunnerPolicy() RunnerPolicy {
|
||
now := time.Now()
|
||
return RunnerPolicy{
|
||
PolicyKey: "default-runner-v1",
|
||
Name: "默认全局调度策略",
|
||
Description: "控制多个候选平台之间的故障切换;模型运行策略只可覆盖 failoverPolicy,不能覆盖 hardStopPolicy。",
|
||
FailoverPolicy: defaultRunnerFailoverPolicy(),
|
||
HardStopPolicy: defaultRunnerHardStopPolicy(),
|
||
PriorityDemotePolicy: defaultRunnerPriorityDemotePolicy(),
|
||
Metadata: map[string]any{"source": "code-default"},
|
||
Status: "active",
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
}
|
||
|
||
func defaultRunnerPriorityDemotePolicy() map[string]any {
|
||
return map[string]any{
|
||
"enabled": true,
|
||
"demoteStep": 100,
|
||
"categories": []any{"network", "timeout", "stream_error", "rate_limit", "provider_5xx", "provider_overloaded"},
|
||
"codes": []any{"network", "timeout", "stream_read_error", "rate_limit", "server_error", "overloaded"},
|
||
"statusCodes": []any{408, 429, 500, 502, 503, 504},
|
||
"keywords": []any{"timeout", "network", "rate_limit", "overloaded", "temporarily_unavailable", "server_error", "429", "5xx"},
|
||
}
|
||
}
|
||
|
||
func defaultRunnerFailoverPolicy() map[string]any {
|
||
return map[string]any{
|
||
"enabled": true,
|
||
"maxPlatforms": 99,
|
||
"maxDurationSeconds": 600,
|
||
"allowCategories": []any{"network", "timeout", "stream_error", "rate_limit", "provider_5xx", "provider_overloaded", "auth_error"},
|
||
"denyCategories": []any{"request_error", "unsupported_model", "user_permission", "insufficient_balance"},
|
||
"allowCodes": []any{"auth_failed", "invalid_api_key", "missing_credentials"},
|
||
"allowKeywords": []any{"timeout", "network", "rate_limit", "overloaded", "temporarily_unavailable", "server_error", "auth_failed", "invalid_api_key", "missing_credentials", "unauthorized", "forbidden", "429", "5xx"},
|
||
"denyKeywords": []any{"invalid_parameter", "missing required", "bad request"},
|
||
"allowStatusCodes": []any{401, 403, 408, 429, 500, 502, 503, 504},
|
||
"denyStatusCodes": []any{},
|
||
}
|
||
}
|
||
|
||
func defaultRunnerHardStopPolicy() map[string]any {
|
||
return map[string]any{
|
||
"enabled": true,
|
||
"categories": []any{"request_error", "unsupported_model", "user_permission", "insufficient_balance"},
|
||
"codes": []any{"bad_request", "invalid_request", "invalid_parameter", "missing_required", "unsupported_kind", "unsupported_model", "insufficient_balance", "permission_denied"},
|
||
"statusCodes": []any{},
|
||
"keywords": []any{"invalid_parameter", "missing required", "bad request", "insufficient balance"},
|
||
}
|
||
}
|