fix: bound AI analysis provider latency

This commit is contained in:
2026-07-09 01:11:29 -07:00
parent a8776f9d79
commit 153c501cf7
6 changed files with 173 additions and 7 deletions

View File

@@ -1,7 +1,6 @@
import { ChatOpenAI } from "@langchain/openai";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AiRuntimeConfigResult } from "@/modules/ai/server/config";
import { getAiRuntimeConfig } from "@/modules/ai/server/config";
import type { ElderAiResidentContext } from "@/modules/ai/server/elder-context";
import { buildElderAiContext } from "@/modules/ai/server/elder-context";
@@ -95,14 +94,17 @@ type ReturnlessMock = ReturnlessFunction & {
type ReturnlessFunction = (...args: unknown[]) => unknown;
const runtimeConfig: AiRuntimeConfigResult = {
const runtimeConfig = {
success: true,
config: {
baseUrl: "https://ai.example.test/v1",
apiKey: "test-api-key",
chatModel: "gpt-test",
requestTimeoutMs: 12_345,
maxTokens: 901,
maxRetries: 4,
},
};
} as const;
const account: AuthContext["account"] = {
id: "account-1",
@@ -327,6 +329,52 @@ beforeEach(() => {
});
describe("elder AI analysis service", () => {
it("constructs ChatOpenAI with latency controls from AI runtime config", async () => {
const result = await generateElderAiAnalysis(createAuthContext(), "elder-1");
expect(result.success).toBe(true);
expect(ChatOpenAI).toHaveBeenCalledWith(expect.objectContaining({
apiKey: "test-api-key",
model: "gpt-test",
timeout: 12_345,
maxTokens: 901,
maxRetries: 4,
configuration: { baseURL: "https://ai.example.test/v1" },
}));
});
it.each([
{
name: "AbortError",
error: Object.assign(new Error("aborted request with sk-live-secret and resident prompt"), { name: "AbortError" }),
},
{
name: "timeout-like provider message",
error: new Error("request timed out after 12345ms with sk-live-secret and resident prompt"),
},
])("maps $name model failures to sanitized timeout failed history", async ({ error }) => {
const database = createDatabaseFake();
useDatabase(database);
chatMocks.invoke.mockRejectedValue(error);
const result = await generateElderAiAnalysis(createAuthContext(), "elder-1");
expect(result).toEqual({ success: false, reason: "AI 服务响应超时", status: 502 });
expect(database.insertedValues).toEqual([
expect.objectContaining({
status: "failed",
dataScopes: ["elder", "health"],
errorCategory: "timeout",
errorReason: "AI 服务响应超时",
}),
]);
const persistedFailure = JSON.stringify(database.insertedValues[0]);
expect(persistedFailure).not.toContain("sk-live-secret");
expect(persistedFailure).not.toContain("resident prompt");
expect(persistedFailure).not.toContain("Night wandering");
expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ result: "failure", reason: "AI 服务响应超时" }));
});
it("saves sanitized failed history when AI runtime config is missing", async () => {
const database = createDatabaseFake();
useDatabase(database);