首页
/ Understand-Anything 实现指南:/understand 首次运行的会话语言自动检测(从 SKILL.md 提示逻辑到 config.json 持久化)

Understand-Anything 实现指南:/understand 首次运行的会话语言自动检测(从 SKILL.md 提示逻辑到 config.json 持久化)

2026-09-06 17:22:52作者:卓炯娓

本文基于 Understand-Anything 仓库中的实施计划 2026-06-03-language-auto-detection.md 展开。该计划的目标是:在项目内第一次运行 /understand 时,自动检测用户对话所使用的语言并加以确认,然后再开始生成知识图谱内容——同时保证英语用户的体验完全不变。读完本文,你能理解这套"纯提示词逻辑"改动是如何通过四级语言解析链(--language 参数 > 已存配置 > 会话语言检测 > en 默认值)落地的,并能对照仓库源码验证检测结果的持久化位置与消费路径(config.json → 持久化层 → Dashboard 国际化)。

背景与问题:静默的 en 默认值

在改动之前,/understand 生成的所有 LLM 撰写内容(节点摘要、标签、层级名称、引导式游览、语言笔记)默认全部是英语。输出语言只由两个来源控制:显式的 --language <lang> 参数,或 .understand-anything/config.json(新版数据目录为 .ua/config.json)中先前存储的 outputLanguage 字段。配套设计文档 2026-06-03-language-auto-detection-design.md 中记录的观察到的失败场景是:一位全程用中文对话的用户运行了最简单的 /understand 命令,拿到了一张全英文的知识图谱——他只在付完一整次分析运行的时间 + token 成本之后才发现语言不匹配,随后不得不带 --language zh 重新运行。这个默认值是静默的、不可发现的,语言决策在最关键的时点(开始花钱分析之前)没有任何提示。

而改动前的 SKILL.md 步骤 3.6 中,--language 未指定时的分支只有两行:检查 config.json 里有没有 outputLanguage,有则用;没有就 default to en。计划文档将这两行视为"锚点文本",并要求实施者先原样核对再动刀。

设计决策:检测作为兜底,而非每次弹窗

设计文档对比了三个备选方案,最终选定"B:在 SKILL.md 中做检测兜底":

方案 结论
A. 个人 workaround(总是传 --language zh / 手改 config) 拒绝——对上游零价值,目标是一个可提交的修复
B. 在 SKILL.md 中做检测兜底(选定) 最小 diff,对非英语用户严格更好,对英语用户完全不可见
C. 每次运行都弹出语言菜单 拒绝——重新引入摩擦,最可能被上游维护者拒绝

具体设计要点:

  1. 检测只是解析链中的新插入项,位于 en 默认值之前:--language 参数 > config.json 中的 outputLanguage > 首次运行检测会话语言 > en
  2. 确认门(confirmation gate)双条件限定:只在"首次运行(无参数、无已存配置)且检测到非英语语言"时出现;英语对话走与原先完全相同的静默 en 路径。
  3. 全分支持久化:无论哪条分支解析出的值(包括 en)都写入 config.json{"outputLanguage": "<lang>"} 合并进已有配置),因此门每个项目至多触发一次。
  4. 不确认就静默应用被用户明确要求改为"先确认再分析",但确认被严格约束为不影响英语用户,且是非阻塞的(非交互场景降级为单行提示)。
  5. 纯提示词改动,不碰代码:没有 schema、TypeScript 或测试钩子的改动——outputLanguage 字段在 ProjectConfig 中早已存在,locales/<lang>.md 的注入也早已接线,本次只是把"决定 $OUTPUT_LANGUAGE 取值"的提示词逻辑扩写。

文件结构:两个文件的改动边界

计划文档给出的改动范围严格限定为两个文件:

文件 职责 改动
understand-anything-plugin/skills/understand/SKILL.md /understand 技能提示词;步骤 3.6 负责解析 $OUTPUT_LANGUAGE 重写 If --language is NOT specified 子块(计划撰写时为第 142–144 行,行号可能漂移)
README.md 面向用户的文档;Localized output 一节 增加一段描述首次运行自动检测的文字

