10 · AI Interpreter
Interpreter 的目标不是聊天,而是把模板中的多个 prompt 变量批量求值成结构化 JSON,再安全地回填到笔记字段。
一、功能边界
当满足三项条件时才启用:
generalSettings.interpreterEnabled
AND template 中存在 prompt variables
AND 至少一个 enabled model它不会自动把每个网页发给模型。只有模板显式包含 prompt 且用户启用/触发 Interpreter 时,才构造请求。
二、配置模型
Provider
id, name, baseUrl, apiKey, apiKeyRequired, presetId
ModelConfig
id, providerId, providerModelId, name, enabledProvider 描述协议端点与认证,ModelConfig 描述该端点下的具体模型。分离后一个 provider 可以挂多个模型,切模型不复制 API key/base URL。
三、Prompt 收集
collectPromptVariables(template) 扫描:
- note body;
- 每个 property value;
- 当前页面上的 input/textarea(考虑用户编辑后内容)。
正则识别:
{{ "prompt text" | filters }}
{{ prompt:"prompt text" | filters }}以 prompt 文本作为 Map key 去重,再编号 prompt_1..N。相同问题在标题和正文出现时只请求一次。
四、Context 的生成
模板可保存独立 context;否则用全局 defaultPromptContext;再否则使用一个默认清洗模板,大致从 fullHtml:
- 移除导航、footer、script/style 等;
- strip 一批结构标签;
- strip 属性;
- 得到紧凑文本上下文。
Context 本身也走 compileTemplate(),所以可引用 content、selector 或其他变量。
token-counter.ts 在 UI 实时估算 context token,帮助用户发现输入过大。
五、请求的统一目标
不论供应商,请求都要求模型只返回:
{
"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 Face | Bearer | OpenAI-like messages,替换 {model-id} |
| Azure OpenAI | api-key | deployment 在 URL,max_completion_tokens |
| DeepSeek | Bearer | OpenAI-like,显式关闭 thinking |
| Gemini native | X-goog-api-key | systemInstruction + contents.parts + JSON mime |
| Anthropic | x-api-key | system 独立,anthropic-version,browser access header |
| Perplexity | Bearer | HTTP-Referer / X-Title |
| Ollama | 可无 key | format: json, num_ctx, 本地 endpoint |
| 默认 | Bearer | OpenAI-compatible chat completion |
这里以 provider name/baseUrl 做分支,简单直接,但新增同名代理或协议变体时要谨慎。
七、一分钟冷却
模块级:
RATE_LIMIT_RESET_TIME = 60_000
lastRequestTime若距离上次成功请求不足一分钟,直接报还需等待的秒数。它防止用户/auto-run 重复点击产生费用或触发供应商限流。
注意它是当前扩展页面进程内的节流,不是跨设备、跨窗口的全局配额。
八、响应抽取
不同响应体的文本位置不同:
Anthropic → content[] 中第一个 type=text block
Gemini → candidates[0].content.parts[].text
Ollama → message.content
OpenAI → choices[0].message.contentAnthropic 可能先返回 thinking block,因此不能假设 content[0] 是最终文本。
九、截断检测
代码统一读取:
stop_reason
done_reason
candidates[0].finishReason
choices[0].finish_reason若是 max_tokens、length 或 MAX_TOKENS,主动报错,避免把不完整 JSON 当成成功结果保存。错误建议缩短 prompts/context。
十、脏 JSON 防御
模型即使被要求输出 JSON,仍可能:
- 包 Markdown fence 或解释文字;
- 使用 curly quotes;
- 在字符串中放裸 newline;
- 漏转义 quote;
- 返回控制字符;
- 只生成到一半。
parseLLMResponse() 采用多级恢复:
1. sanitize 全文后 JSON.parse
2. regex 提取第一个 {...}
3. minimal sanitize 后 parse
4. full sanitize 后 parse
5. 按 prompt_N regex 单独抽取,重建 JSON
6. 仍失败则抛可读错误这是一种“结构化输出不可信”的防御式解析。
十一、回填流程
解析成功得到:
{
promptResponses: [
{ key, prompt, user_response }
]
}之后:
replacePromptVariables()按 prompt 文本/key 查响应;- 应用 prompt 后声明的 filters;
- 重新编译或更新 note name/property/body fields;
- UI 展示耗时、成功或错误;
- 用户仍可在保存前编辑结果。
模型变量如 model、modelId、modelProvider 也由独立 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 能快速支持常见供应商,但方言增长后可抽象:
ProviderAdapter
buildRequest(model, system, context, prompts)
extractText(response)
extractFinishReason(response)
explainError(status, body)是否值得重构取决于 provider 数与差异增长。现有代码的优势是所有请求形态在一个函数里可见,调试直接。
十六、失败检查表
Interpreter 出错时按顺序排查:
- provider 是否找到、model.providerId 是否正确;
- enabled model 与 interpreterModel 是否一致;
- apiKeyRequired 与 key;
- baseUrl 是否包含应替换 placeholder;
- 浏览器 CORS / Ollama origins;
- 是否处于 60 秒冷却;
- token counter 是否过大;
- finishReason 是否截断;
- 响应文本是否包含
prompts_responses; - prompt key 是否与收集编号一致。
十七、本章检查点
- 能解释 prompt 为什么批量发送;
- 能区分 Provider 与 ModelConfig;
- 能列出主要供应商的请求/响应差异;
- 能描述五级脏 JSON 恢复;
- 能说明 auto-run、API key、CORS 的风险;
- 能追踪 prompt response 如何重新进入模板字段。