Skip to content

10 · AI Interpreter

Interpreter 的目标不是聊天,而是把模板中的多个 prompt 变量批量求值成结构化 JSON,再安全地回填到笔记字段。

一、功能边界

当满足三项条件时才启用:

text
generalSettings.interpreterEnabled
AND template 中存在 prompt variables
AND 至少一个 enabled model

它不会自动把每个网页发给模型。只有模板显式包含 prompt 且用户启用/触发 Interpreter 时,才构造请求。

二、配置模型

text
Provider
  id, name, baseUrl, apiKey, apiKeyRequired, presetId

ModelConfig
  id, providerId, providerModelId, name, enabled

Provider 描述协议端点与认证,ModelConfig 描述该端点下的具体模型。分离后一个 provider 可以挂多个模型,切模型不复制 API key/base URL。

三、Prompt 收集

collectPromptVariables(template) 扫描:

  • note body;
  • 每个 property value;
  • 当前页面上的 input/textarea(考虑用户编辑后内容)。

正则识别:

liquid
{{ "prompt text" | filters }}
{{ prompt:"prompt text" | filters }}

以 prompt 文本作为 Map key 去重,再编号 prompt_1..N。相同问题在标题和正文出现时只请求一次。

四、Context 的生成

模板可保存独立 context;否则用全局 defaultPromptContext;再否则使用一个默认清洗模板,大致从 fullHtml

  1. 移除导航、footer、script/style 等;
  2. strip 一批结构标签;
  3. strip 属性;
  4. 得到紧凑文本上下文。

Context 本身也走 compileTemplate(),所以可引用 content、selector 或其他变量。

token-counter.ts 在 UI 实时估算 context token,帮助用户发现输入过大。

五、请求的统一目标

不论供应商,请求都要求模型只返回:

json
{
  "prompts_responses": {
    "prompt_1": "Markdown string",
    "prompt_2": "Markdown string"
  }
}

System prompt 强调:

  • 一个 JSON object;
  • 不要前后解释;
  • key 必须沿用给定编号;
  • value 默认是简洁 Markdown。

一次请求携带 context 和 prompts map,避免 N 个 prompt 发 N 次完整正文。

六、供应商方言

sendToLLM() 不是完整通用 SDK,而是按 provider 特征构造非流式请求。

分支URL/认证Body 特点
Hugging FaceBearerOpenAI-like messages,替换 {model-id}
Azure OpenAIapi-keydeployment 在 URL,max_completion_tokens
DeepSeekBearerOpenAI-like,显式关闭 thinking
Gemini nativeX-goog-api-keysystemInstruction + contents.parts + JSON mime
Anthropicx-api-keysystem 独立,anthropic-version,browser access header
PerplexityBearerHTTP-Referer / X-Title
Ollama可无 keyformat: json, num_ctx, 本地 endpoint
默认BearerOpenAI-compatible chat completion

这里以 provider name/baseUrl 做分支,简单直接,但新增同名代理或协议变体时要谨慎。

七、一分钟冷却

模块级:

text
RATE_LIMIT_RESET_TIME = 60_000
lastRequestTime

若距离上次成功请求不足一分钟,直接报还需等待的秒数。它防止用户/auto-run 重复点击产生费用或触发供应商限流。

注意它是当前扩展页面进程内的节流,不是跨设备、跨窗口的全局配额。

八、响应抽取

不同响应体的文本位置不同:

text
Anthropic → content[] 中第一个 type=text block
Gemini    → candidates[0].content.parts[].text
Ollama    → message.content
OpenAI    → choices[0].message.content

Anthropic 可能先返回 thinking block,因此不能假设 content[0] 是最终文本。

九、截断检测

代码统一读取:

text
stop_reason
done_reason
candidates[0].finishReason
choices[0].finish_reason

若是 max_tokenslengthMAX_TOKENS,主动报错,避免把不完整 JSON 当成成功结果保存。错误建议缩短 prompts/context。

十、脏 JSON 防御