其余相关文件明确不改,这在源码中可以逐一印证:

  • outputLanguage 字段已存在于 types.tsProjectConfig 接口中(outputLanguage?: string;计划撰写时引用的是 types.ts:119,当前仓库中该行已随文件增长下移,可视为行号漂移的正常现象);
  • $LANGUAGE_DIRECTIVE 模板与 locales/<lang>.md 注入位于 SKILL.md 步骤 3.6 末尾及 Phase 4 的输出语言注入(第 424 行附近),均保持原样;
  • 非英语用户最终消费这条语言链的位置是 Dashboard:App.tsx 启动时 fetch 项目的 config.jsonconfig?.outputLanguage 存在时驱动 <I18nProvider language={...}>App.tsx#L261)。也就是说,技能侧把语言"写进配置"这一步完成后,Dashboard 的 UI 语言由既有代码自动接管,无需为本次功能新增任何前端逻辑。

这正是本计划"锦上添花"之处:一个只改 Markdown 提示词的 feature,其持久化、容错与消费链路全部由既有 TypeScript 代码承担。

Task 1:扩写 SKILL.md 的语言解析分支

Step 1:先核对锚点文本

计划要求先运行:

sed -n '142,144p' understand-anything-plugin/skills/understand/SKILL.md

并期望看到改动前的三行原文:

    - If `--language` is NOT specified:
      - Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that.
      - If no stored preference, default to `en` (English).

如果文本不一致,说明行号已漂移,应先重读步骤 3.6 定位等价代码块再编辑,而不是硬套行号。

Step 2:替换为四级解析链

用精确的 old_string / new_string 编辑替换上述三行。计划特意说明:新文本描述的是确认的意图,而不是硬编码一个字面量提示框——因为 SKILL.md 是供模型解释执行的提示词,运行时由模型自行渲染询问语句。替换后的新块为:

    - If `--language` is NOT specified:
      - **Stored preference wins.** If `$UA_DIR/config.json` has an `outputLanguage` field, set `$OUTPUT_LANGUAGE` to it and skip the rest.
      - **Otherwise detect (first run only).** Infer the predominant language of the user's conversation as an ISO 639-1 code (`$DETECTED_LANG`). If it is `en` or cannot be confidently determined, set `$OUTPUT_LANGUAGE=en` and proceed silently — no prompt (English users see no change).
      - **If `$DETECTED_LANG` ≠ `en`, confirm once before analyzing:** tell the user you detected `<language>` and ask whether to generate all content in it; they press Enter/"yes" to accept, or type another language code/name to override (normalize via the friendly-name map above). If running non-interactively (no reply possible), skip the wait, use `$DETECTED_LANG`, and print a one-line notice instead of blocking.
      - **Persist** the resolved `$OUTPUT_LANGUAGE` (including `en`) into `config.json` so it never re-prompts for this project.

四条规则各自承担一个职责:

  1. 已存配置优先:有 outputLanguage 就直接用并跳过后续全部逻辑——这保证了"门每个项目至多触发一次";
  2. 检测仅在首次运行时发生:把对话的主导语言推断为 ISO 639-1 码 $DETECTED_LANG;若是 en、或无法有把握地判定(混合/含糊对话),直接静默置 en,不弹任何提示;
  3. 非英语才弹确认门:告知用户检测到的 <language>,询问是否用它生成全部内容;用户按 Enter/yes 接受,或输入其他语言代码/名称覆盖(覆盖值通过 3.6 上方既有的 friendly-name 映射表归一化,如 chinesezhjapaneseja,locale 变体如 zh-TWpt-BR 原样保留);非交互运行(headless/CI,无人应答)则跳过等待、直接用 $DETECTED_LANG 并打印一行提示,绝不挂起;
  4. 持久化:把解析出的 $OUTPUT_LANGUAGE(包括 en)写入 config.json

注意新块中的 $UA_DIR 而非改动前的 $PROJECT_ROOT/.understand-anything——这符合 SKILL.md Phase 0 步骤 1.7 对数据目录的解析规则:项目已有 .understand-anything/ 则沿用(存量项目零迁移),否则用新的 .ua/README.md 对这一兼容性有面向用户的表述:"Projects that already have a .understand-anything/ directory keep using it."

"用中文对话但想要英文文档给团队"这个逃生通道由确认门的覆盖输入承担:检测出 zh 但用户在门里输入 en,最终输出即为英文。

