Gemini 图片响应未包含可提取资源时,不再返回伪造占位图,改为非重试失败并保留安全拦截、候选结束状态和上游错误等结构化诊断。\n\n同步将诊断写入 HTTP 错误、任务结果、attempt 指标和日志,确保任务失败时不结算,并补充单元及 PostgreSQL 验收测试。\n\n验证:gofmt、go test ./... -count=1、go vet ./...、迁移安全检查、pnpm openapi。
73 lines
2.6 KiB
Go
73 lines
2.6 KiB
Go
package runner
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/clients"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
|
)
|
|
|
|
func TestAttemptMetricsIncludesCacheAffinityCoefficient(t *testing.T) {
|
|
metrics := attemptMetrics(store.RuntimeModelCandidate{
|
|
ModelName: "deepseek-chat",
|
|
ModelType: "text_generate",
|
|
PlatformID: "deepseek-platform",
|
|
PlatformPriority: 100,
|
|
CacheAffinity: store.RuntimeCandidateCacheAffinity{
|
|
Key: "prompt_lcp_v2:test",
|
|
RequestCount: 2,
|
|
CachedInputTokens: 7296,
|
|
EMAHitRatio: 0.91,
|
|
LastHitRatio: 0.92,
|
|
Confidence: 1,
|
|
Score: 1.74,
|
|
Boost: 20,
|
|
AdjustedPriority: 80,
|
|
MatchedPrefixDepth: 4,
|
|
CandidateCount: 2,
|
|
OverrideReason: "different_effective_priority_tier",
|
|
Applied: true,
|
|
},
|
|
}, 1, false)
|
|
|
|
if metrics["cacheAffinityScore"] != 1.74 {
|
|
t.Fatalf("expected cache affinity score in attempt metrics, got %+v", metrics)
|
|
}
|
|
if metrics["cacheAffinityCachedInputTokens"] != 7296 {
|
|
t.Fatalf("expected cached input tokens in attempt metrics, got %+v", metrics)
|
|
}
|
|
if metrics["cacheAffinityHitRatio"] != 0.91 || metrics["cacheAffinityLastHitRatio"] != 0.92 {
|
|
t.Fatalf("expected hit ratios in attempt metrics, got %+v", metrics)
|
|
}
|
|
if metrics["cacheAffinityMatched"] != true ||
|
|
metrics["cacheAffinityMatchedPrefixDepth"] != 4 ||
|
|
metrics["cacheAffinityCandidateCount"] != 2 ||
|
|
metrics["cacheAffinityOverrideReason"] != "different_effective_priority_tier" {
|
|
t.Fatalf("expected cache affinity routing diagnostics in attempt metrics, got %+v", metrics)
|
|
}
|
|
}
|
|
|
|
func TestFailureRecordIncludesStructuredClientErrorDetails(t *testing.T) {
|
|
cause := &clients.ClientError{
|
|
Code: "gemini_image_output_missing",
|
|
Message: "Gemini image response contains no extractable image: prompt blocked",
|
|
Details: map[string]any{
|
|
"provider": "gemini",
|
|
"reason": "prompt_blocked",
|
|
"promptBlockReason": "SAFETY",
|
|
},
|
|
}
|
|
|
|
_, metrics, _, _, _ := failureMetrics(cause, false)
|
|
details, _ := metrics["errorDetails"].(map[string]any)
|
|
if details["reason"] != "prompt_blocked" || details["promptBlockReason"] != "SAFETY" {
|
|
t.Fatalf("failure metrics lost client error details: %+v", metrics)
|
|
}
|
|
result := buildFailureResult(cause.Code, cause.Message, "", cause)
|
|
errorPayload, _ := result["error"].(map[string]any)
|
|
resultDetails, _ := errorPayload["details"].(map[string]any)
|
|
if resultDetails["provider"] != "gemini" || resultDetails["reason"] != "prompt_blocked" {
|
|
t.Fatalf("failure result lost client error details: %+v", result)
|
|
}
|
|
}
|