发布 0.2.0:模型池自愈、探测修正、任务异步触发与删除加固
CI / test (push) Successful in 32s
Release / release (push) Successful in 52s

This commit is contained in:
2026-07-10 20:25:37 +08:00
parent dbba1f4905
commit 7706f59549
19 changed files with 978 additions and 107 deletions
+30 -14
View File
@@ -43,10 +43,13 @@ func (s *AiGatewayService) Chat(ctx context.Context, ir aiwire.ChatRequest, grou
s.markSuccess(ctx, cand.ch.ID)
return resp, meta, nil
}
if !retryable(err) {
retry, penalize := s.noteCallErr(ctx, cand, err)
if !retry {
return nil, meta, err
}
s.markFailure(ctx, cand.ch.ID)
if penalize {
s.markFailure(ctx, cand.ch.ID)
}
excluded[cand.ch.ID] = true
meta.Retries++
lastErr = err
@@ -83,10 +86,13 @@ func (s *AiGatewayService) OpenStream(ctx context.Context, ir aiwire.ChatRequest
s.markSuccess(ctx, cand.ch.ID)
return stream, meta, nil
}
if !retryable(err) {
retry, penalize := s.noteCallErr(ctx, cand, err)
if !retry {
return nil, meta, err
}
s.markFailure(ctx, cand.ch.ID)
if penalize {
s.markFailure(ctx, cand.ch.ID)
}
excluded[cand.ch.ID] = true
meta.Retries++
lastErr = err
@@ -102,12 +108,18 @@ func firstErr(lastErr, pickErr error) error {
return pickErr
}
// retryable 判定是否换渠道重试:429 / 5xx / 网络错误可重试,其余 4xx 直接透传。
func retryable(err error) bool {
if status, ok := oci.ServiceStatus(err); ok {
return status == 429 || status >= 500
// noteCallErr 汇总一次调用失败:模型级不可用(微调基座 400 / 实体不存在 404)先标记
// 剔除该 (渠道, 模型),换渠道重试且不计熔断——这是模型×区域供给问题而非渠道健康问题;
// 429 / 5xx / 网络错误换渠道并计熔断;其余 4xx 直接透传。
func (s *AiGatewayService) noteCallErr(ctx context.Context, cand *aiCandidate, err error) (retry, penalize bool) {
if oci.IsModelUnavailable(err) {
s.markModelUnusable(ctx, cand.ch.ID, cand.modelOcid, oci.CompactError(err))
return true, false
}
return true
if status, ok := oci.ServiceStatus(err); ok {
return status == 429 || status >= 500, true
}
return true, true
}
// pick 选出支持该模型的最优渠道:能力匹配 → 启用 → 分组匹配 → 未熔断 → 最小优先级组 → 加权随机。
@@ -142,10 +154,11 @@ func (s *AiGatewayService) pick(ctx context.Context, modelName, group, capabilit
return &aiCandidate{ch: chosen, modelOcid: ocids[chosen.ID]}, nil
}
// modelChannels 查出提供该模型的渠道 ID 及各自的模型 OCID;
// capability=CHAT 时兼容存量空串(加列前只同步对话模型)
// modelChannels 查出提供该模型的渠道 ID 及各自的模型 OCID(剔除不可按需调用标记);
// capability=CHAT 时兼容存量空串(加列前只同步对话模型);unusable 需兼容 NULL
// (AutoMigrate 加列后、首次重同步前的存量行)。
func (s *AiGatewayService) modelChannels(ctx context.Context, modelName, capability string) (map[uint]string, []uint, error) {
q := s.db.WithContext(ctx).Where("name = ?", modelName)
q := s.db.WithContext(ctx).Where("name = ? AND (unusable = ? OR unusable IS NULL)", modelName, false)
if capability == "CHAT" {
q = q.Where("capability IN ?", []string{"CHAT", ""})
} else {
@@ -221,10 +234,13 @@ func (s *AiGatewayService) Embeddings(ctx context.Context, req aiwire.Embeddings
s.markSuccess(ctx, cand.ch.ID)
return resp, meta, nil
}
if !retryable(err) {
retry, penalize := s.noteCallErr(ctx, cand, err)
if !retry {
return nil, meta, err
}
s.markFailure(ctx, cand.ch.ID)
if penalize {
s.markFailure(ctx, cand.ch.ID)
}
excluded[cand.ch.ID] = true
meta.Retries++
lastErr = err