Step 3:验证编辑落位且格式正确

sed -n '142,170p' understand-anything-plugin/skills/understand/SKILL.md
grep -c 'default to `en`' understand-anything-plugin/skills/understand/SKILL.md

第一条确认新多级块存在,且其后紧跟着未改动的 - If --language IS specified: 行;第二条期望输出 0——旧的独立 default to en 行已消失,新措辞是 "set $OUTPUT_LANGUAGE to en"。

Step 4:提交

计划给出的提交信息(保留了原计划中的措辞):

git add understand-anything-plugin/skills/understand/SKILL.md
git commit -m "feat(understand): detect conversation language on first run

Expand SKILL.md step 3.6 with conversation-language detection as a
fallback before the en default, gated behind a first-run-only,
non-English-only confirmation. Resolved value is persisted to
config.json so the gate fires at most once. English users see no change."

源码佐证:持久化一侧的既有设施

值得从源码层面确认的是,提示词中"Persist into config.json"这一句背后的读写基础设施是现成且带容错的:

  • persistence/index.ts 定义了 DEFAULT_CONFIG: ProjectConfig = { autoUpdate: false, outputLanguage: "en" }saveConfig / loadConfig 负责 config.json 的读写;
  • loadConfig 在文件缺失或 JSON 损坏时都安全回退到默认配置(outputLanguage: "en"),因此即使 config 被写坏,语言解析也不会抛异常——最坏情况退回本次改动之前的行为;
  • persistence.test.ts 覆盖了三个场景:配置往返一致、无文件返回默认(含 outputLanguage: "en")、config.json 内容损坏时返回默认;
  • 从源码结构看,服务端的兜底同样存在:vite.config.tsviewer.mjs 在直接请求 config.json 失败时会返回 { autoUpdate: false, outputLanguage: "en" } 作为默认响应。

这些事实说明:本计划的"只改提示词"边界之所以成立,是因为写端(技能执行时写配置)和读端(core 持久化层、Dashboard)都早已按 outputLanguage 契约工作。

Task 2:README 文档化

README.mdLocalized output 一节、锚点行 The \--language` parameter affects:` 之前插入一段说明。计划给出的验证与插入命令:

grep -n 'The `--language` parameter affects:' README.md   # 期望在 ~130 行附近命中一行

插入的段落(old_string 为锚点行,new_string 为新段落 + 锚点行):

On the **first run** in a project — when you don't pass `--language` and no language is stored yet — `/understand` detects the language you're conversing in. If it isn't English, it asks you to confirm (or override) before generating; English conversations are unaffected. Your choice is saved to `.understand-anything/config.json` and reused on every later run.

The `--language` parameter affects:

验证:

grep -n 'first run' README.md

期望命中的新句子正好位于 "The --language parameter affects:" 上方。当前仓库中该段落已落地于 README.md#L141(措辞随数据目录更名更新为 .ua/config.json),其上下文还包含既有的 --language 用法示例(/understand --language zh,支持 en(默认)、zhzh-TWjakoru)。随后的提交:

git add README.md
git commit -m "docs: note first-run conversation-language auto-detection"

Task 3:手工验证(无自动化测试钩子)

计划明确指出:技能提示词行为没有单元测试框架可挂(这与既有 --language 参数的验证方式一致),因此验证方式是对照编辑后的 3.6 文本逐场景推演,确认提示词逻辑产生正确的 $OUTPUT_LANGUAGEconfig.json 写入,并把每个场景的结果记录进 PR 描述。五个场景与预期:

