fix(dictionary): 修复 AI 编排系统的错误处理和超时控制
- 修复 orchestrator 中 throw 字符串的问题,改为 throw LookUpError - 为 zhipu.ts 添加 30 秒超时控制,防止 LLM 调用卡死 - stage1 添加 isEmpty 和 isNaturalLanguage 字段验证 - stage2 改为降级处理而非直接失败,提升用户体验 - types.ts 添加 canMap 字段 - AGENTS.md 添加禁止擅自运行 pnpm dev 的说明
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# LEARN-LANGUAGES 知识库
|
# LEARN-LANGUAGES 知识库
|
||||||
|
|
||||||
**生成时间:** 2026-03-08
|
**生成时间:** 2026-03-08
|
||||||
**提交:** 91c59c3
|
**提交:** 6ba5ae9
|
||||||
**分支:** dev
|
**分支:** dev
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
@@ -112,6 +112,7 @@ log.error("Failed to fetch folders", { error });
|
|||||||
- ❌ Server Component 可行时用 Client Component
|
- ❌ Server Component 可行时用 Client Component
|
||||||
- ❌ npm 或 yarn (使用 pnpm)
|
- ❌ npm 或 yarn (使用 pnpm)
|
||||||
- ❌ 生产代码中使用 `console.log` (使用 winston logger)
|
- ❌ 生产代码中使用 `console.log` (使用 winston logger)
|
||||||
|
- ❌ 擅自运行 `pnpm dev` (不需要,用 `pnpm build` 验证即可)
|
||||||
|
|
||||||
## 独特风格
|
## 独特风格
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export async function executeDictionaryLookup(
|
|||||||
|
|
||||||
if (!standardFormResult.standardForm) {
|
if (!standardFormResult.standardForm) {
|
||||||
log.error("[Stage 3] Standard form is empty");
|
log.error("[Stage 3] Standard form is empty");
|
||||||
throw "无法生成标准形式";
|
throw new LookUpError("无法生成标准形式");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("[Stage 3] Standard form complete", { standardFormResult });
|
log.debug("[Stage 3] Standard form complete", { standardFormResult });
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ export async function analyzeInput(text: string): Promise<InputAnalysisResult> {
|
|||||||
throw new Error("阶段1:isValid 字段类型错误");
|
throw new Error("阶段1:isValid 字段类型错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof result.isEmpty !== "boolean") {
|
||||||
|
throw new Error("阶段1:isEmpty 字段类型错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof result.isNaturalLanguage !== "boolean") {
|
||||||
|
throw new Error("阶段1:isNaturalLanguage 字段类型错误");
|
||||||
|
}
|
||||||
|
|
||||||
// 确保 reason 字段存在
|
// 确保 reason 字段存在
|
||||||
if (typeof result.reason !== "string") {
|
if (typeof result.reason !== "string") {
|
||||||
result.reason = "";
|
result.reason = "";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const log = createLogger("dictionary-stage2");
|
|||||||
* 阶段 2:跨语言语义映射决策
|
* 阶段 2:跨语言语义映射决策
|
||||||
*
|
*
|
||||||
* 独立的 LLM 调用,决定是否需要语义映射
|
* 独立的 LLM 调用,决定是否需要语义映射
|
||||||
* 如果输入不符合"明确、基础、可词典化的语义概念"且语言不一致,直接返回失败
|
* 如果输入不符合"明确、基础、可词典化的语义概念"且语言不一致,则降级使用原始输入
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function determineSemanticMapping(
|
export async function determineSemanticMapping(
|
||||||
@@ -86,9 +86,17 @@ b) 输入是明确、基础、可词典化的语义概念
|
|||||||
result.reason = "";
|
result.reason = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果不应该映射,返回错误
|
// 如果不应该映射,返回降级结果(不抛出错误)
|
||||||
|
// 这样可以让后续阶段使用原始输入继续处理
|
||||||
if (!result.shouldMap) {
|
if (!result.shouldMap) {
|
||||||
throw new Error(result.reason || "输入不符合可词典化的语义概念,无法进行跨语言查询");
|
log.debug("Semantic mapping not applicable, using original input", {
|
||||||
|
reason: result.reason
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
shouldMap: false,
|
||||||
|
canMap: result.canMap ?? false,
|
||||||
|
reason: result.reason,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.mappedQuery || result.mappedQuery.trim().length === 0) {
|
if (!result.mappedQuery || result.mappedQuery.trim().length === 0) {
|
||||||
@@ -97,6 +105,7 @@ b) 输入是明确、基础、可词典化的语义概念
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
shouldMap: result.shouldMap,
|
shouldMap: result.shouldMap,
|
||||||
|
canMap: result.canMap ?? true,
|
||||||
coreSemantic: result.coreSemantic,
|
coreSemantic: result.coreSemantic,
|
||||||
mappedQuery: result.mappedQuery,
|
mappedQuery: result.mappedQuery,
|
||||||
reason: result.reason,
|
reason: result.reason,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface InputAnalysisResult {
|
|||||||
// 阶段2:语义映射结果
|
// 阶段2:语义映射结果
|
||||||
export interface SemanticMappingResult {
|
export interface SemanticMappingResult {
|
||||||
shouldMap: boolean;
|
shouldMap: boolean;
|
||||||
|
canMap?: boolean;
|
||||||
coreSemantic?: string;
|
coreSemantic?: string;
|
||||||
mappedQuery?: string;
|
mappedQuery?: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
|
|||||||
@@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
type Messages = { role: string; content: string; }[];
|
type Messages = { role: string; content: string; }[];
|
||||||
|
|
||||||
|
const LLM_TIMEOUT_MS = 30000;
|
||||||
|
|
||||||
async function callZhipuAPI(
|
async function callZhipuAPI(
|
||||||
messages: Messages,
|
messages: Messages,
|
||||||
model = process.env.ZHIPU_MODEL_NAME,
|
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 url = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@@ -22,8 +27,11 @@ async function callZhipuAPI(
|
|||||||
type: "disabled",
|
type: "disabled",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`API 调用失败: ${response.status} ${response.statusText}`);
|
throw new Error(`API 调用失败: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user