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:
2026-03-09 17:19:12 +08:00
parent 6ba5ae993a
commit 3652e350e6
6 changed files with 33 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
# LEARN-LANGUAGES 知识库
**生成时间:** 2026-03-08
**提交:** 91c59c3
**提交:** 6ba5ae9
**分支:** dev
## 概述
@@ -112,6 +112,7 @@ log.error("Failed to fetch folders", { error });
- ❌ Server Component 可行时用 Client Component
- ❌ npm 或 yarn (使用 pnpm)
- ❌ 生产代码中使用 `console.log` (使用 winston logger)
- ❌ 擅自运行 `pnpm dev` (不需要,用 `pnpm build` 验证即可)
## 独特风格

View File

@@ -53,7 +53,7 @@ export async function executeDictionaryLookup(
if (!standardFormResult.standardForm) {
log.error("[Stage 3] Standard form is empty");
throw "无法生成标准形式";
throw new LookUpError("无法生成标准形式");
}
log.debug("[Stage 3] Standard form complete", { standardFormResult });

View File

@@ -55,6 +55,14 @@ export async function analyzeInput(text: string): Promise<InputAnalysisResult> {
throw new Error("阶段1isValid 字段类型错误");
}
if (typeof result.isEmpty !== "boolean") {
throw new Error("阶段1isEmpty 字段类型错误");
}
if (typeof result.isNaturalLanguage !== "boolean") {
throw new Error("阶段1isNaturalLanguage 字段类型错误");
}
// 确保 reason 字段存在
if (typeof result.reason !== "string") {
result.reason = "";

View File

@@ -9,7 +9,7 @@ const log = createLogger("dictionary-stage2");
* 阶段 2跨语言语义映射决策
*
* 独立的 LLM 调用,决定是否需要语义映射
* 如果输入不符合"明确、基础、可词典化的语义概念"且语言不一致,直接返回失败
* 如果输入不符合"明确、基础、可词典化的语义概念"且语言不一致,则降级使用原始输入
*/
export async function determineSemanticMapping(
@@ -86,9 +86,17 @@ b) 输入是明确、基础、可词典化的语义概念
result.reason = "";
}
// 如果不应该映射,返回错误
// 如果不应该映射,返回降级结果(不抛出错误
// 这样可以让后续阶段使用原始输入继续处理
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) {
@@ -97,6 +105,7 @@ b) 输入是明确、基础、可词典化的语义概念
return {
shouldMap: result.shouldMap,
canMap: result.canMap ?? true,
coreSemantic: result.coreSemantic,
mappedQuery: result.mappedQuery,
reason: result.reason,

View File

@@ -20,6 +20,7 @@ export interface InputAnalysisResult {
// 阶段2语义映射结果
export interface SemanticMappingResult {
shouldMap: boolean;
canMap?: boolean;
coreSemantic?: string;
mappedQuery?: string;
reason: string;

View File

@@ -2,10 +2,15 @@
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, {
@@ -22,8 +27,11 @@ async function callZhipuAPI(
type: "disabled",
},
}),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`API 调用失败: ${response.status} ${response.statusText}`);
}