fix: 错误记忆DAO全局化(NoTenantId)+GetByKey miss契约修复; 分析prompt补可重试白名单
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -13,13 +13,19 @@ var ErrorMemory = &errorMemoryDao{}
|
||||
type errorMemoryDao struct{}
|
||||
|
||||
// GetByKey 按记忆键查询(未命中返回 (nil, nil))
|
||||
// 错误记忆为全局表:NoTenantId 绕过租户过滤,跨租户共享;r.IsEmpty() 兜底 miss 契约,
|
||||
// 避免对空记录 r.Struct(&res) 上浮 sql.ErrNoRows 导致调用方 fail-closed。
|
||||
func (d *errorMemoryDao) GetByKey(ctx context.Context, key string) (res *entity.ErrorMemory, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameErrorMemory).
|
||||
NoTenantId(ctx).
|
||||
Where(entity.ErrorMemoryCol.MemoryKey, key).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
@@ -27,7 +33,8 @@ func (d *errorMemoryDao) GetByKey(ctx context.Context, key string) (res *entity.
|
||||
// Upsert 存在则更新 retryable/reason/analyzed_by,不存在则插入
|
||||
func (d *errorMemoryDao) Upsert(ctx context.Context, m *entity.ErrorMemory) (err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameErrorMemory)
|
||||
n, err := model.Where(entity.ErrorMemoryCol.MemoryKey, m.MemoryKey).Count()
|
||||
// Count 同样全局化:跨租户已存在的记忆键需命中更新分支,而非重复插入
|
||||
n, err := model.NoTenantId(ctx).Where(entity.ErrorMemoryCol.MemoryKey, m.MemoryKey).Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -43,9 +50,9 @@ func (d *errorMemoryDao) Upsert(ctx context.Context, m *entity.ErrorMemory) (err
|
||||
return
|
||||
}
|
||||
|
||||
// List 分页查询(按 id 倒序)
|
||||
// List 分页查询(按 id 倒序);全局表,管理端列表展示所有租户记忆
|
||||
func (d *errorMemoryDao) List(ctx context.Context, page, pageSize int) (list []entity.ErrorMemory, total int64, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameErrorMemory)
|
||||
model := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameErrorMemory).NoTenantId(ctx)
|
||||
n, err := model.Count()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"model-gateway/consts/public"
|
||||
|
||||
_ "gitea.redpowerfuture.com/red-future/common/consul"
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
)
|
||||
|
||||
// TestErrorMemoryDaoGetByKeyMiss 回归:错误记忆未命中必须返回 (nil, nil),
|
||||
// 而非把空记录丢给 r.Struct(&res) 上浮 sql.ErrNoRows —— 否则 service.shouldRetryWithMemory
|
||||
// 会把一切查询错误当 fail-closed,导致 分析→落库→重试 整条链路变死代码。
|
||||
// 依赖真实 PG;不可达时跳过(不在无 PG 环境硬失败)。
|
||||
func TestErrorMemoryDaoGetByKeyMiss(t *testing.T) {
|
||||
ctx, span := gtrace.NewSpan(context.Background(), "TestErrorMemoryDaoGetByKeyMiss")
|
||||
defer span.End()
|
||||
|
||||
// 探测连接:不可用则跳过
|
||||
if _, err := gfdb.DB(ctx, public.DbNameModelGateway).
|
||||
Model(ctx, public.TableNameErrorMemory).
|
||||
NoTenantId(ctx).
|
||||
Count(); err != nil {
|
||||
t.Skipf("DB不可用,跳过: %v", err)
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("test-miss-%d", time.Now().UnixNano())
|
||||
row, err := ErrorMemory.GetByKey(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByKey 未命中不应报错: %v", err)
|
||||
}
|
||||
if row != nil {
|
||||
t.Fatalf("GetByKey 未命中应返回 nil, got %+v", row)
|
||||
}
|
||||
}
|
||||
@@ -45,11 +45,13 @@ const analysisSystemPrompt = `你是 AI 模型网关的错误分析器。上游
|
||||
|
||||
## 值得重试(retryable: true)
|
||||
- 限流:429、rate limit、请求过密、并发超限
|
||||
- 服务端瞬时故障:5xx、服务过载、上游临时不可用、连接抖动
|
||||
- 服务端瞬时故障:5xx、InternalServiceError、服务过载、上游临时不可用
|
||||
- 超时/取消/连接:Timeout、RequestCanceled、Error while connecting、连接抖动
|
||||
- 媒体源暂不可用(视频/音频生成类上游常见):Invalid video_url、Invalid audio track、Error while downloading、download failed —— 通常是源尚未就绪或下载瞬断,重试可成功,不要误判为永久参数错误
|
||||
- 资源暂时不足:quota 暂时受限
|
||||
|
||||
## 不值得重试(retryable: false)
|
||||
- 请求/参数错误:400、invalid_argument、格式错误
|
||||
- 请求/参数错误:400、invalid_argument、格式错误(注意 Invalid video_url / Invalid audio track 属上类的媒体源错误,不归此类)
|
||||
- 鉴权失败:401、403、invalid_api_key、签名错误
|
||||
- 模型不存在:404、model_not_found
|
||||
- 余额不足:insufficient_quota
|
||||
|
||||
Reference in New Issue
Block a user