# 情境(全新项目,无 config.json) 预期结果
1 中文对话,运行 /understand 确认门出现 → 用户确认 → $OUTPUT_LANGUAGE=zhconfig.json 写入 "outputLanguage":"zh"
2 同项目再运行(config 已有 zh 无门(已存配置优先);直接生成 zh
3 英语对话,运行 /understand 无门;$OUTPUT_LANGUAGE=en;英语输出(无回归)
4 全新项目传 --language ja 无门(参数优先);config.json 写入 "outputLanguage":"ja"
5 检测到 zh,用户在门里输入 en $OUTPUT_LANGUAGE=enconfig.json 写入 "outputLanguage":"en"

此外还有两个必须确认的性质:

  • 无回归不变量(计划称之为"上游接受与否最关键的一条属性"):重读编辑后的代码块,确认不存在任何"纯英语对话 + 无参数 + 无配置却弹出提示"的路径——场景 3 必须保持静默。
  • 可选的实盘冒烟测试:在一个丢弃型仓库(无 .understand-anything/config.json)中用中文简短对话、运行 /understand,确认确认门出现、确认后 config.json 被写入 outputLanguage: "zh"。若完整分析运行成本太高可跳过——Step 1 的推演对 PR 已足够。

边缘情形的完整行为在设计文档中有对照表(检测不确定/混合语言 → 按 en 静默处理,绝不为猜测而阻塞;非交互调用 → 回退到检测值并附单行提示;自动更新钩子路径不受影响——它不复用语言解析;检测语言有 locales/<lang>.md 文件 → Phase 4 既有注入直接生效,如 locales/zh.md 提供的标签命名与摘要风格指引;没有 locale 文件 → $LANGUAGE_DIRECTIVE 照常生效,走"静默跳过"行为)。

Task 4:开 PR 与上游话术

推分支后创建 PR,计划给出了完整的 PR 描述模板,其结构值得借鉴(面向"任何新增交互都会被 reviewer 警惕"这一上游现实):

  • What:首次 /understand(无 --language、无已存 outputLanguage)时,技能检测对话语言,且仅在非英语时请求确认或覆盖;选择持久化到 .understand-anything/config.json,门永不再触发。
  • Why:输出语言默认静默为英语,中文用户要付完全量分析成本才发现语言不匹配,还得带 --language zh 重跑。
  • Zero change for English users:确认门只在"检测到非英语且尚未选择过语言"时出现;英语对话走与之前完全相同的静默 en 路径;--language 参数与已存配置均优先于检测。
  • Scope:只动了 SKILL.md 步骤 3.6 加一句 README;无代码/schema 改动;其他技能与自动更新钩子未动(是另外的已知缺口);非交互调用降级为检测值 + 提示。

计划自检:覆盖度、占位符与命名一致性

计划文档末尾附了一份 Self-Review,是这类"提示词即代码"改动质量的重要保障,包含三个维度:

  1. Spec 覆盖:解析链(参数 > 配置 > 检测 > en)、"仅非英语 + 仅首次"的门、含 en 在内的值持久化、不确定/混合对话按 en 静默处理、非交互回退、自动更新钩子不受影响、locales/<lang>.md 已接线无需改动、README 一句话、五个手工验证场景——逐条对应到 Task 1–3 的具体步骤,全部打勾。
  2. 占位符扫描:文中没有留下模糊的 "TBD/TODO/handle edge cases",每个边缘情形都有明确解析后的行为;确认门里的 <language>/<code> 是技能运行时要填充的模板占位符,属有意为之,不是计划缺口。
  3. 命名一致性:全文四个变量名保持稳定——$OUTPUT_LANGUAGE(解析结果)、$DETECTED_LANG(检测值)、outputLanguage(config 键名)、--language(CLI 参数),且与 SKILL.md 步骤 3.6 中既有命名完全一致。这四个名字在当前仓库中均可检索印证:$OUTPUT_LANGUAGE/$DETECTED_LANG 出现在 SKILL.mdoutputLanguage 出现在 types.tspersistence/index.tsApp.tsx--language 出现在 SKILL.md 的 Options 一节

小结

这份计划展示了 Understand-Anything 中一类值得参考的实现路径:当功能本身是"给模型的指令"而非"给机器的代码"时,一个完整 feature 可以被压缩为对 SKILL.md 单个步骤的块级重写加一句 README 说明,同时用"锚点文本先核对、grep 负断言验证、五场景手工推演 + 无回归不变量"替代不存在的自动化测试钩子,并以持久化契约(config.jsonoutputLanguage 键,读写与容错由 @understand-anything/core 既有代码承担)保证行为跨运行一致。对英语用户零变化、对非英语用户首次确认后零再扰动的双约束,是这类"引入交互"改动通过上游审查的关键设计。

登录后查看全文
热门项目推荐
相关项目推荐