From fbff25abaa84ea7239e859aa0e4cbdb7e26609f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 03:42:33 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(ai):=20=E8=87=AA=E9=80=89=20provider=20?= =?UTF-8?q?=E7=9A=84=E8=BF=BD=E9=97=AE=E5=BB=BA=E8=AE=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=87=AA=E5=B7=B1=E7=9A=84=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E8=90=BD=E8=BF=9B=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 条件写成 provider !== "intern" && !ZHIPU_API_KEY,线上配了 ZHIPU,于是选了 openai/gemini 并填了自己 key 的用户也走公共缓存分支,cache 里固定用 intern/glm, 用户的 key 和模型被丢掉,和注释「自定义 key 不走公共缓存」相反。 --- app/api/suggestions/route.ts | 9 +---- tests/suggestions-route.test.ts | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 tests/suggestions-route.test.ts diff --git a/app/api/suggestions/route.ts b/app/api/suggestions/route.ts index 2fa23003..5c045d79 100644 --- a/app/api/suggestions/route.ts +++ b/app/api/suggestions/route.ts @@ -120,7 +120,7 @@ export async function POST(req: Request) { let text = ""; // 判断是否可以使用缓存(如果使用了自定义 apiKey,为了防止数据交叉,不采用公用缓存) - if (provider !== "intern" && !process.env.ZHIPU_API_KEY) { + if (provider !== "intern") { const { text: directText } = await generateText({ model, prompt, @@ -128,12 +128,7 @@ export async function POST(req: Request) { text = directText; } else { // 确定内部用于缓存匹配的模型标识 - const internalModelId = - provider === "intern" - ? process.env.ZHIPU_API_KEY - ? "glm" - : "intern" - : "intern"; + const internalModelId = process.env.ZHIPU_API_KEY ? "glm" : "intern"; // 将缓存粒度关联到请求本身的内容上 const suggestionKey = isWelcomeRequest ? `welcome-${pageContext?.slug || "default"}` diff --git a/tests/suggestions-route.test.ts b/tests/suggestions-route.test.ts new file mode 100644 index 00000000..f2fc3b5c --- /dev/null +++ b/tests/suggestions-route.test.ts @@ -0,0 +1,67 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const generateText = vi.fn(async () => ({ text: '["q1","q2","q3"]' })); +const unstableCache = vi.fn( + (fn: (...args: unknown[]) => unknown, _keys?: string[], _opts?: object) => + (...args: unknown[]) => + fn(...args), +); + +vi.mock("ai", () => ({ generateText })); +vi.mock("next/cache", () => ({ unstable_cache: unstableCache })); +vi.mock("@/lib/rate-limit", () => ({ + limitChat: async () => ({ success: true }), + rateLimitResponse: () => new Response(null, { status: 429 }), +})); +vi.mock("@/lib/ai/models", () => ({ + getModel: (provider: string, apiKey?: string) => ({ provider, apiKey }), + requiresApiKey: (provider: string) => provider !== "intern", +})); +vi.mock("@/lib/ai/providers/glm", () => ({ + createGlmFlashModel: () => ({ provider: "glm" }), +})); + +const { POST } = await import("@/app/api/suggestions/route"); + +// 公共缓存按请求建一层 unstable_cache,key 形如 suggestion-key-<...> +const usedSharedCache = () => + unstableCache.mock.calls.some(([, keys]) => + (keys as string[]).some((k) => k.startsWith("suggestion-key-")), + ); + +function post(body: object) { + return POST( + new Request("http://localhost/api/suggestions", { + method: "POST", + body: JSON.stringify(body), + }), + ); +} + +describe("/api/suggestions 模型选择", () => { + beforeEach(() => vi.stubEnv("ZHIPU_API_KEY", "server-key")); + afterEach(() => { + vi.unstubAllEnvs(); + vi.clearAllMocks(); + }); + + it("自带 key 的 provider 用用户自己的模型,不进公共缓存(即使服务端配了 ZHIPU)", async () => { + await post({ messages: [], provider: "openai", apiKey: "user-key" }); + + expect(usedSharedCache()).toBe(false); + expect(generateText).toHaveBeenCalledWith( + expect.objectContaining({ + model: { provider: "openai", apiKey: "user-key" }, + }), + ); + }); + + it("默认 intern 走公共缓存,配了 ZHIPU 时用 GLM", async () => { + await post({ messages: [] }); + + expect(usedSharedCache()).toBe(true); + expect(generateText).toHaveBeenCalledWith( + expect.objectContaining({ model: { provider: "glm" } }), + ); + }); +}); From 13e91f1dde08178dd436fcbabdabd9a173a1bc49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 03:44:02 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(ai):=20=E6=96=87=E6=A1=A3=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E6=8C=89=E9=A1=B5=E9=9D=A2=E8=AF=AD=E8=A8=80=E8=AF=BB?= =?UTF-8?q?=E6=AD=A3=E6=96=87=EF=BC=8C/en=20=E9=A1=B5=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E6=8B=BF=E5=88=B0=E4=B8=AD=E6=96=87=E5=8E=9F=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat 路由 source.getPage(slugArray) 没传 locale,fumadocs 固定返回 zh 原文, 英文文档页的助手上下文一直是中文。DocsAssistant 的 pageContext 带上 locale, 路由用 hasLocale 校验后传给 getPage;非法值走 fallback(zh)。 --- app/[locale]/docs/[...slug]/page.tsx | 1 + app/api/chat/route.ts | 9 ++++++++- app/components/DocsAssistant.tsx | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/[locale]/docs/[...slug]/page.tsx b/app/[locale]/docs/[...slug]/page.tsx index f8c79764..99668ff8 100644 --- a/app/[locale]/docs/[...slug]/page.tsx +++ b/app/[locale]/docs/[...slug]/page.tsx @@ -214,6 +214,7 @@ export default async function DocPage({ params }: Param) { title: page.data.title, description: page.data.description, slug: slug?.join("/"), + locale, }} /> diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index fca7d3e3..97dba3a2 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -2,6 +2,8 @@ import { streamText, UIMessage, convertToModelMessages } from "ai"; import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models"; import { buildSystemMessage } from "@/lib/ai/prompt"; import { source } from "@/lib/source"; +import { routing } from "@/i18n/routing"; +import { hasLocale } from "next-intl"; import { limitChat, rateLimitResponse } from "@/lib/rate-limit"; import fs from "fs/promises"; import path from "path"; @@ -20,6 +22,7 @@ interface ChatRequest { description?: string; content?: string; slug?: string; + locale?: string; }; provider?: AIProvider; apiKey?: string; @@ -140,7 +143,11 @@ export async function POST(req: Request) { if (pageContext?.slug && !pageContext.content) { try { const slugArray = pageContext.slug.split("/"); - const page = source.getPage(slugArray); + // 不传 locale 会固定拿到 zh 原文,/en 页的助手就读中文;非法值交给 fallback(zh) + const locale = hasLocale(routing.locales, pageContext.locale) + ? pageContext.locale + : undefined; + const page = source.getPage(slugArray, locale); if (page) { const cachedContent = mdxContentCache.get(page.path); diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx index d6f4e4ab..18c4a012 100644 --- a/app/components/DocsAssistant.tsx +++ b/app/components/DocsAssistant.tsx @@ -19,6 +19,7 @@ interface PageContext { title?: string; description?: string; slug?: string; + locale?: string; } export interface WelcomeSuggestion {