diff --git a/package.json b/package.json index a3fd4d9..8e65c27 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "next": "16.1.1", "next-intl": "^4.7.0", "nodemailer": "^8.0.2", + "openai": "^6.27.0", "pg": "^8.16.3", "react": "19.2.3", "react-dom": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcc98c0..7c00bb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: nodemailer: specifier: ^8.0.2 version: 8.0.2 + openai: + specifier: ^6.27.0 + version: 6.27.0(zod@4.3.5) pg: specifier: ^8.16.3 version: 8.16.3 @@ -2731,6 +2734,18 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + openai@6.27.0: + resolution: {integrity: sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -6162,6 +6177,10 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + openai@6.27.0(zod@4.3.5): + optionalDependencies: + zod: 4.3.5 + optionator@0.9.4: dependencies: deep-is: 0.1.4 diff --git a/src/lib/bigmodel/dictionary/stage1-preprocess.ts b/src/lib/bigmodel/dictionary/stage1-preprocess.ts index 350fa17..d4082f9 100644 --- a/src/lib/bigmodel/dictionary/stage1-preprocess.ts +++ b/src/lib/bigmodel/dictionary/stage1-preprocess.ts @@ -1,4 +1,4 @@ -import { getAnswer } from "../zhipu"; +import { getAnswer } from "../llm"; import { parseAIGeneratedJSON } from "@/utils/json"; import { PreprocessResult } from "./types"; import { createLogger } from "@/lib/logger"; diff --git a/src/lib/bigmodel/dictionary/stage4-entriesGeneration.ts b/src/lib/bigmodel/dictionary/stage4-entriesGeneration.ts index 0519f99..22aac32 100644 --- a/src/lib/bigmodel/dictionary/stage4-entriesGeneration.ts +++ b/src/lib/bigmodel/dictionary/stage4-entriesGeneration.ts @@ -1,4 +1,4 @@ -import { getAnswer } from "../zhipu"; +import { getAnswer } from "../llm"; import { parseAIGeneratedJSON } from "@/utils/json"; import { EntriesGenerationResult } from "./types"; import { createLogger } from "@/lib/logger"; diff --git a/src/lib/bigmodel/llm.ts b/src/lib/bigmodel/llm.ts new file mode 100644 index 0000000..623471a --- /dev/null +++ b/src/lib/bigmodel/llm.ts @@ -0,0 +1,37 @@ +"use server"; + +import OpenAI from "openai"; + +const openai = new OpenAI({ + apiKey: process.env.ZHIPU_API_KEY, + baseURL: "https://open.bigmodel.cn/api/paas/v4", +}); + +type Messages = Array< + | { role: "system"; content: string } + | { role: "user"; content: string } + | { role: "assistant"; content: string } +>; + +async function getAnswer(prompt: string): Promise; +async function getAnswer(prompt: Messages): Promise; +async function getAnswer(prompt: string | Messages): Promise { + const messages: Messages = typeof prompt === "string" + ? [{ role: "user", content: prompt }] + : prompt; + + const response = await openai.chat.completions.create({ + model: process.env.ZHIPU_MODEL_NAME || "glm-4", + messages: messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[], + temperature: 0.2, + }); + + const content = response.choices[0]?.message?.content; + if (!content) { + throw new Error("AI API 返回空响应"); + } + + return content.trim(); +} + +export { getAnswer }; diff --git a/src/lib/bigmodel/translator/orchestrator.ts b/src/lib/bigmodel/translator/orchestrator.ts index 739441d..92bd55e 100644 --- a/src/lib/bigmodel/translator/orchestrator.ts +++ b/src/lib/bigmodel/translator/orchestrator.ts @@ -1,4 +1,4 @@ -import { getAnswer } from "../zhipu"; +import { getAnswer } from "../llm"; import { parseAIGeneratedJSON } from "@/utils/json"; import { LanguageDetectionResult, TranslationLLMResponse } from "./types"; import { createLogger } from "@/lib/logger"; diff --git a/src/lib/bigmodel/zhipu.ts b/src/lib/bigmodel/zhipu.ts deleted file mode 100644 index 0892459..0000000 --- a/src/lib/bigmodel/zhipu.ts +++ /dev/null @@ -1,58 +0,0 @@ -"use server"; - -type Messages = { role: string; content: string; }[]; - -const LLM_TIMEOUT_MS = 30000; - -async function callZhipuAPI( - messages: Messages, - model = process.env.ZHIPU_MODEL_NAME, -) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS); - - const url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"; - - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: "Bearer " + process.env.ZHIPU_API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: model, - messages: messages, - temperature: 0.2, - thinking: { - type: "disabled", - }, - }), - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`API 调用失败: ${response.status} ${response.statusText}`); - } - - return await response.json(); -} - -async function getAnswer(prompt: string): Promise; -async function getAnswer(prompt: Messages): Promise; -async function getAnswer(prompt: string | Messages): Promise { - const messages = typeof prompt === "string" - ? [{ role: "user", content: prompt }] - : prompt; - - const response = await callZhipuAPI(messages); - - if (!response.choices?.[0]?.message?.content) { - throw new Error("AI API 返回空响应"); - } - - return response.choices[0].message.content.trim(); -} - -export { getAnswer }; diff --git a/src/modules/translator/translator-action.ts b/src/modules/translator/translator-action.ts index 03d3176..f4799a7 100644 --- a/src/modules/translator/translator-action.ts +++ b/src/modules/translator/translator-action.ts @@ -8,7 +8,7 @@ import { import { ValidateError } from "@/lib/errors"; import { createLogger } from "@/lib/logger"; import { serviceTranslateText } from "./translator-service"; -import { getAnswer } from "@/lib/bigmodel/zhipu"; +import { getAnswer } from "@/lib/bigmodel/llm"; const log = createLogger("translator-action");