Răsfoiți Sursa

fix(studio): 限制 bank 服务测试模型候选

fanghanjun 6 luni în urmă
părinte
comite
a97abd0d54
2 a modificat fișierele cu 63 adăugiri și 5 ștergeri
  1. 45 0
      packages/studio/src/api/server.test.ts
  2. 18 5
      packages/studio/src/api/server.ts

+ 45 - 0
packages/studio/src/api/server.test.ts

@@ -95,6 +95,7 @@ const endpointMocks = [
     label: id,
     group,
     ...(id === "google" ? { checkModel: "gemini-2.5-flash" } : {}),
+    ...(id === "minimax" ? { checkModel: "MiniMax-M2.7" } : {}),
     models: [
       { id: `${id}-model`, maxOutput: 4096, contextWindowTokens: 32768, enabled: true },
       { id: `${id}-disabled`, maxOutput: 4096, contextWindowTokens: 32768, enabled: false },
@@ -1167,6 +1168,50 @@ describe("createStudioServer daemon lifecycle", () => {
       expect.any(Array),
       expect.any(Object),
     );
+    expect(chatCompletionMock.mock.calls.map((call) => call[1])).not.toContain("MiniMax-M2.7");
+  });
+
+  it("does not fall back to the global default model when a bank endpoint probe fails", async () => {
+    await writeFile(join(root, "inkos.json"), JSON.stringify({
+      ...projectConfig,
+      llm: {
+        services: [
+          { service: "google", apiFormat: "chat", stream: false },
+        ],
+        defaultModel: "MiniMax-M2.7",
+      },
+    }, null, 2), "utf-8");
+
+    const fetchMock = vi.fn().mockResolvedValue({
+      ok: false,
+      status: 404,
+      text: async () => "Not Found",
+    });
+    vi.stubGlobal("fetch", fetchMock as typeof fetch);
+    createLLMClientMock.mockImplementation(((cfg: unknown) => cfg) as any);
+    chatCompletionMock.mockImplementation(async (_client: any, model: string) => {
+      throw new Error(`probe failed for ${model}`);
+    });
+
+    const { createStudioServer } = await import("./server.js");
+    const app = createStudioServer(cloneProjectConfig() as never, root);
+
+    const response = await app.request("http://localhost/api/v1/services/google/test", {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({
+        apiKey: "google-key",
+        apiFormat: "chat",
+        stream: false,
+      }),
+    });
+
+    expect(response.status).toBe(400);
+    await expect(response.json()).resolves.toMatchObject({
+      ok: false,
+      error: expect.stringContaining("gemini-2.5-flash"),
+    });
+    expect(new Set(chatCompletionMock.mock.calls.map((call) => call[1]))).toEqual(new Set(["gemini-2.5-flash"]));
   });
 
   it("uses the preset models baseUrl when listing Bailian models", async () => {

+ 18 - 5
packages/studio/src/api/server.ts

@@ -360,6 +360,7 @@ function buildModelCandidates(args: {
   configModel?: string;
   envModel?: string | null;
   discoveredModels: Array<{ id: string; name: string }>;
+  includeGenericFallbacks?: boolean;
 }): string[] {
   const seen = new Set<string>();
   const candidates: string[] = [];
@@ -375,6 +376,7 @@ function buildModelCandidates(args: {
   push(args.configModel);
   push(args.envModel ?? undefined);
   for (const model of args.discoveredModels) push(model.id);
+  if (args.includeGenericFallbacks === false) return candidates;
   push("gpt-5.4");
   push("gpt-4o");
   push("claude-sonnet-4-6");
@@ -450,13 +452,24 @@ async function probeServiceCapabilities(args: {
   const preset = resolveServicePreset(baseService);
   const serviceFirstModel =
     endpoint?.checkModel
-    ?? endpoint?.models.find((model) => model.enabled !== false)?.id
-    ?? preset?.knownModels?.[0];
+    ?? preset?.knownModels?.[0]
+    ?? endpoint?.models.find((model) => model.enabled !== false)?.id;
+  const useEndpointCheckModel = !isCustomServiceId(args.service) && Boolean(endpoint?.checkModel);
+  const configService = typeof llm.service === "string" ? llm.service : undefined;
+  const configModel = !useEndpointCheckModel && configService === args.service
+    ? typeof llm.defaultModel === "string"
+      ? llm.defaultModel
+      : typeof llm.model === "string"
+        ? llm.model
+        : undefined
+    : undefined;
+  const useCustomFallbacks = isCustomServiceId(args.service);
   const modelCandidates = buildModelCandidates({
     preferredModel: args.preferredModel ?? serviceFirstModel,
-    configModel: typeof llm.defaultModel === "string" ? llm.defaultModel : typeof llm.model === "string" ? llm.model : undefined,
-    envModel,
-    discoveredModels,
+    configModel,
+    envModel: useCustomFallbacks ? envModel : undefined,
+    discoveredModels: useEndpointCheckModel ? [] : discoveredModels,
+    includeGenericFallbacks: useCustomFallbacks,
   });
 
   if (modelCandidates.length === 0) {