模型即使被要求输出 JSON,仍可能:

  • 包 Markdown fence 或解释文字;
  • 使用 curly quotes;
  • 在字符串中放裸 newline;
  • 漏转义 quote;
  • 返回控制字符;
  • 只生成到一半。

parseLLMResponse() 采用多级恢复:

text
1. sanitize 全文后 JSON.parse
2. regex 提取第一个 {...}
3. minimal sanitize 后 parse
4. full sanitize 后 parse
5. 按 prompt_N regex 单独抽取,重建 JSON
6. 仍失败则抛可读错误

这是一种“结构化输出不可信”的防御式解析。

十一、回填流程

解析成功得到:

ts
{
  promptResponses: [
    { key, prompt, user_response }
  ]
}

之后:

  1. replacePromptVariables() 按 prompt 文本/key 查响应;
  2. 应用 prompt 后声明的 filters;
  3. 重新编译或更新 note name/property/body fields;
  4. UI 展示耗时、成功或错误;
  5. 用户仍可在保存前编辑结果。

模型变量如 modelmodelIdmodelProvider 也由独立 processor 替换,便于在笔记中记录生成来源。

十二、UI 生命周期

initializeInterpreter()

  • 清理旧 event listener,防止模板切换后重复触发;
  • 控制 interpreter container 与 button 显隐;
  • 编译并填充 context;
  • 更新 token counter;
  • 只列 enabled models;
  • 验证上次选择仍有效;
  • auto-run 关闭时绑定 click;
  • model change 后保存设置。

事件 listener 放在 WeakMap 中,允许元素回收并支持按 event type 替换。

十三、Auto-run 风险

interpreterAutoRun 会在模板/页面初始化后主动请求。它提升一键体验,但要考虑:

  • 页面切换/refresh 是否重复请求;
  • 60 秒冷却是否造成意外失败;
  • context 是否包含敏感页面内容;
  • provider 是否按 token 收费;
  • 模型输出被截断时 UI 是否阻止保存错误内容。

因此 auto-run 是显式设置,默认关闭更符合隐私与费用预期。

十四、浏览器直连 API 的安全现实

Provider API key 存在扩展设置中,请求直接从浏览器发出。优点:

  • 无官方中转服务器;
  • 用户控制 provider;
  • 本地 Ollama 可完全离线。

限制:

  • 扩展存储/设备同步的密钥暴露面;
  • 供应商必须允许 browser extension origin/CORS;
  • Anthropic 需要 dangerous direct browser access header;
  • Ollama 403 需要配置 OLLAMA_ORIGINS
  • 无服务端秘密管理、配额或审计。

这是“本地优先、用户自带 key”产品的典型取舍。

十五、可改进的协议抽象

当前 if/else 能快速支持常见供应商,但方言增长后可抽象:

text
ProviderAdapter
  buildRequest(model, system, context, prompts)
  extractText(response)
  extractFinishReason(response)
  explainError(status, body)

是否值得重构取决于 provider 数与差异增长。现有代码的优势是所有请求形态在一个函数里可见,调试直接。

十六、失败检查表

Interpreter 出错时按顺序排查:

  1. provider 是否找到、model.providerId 是否正确;
  2. enabled model 与 interpreterModel 是否一致;
  3. apiKeyRequired 与 key;
  4. baseUrl 是否包含应替换 placeholder;
  5. 浏览器 CORS / Ollama origins;
  6. 是否处于 60 秒冷却;
  7. token counter 是否过大;
  8. finishReason 是否截断;
  9. 响应文本是否包含 prompts_responses
  10. prompt key 是否与收集编号一致。

十七、本章检查点

  • 能解释 prompt 为什么批量发送;
  • 能区分 Provider 与 ModelConfig;
  • 能列出主要供应商的请求/响应差异;
  • 能描述五级脏 JSON 恢复;
  • 能说明 auto-run、API key、CORS 的风险;
  • 能追踪 prompt response 如何重新进入模板字段。

基于 Obsidian Web Clipper 1.7.1 源码快照的独立学习笔记