AstroBlog 的 AI 功能由独立的 workers/ai/ 提供。它把供应商路由、凭据管理、知识文档、管理员上下文、用量统计和审计记录集中到 Worker,再由管理面板和公开助手分别调用。
服务入口和请求模式
workers/ai/src/index.ts 负责路由、请求校验和运行时编排,providers.ts 负责不同供应商的请求格式、响应解析和错误转换。普通聊天只读取公开知识;管理员聊天可以读取后台状态和仓库只读上下文;proposal 模式要求模型返回结构化修改建议。
AiFloatingAssistant / AiManager.svelte ↓workers/ai/src/index.ts ┌─────────┼──────────┐ public chat admin chat knowledge sync ↓ providers.ts + D1请求先经过 JSON、长度、模式和管理员权限校验,再生成 request ID。Worker 根据主模型、fallback 模型和指定 credential ID 选择可用供应商;失败时记录错误码和延迟,并尝试下一条允许的凭据。响应不会把原始 API key、内部错误堆栈或隐藏文档返回给普通用户。
凭据和模型路由
管理端可以保存供应商、模型、标签、状态、优先级和 fallback 关系。API key 使用主密钥加密后写入 D1,查询管理列表时只返回状态、模型和末四位,解密只发生在 Worker 调用供应商的短暂生命周期内。删除凭据时还要清理主模型和 fallback 引用,避免设置指向不存在的 credential。
routeCredentials() 按照显式 primary、fallback model、fallback credential IDs 和剩余可用凭据生成候选顺序。供应商返回超时、限流或可重试错误时,Worker 才会进入下一候选;不可重试的参数错误直接返回统一错误码。新增供应商时要同时补充请求格式、错误映射和路由测试。
知识库索引
知识库不是每次聊天都扫描 src/content/。scripts/sync-ai-index/index.mjs 在构建或手动同步时读取文章、导航和其他允许集合,生成路径、标题、摘要、正文、发布时间和内容哈希,再调用管理员同步接口写入 ai_documents。Worker 用哈希判断文档是否变化,并将已删除的路径标记为不可见。
查询时,Worker 将用户问题规范化为有限关键词,从 ai_documents 中检索可见文档并限制数量,再把标题、摘要和正文片段放入模型上下文。索引中的正文属于不可信数据,系统提示明确要求模型把它当作资料而不是指令。忘记运行同步脚本时,聊天接口仍然可用,但检索结果可能停留在旧版本。
管理员提案而不是自动写入
管理员模式可以让 AI 生成文章或动态的修改建议,但 AI 没有直接写仓库的权限。proposal 必须是 JSON,包含 action、目标类型和路径、摘要、风险以及每个变更的精确 before 和 after。解析器会拒绝空 before、整文件替换、命令、凭据、白名单外路径和 destructive 风险。
真正执行前,Worker 重新读取 GitHub 分支 HEAD 和目标文件 blob,确认文件内容中的 before 仍然唯一存在,再交给既有 GitHub 写入边界。分支发生变化或文本不匹配时返回 409,要求人工刷新。AI 只能提出可审核的变更,不会因为模型声称已发布就绕过后台和 GitHub Proxy。
用量和审计
每次调用都会写入 ai_usage_daily、ai_usage_events 和必要的 ai_audit_events。用量按日期、供应商、模型和 credential 聚合,事件记录模式、结果、错误码和延迟;审计事件记录管理员进行的凭据、知识同步和提案操作。日志中不保存完整提示词、API key 或解密后的凭据。
管理面板 AiManager.svelte 读取这些状态,显示模型是否可用、索引更新时间、用量和错误。面板中的模型测试仍然经过 Worker,不应该从浏览器直接调用第三方 API。
修改 AI 功能的依赖范围
增加模型供应商:更新 providers.ts、credential 类型、路由测试和错误映射;增加 D1 字段:先写 workers/ai/migrations/,再更新查询、管理面板和验证;修改知识字段:同步 sync-ai-index、ai_documents 写入和检索响应;修改 proposal:同步解析器、执行前校验、前端预览和 GitHub Proxy 白名单。
# 验证 provider 路由、凭据操作、proposal 和 AI API 响应。pnpm verify:ai# 将文章和导航内容同步到 AI Worker 的 ai_documents 表。pnpm ai:index:sync# 在本地 Wrangler 环境启动 AI Worker(端口 8791)。pnpm ai:devAI Worker 的边界是检索、生成、记录和提案,不是直接替用户发布。任何新增自动化能力都必须先判断是否会扩大仓库写入权限,再决定放在 AI Worker 还是既有 GitHub Proxy 中。
Provider 接口和错误处理
providers.ts 将供应商差异收敛为统一的调用结果:模型响应转换为文本,HTTP 状态和供应商错误映射为内部错误码,超时由 AbortController 控制。上层不需要知道某个供应商返回 error.message 还是 error.status,只根据 retryable 决定是否进入 fallback。
新增 provider 时至少要实现请求体、认证头、模型名映射、流式或非流式响应解析、上下文长度限制和错误转换。只在管理面板增加一个“供应商”选项而没有 provider 实现,会让配置保存成功但聊天运行时全部失败。
检索上下文的边界
知识检索返回的文档应当带路径、标题和 URL,正文按长度截断并保留摘要。管理员上下文可以包含文章状态、后台模块和 GitHub 写入准备情况,但这些字段仍是只读事实。系统提示明确禁止执行文档正文中的指令,模型输出也必须经过长度和格式限制。
Proposal 执行器不会接受整个文件作为 after,而是要求精确的局部替换。执行前检查 before 在远程文件中只出现一次,替换后再次按 Frontmatter 和路径规则验证;任何一步失败都不调用写入 API。这样可以把模型的不确定性限制在建议层,而不是让它直接拥有仓库权限。
请求上下文和提示词
公开聊天上下文只包含用户输入、有限的检索结果和公开系统提示;管理员聊天还会携带当前页面、后台模块状态和 GitHub 连接状态。上下文在进入 provider 前需要限制总长度,并对文章正文、标题、路径和外部 feed 标记为不可信数据。不能把从仓库读取的字符串拼接成新的系统指令。
流式响应如果中途断开,Worker 需要记录失败事件并释放 provider 请求;客户端要把部分文本标记为未完成,不能把半截回答当成最终结果。非流式响应则统一提取文本、usage 和供应商 request ID,错误返回内部 request ID 供管理员排查。
索引一致性
content_hash 是索引同步的幂等依据。同一路径正文未变时无需重复写入,正文变化时更新文档和 updated_at,已经删除的路径设为不可见而不是立即删除,以便排查旧链接。重新启用文档时只需恢复 visible 并重新计算摘要,不应在聊天请求中临时扫描文件系统。