package llm import ( "context" "testing" "time" "github.com/cloudwego/eino/components/model" "github.com/cloudwego/eino/schema" ) func msgs(text string) []*schema.Message { return []*schema.Message{schema.UserMessage(text)} } // TestCache_HitSkipsInner 同输入第二次命中缓存,不再调底层模型。 func TestCache_HitSkipsInner(t *testing.T) { var calls int cm := &cachingModel{ inner: &fakeModel{reply: "答案", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test", } ctx := context.Background() a1, _ := cm.Generate(ctx, msgs("问题")) a2, _ := cm.Generate(ctx, msgs("问题")) if a1.Content != "答案" || a2.Content != "答案" { t.Fatalf("两次都应得答案, got %q %q", a1.Content, a2.Content) } if calls != 1 { t.Fatalf("第二次应命中缓存、底层只调一次,got calls=%d", calls) } } // TestCache_DifferentInputMiss 不同输入不串味,各调一次。 func TestCache_DifferentInputMiss(t *testing.T) { var calls int cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"} ctx := context.Background() cm.Generate(ctx, msgs("A")) cm.Generate(ctx, msgs("B")) if calls != 2 { t.Fatalf("不同输入应各调一次,got calls=%d", calls) } } // TestCache_TTLExpiry 过期后重新调底层。 func TestCache_TTLExpiry(t *testing.T) { var calls int cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(20*time.Millisecond, 100), keyExtra: "m:test"} ctx := context.Background() cm.Generate(ctx, msgs("Q")) time.Sleep(40 * time.Millisecond) cm.Generate(ctx, msgs("Q")) // 已过期 → 再调 if calls != 2 { t.Fatalf("过期后应重新调底层,got calls=%d", calls) } } // TestCache_StreamNotCached 流式不缓存,每次都调底层。 func TestCache_StreamNotCached(t *testing.T) { var calls int cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"} ctx := context.Background() for i := 0; i < 2; i++ { sr, _ := cm.Stream(ctx, msgs("Q")) sr.Close() } if calls != 2 { t.Fatalf("流式不应缓存,got calls=%d", calls) } } // TestCache_WithToolsDifferentKey 不同工具集 → 不同缓存键,不串味。 func TestCache_WithToolsDifferentKey(t *testing.T) { var calls int base := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"} ctx := context.Background() toolA, _ := base.WithTools([]*schema.ToolInfo{{Name: "a", Desc: "A"}}) toolB, _ := base.WithTools([]*schema.ToolInfo{{Name: "b", Desc: "B"}}) toolA.Generate(ctx, msgs("Q")) toolB.Generate(ctx, msgs("Q")) // 同输入但工具集不同 → 不命中 if calls != 2 { t.Fatalf("不同工具集不应串味,got calls=%d", calls) } } // TestWithCache_Disabled TTL=0 时不包缓存,直接返回原模型。 func TestWithCache_Disabled(t *testing.T) { t.Setenv("LLM_CACHE_TTL_S", "0") inner := &fakeModel{reply: "x"} got := withCache(inner, "test") if _, isCaching := got.(*cachingModel); isCaching { t.Fatal("TTL=0 应禁用缓存,不应包 cachingModel") } var _ model.ToolCallingChatModel = got }