OpenSEO Onboarding Agent 实现方案:无 Agent 框架的引导式 SEO 激活管线
【免费下载链接】open-seoOpen source alternative to Semrush and Ahrefs项目地址: https://gitcode.com/GitHub_Trending/op/open-seo
导读
本文基于 OpenSEO 仓库中的技术设计文档 specs/0006-onboarding-agent-implementation.md,详细拆解「Onboarding Agent」的落地实现:一个确定性 seed 函数负责分析新用户真实网站并生成首份 SEO 策略,配合普通流式聊天让用户在线打磨这份策略,最终沉淀为项目可复用的Project Context。读完本文,你将理解 OpenSEO 如何在不引入任何 Agent 框架、不做持久化执行的前提下,用fetch抓取、Browser Rendering 抓取、两次 DataForSEO 付费调用和一次 OpenRouter 合成调用,把注册用户转化为激活用户,并掌握其数据模型、计量护栏、MCP 暴露方式与分阶段构建顺序。
文档状态说明:本文主体是 0006 号规格书描述的原始技术方案(Status: Accepted, June 2026),该文档自身标注了「shipped implementation diverged from the plan」的更新说明;文末会结合仓库当前源码,单独对比实际交付形态与原始计划的差异。
一、为什么需要 Onboarding Agent
Onboarding Agent 是 OpenSEO 的最高激活优先级功能。产品规格 specs/0005-onboarding-agent.md 开宗明义:把注册变成激活。当新用户完成注册后,一个「agent」实时分析他们真实的网站,随后产出一份量身定制的 SEO 策略。
关键设计理念是:策略本身免费且自洽,付费墙落在「执行策略」上——排名追踪、内容简报、持续 coach 都是付费面。这份策略会成为项目持久的「Project Context」,既能在应用内阅读,也能通过 MCP 暴露给用户的 Claude/Codex 等客户端读取。
规格文档特别澄清了「agent」一词的含义:它不是 LLM agent 循环,而是一条实时旁白式引导管线。步骤大多是确定性的(抓取、拉取关键词数据),只在最后用一次 LLM 调用合成策略叙事。所谓「agent 感」来自两步:实时流式播报各阶段进度(「正在读取你的首页……你看起来像 Notion 的竞品……有 4 个关键词在排名,30 个值得瞄准……」),以及最终报告逐 token 流出。v1 不需要 Anthropic SDK 或任何 agent 基础设施。
二、总体架构:两个简单部件,零 Agent 框架
实现计划(TL;DR)将系统收敛为两个互不依赖的部件:
- seed 函数(普通 async 函数):发现 sitemap → 用 Browser Rendering 把 3–5 个页面抓成 markdown → 2 次付费 DataForSEO 调用 → 1 次 OpenRouter 合成调用 → 把结果保存为项目的第一个Project Context版本。在 onboarding 启动时只运行一次。
- 普通流式聊天(Vercel AI SDK
streamText走 OpenRouter):用户提问/打磨;update_project_context工具写入新版本。由一条基于现有 better-auth session 的普通 API 路由承载。
Project Context 是版本化的:不可变 markdown blob 存 R2,D1 里存 append-only 日志。回滚复用先前 blob 的 key。通过 MCP 的get_project_context暴露。
三、为什么不用 Cloudflare Think / Workflows(以及为什么保留 agents 包)
方案演进过程中,作者曾提出用 Cloudflare Project Think + Durable Objects + Workflow 的草案,最终全部放弃,理由在文档中有三条硬核论证:
- 不需要持久化执行。付费的 DataForSEO 服务是 cache-first 的——
getCached先于createDataforseoClient/metering 执行,所以同一域名崩溃后重试会重新命中 12 小时 R2 缓存,不会重复扣费。这直接消除了引入 fibers/Workflows 的唯一理由。 - 不需要 agent 宿主。想要的能力(流式回答、未来的 docs 工具、保存/更新产物)都是普通的
streamText({ tools })就能覆盖。Think 的差异化价值(durable DO sessions、scheduled turns、sub-agents)在这里一个都用不上。 agents包保留,但用途不是 agent 运行时:它被 MCP handler 使用——agents/mcp→createMcpHandler(见 src/server/mcp/transport.ts)。不做版本升级。
方案留有「毕业」路径:只有当某个能力确实需要 durable sessions、schedule(如每周排名追踪)或 sub-agents(按竞品)时,才考虑迁移;seed 函数和聊天路由可以直接平移过去。
四、数据模型:projects 扩展 + append-only 版本日志
4.1projects表新增列
| 列 | 类型 | 默认值/说明 |
|---|---|---|
location_code | int | 默认2840(美国) |
language_code | text | 默认'en' |
onboarding_run_status | text 可空 | running\|complete\|failed |
onboarding_run_at | text 可空 | 运行时间戳 |
新增 location 相关默认值并非孤例:仓库中 src/shared/keyword-locations.ts 及其厂商默认值测试 src/shared/keyword-locations.vendor-defaults.test.ts 也维护了位置/语言默认逻辑。产品规格要求从 Stage 0 的表单收集国家,写入projects后复用于此后所有 DataForSEO 调用。
4.2project_context_versions(新增,append-only 日志)
| 列 | 说明 |
|---|---|
id | 主键 |
project_id | 外键 |
r2_key | 指向 R2 中的 markdown blob |
author | onboarding\|chat\|user |
note | 可空,记录该版本变更说明 |
reverted_from_id | 可空,回滚来源 |
created_at | 创建时间 |
当前版本 = 每个项目最新一行;索引建在(project_id, created_at)上。
4.3 R2 不可变 blob + 写序约束
- 每个版本对应 R2 中一个不可变 markdown blob:
project-context/{projectId}/{versionId}.md。 - 必须先写 R2、再写 D1 行:失败只会留下一个无害的孤儿 blob,绝不会出现指向空内容的行。
- 回滚时插入新行复用目标版本的
r2_key,不产生新 blob。
五、seed 函数逐阶段拆解
runOnboardingSeed({ projectId, organizationId, userId, userEmail, domain })是一条六阶段的确定性管线:
- 准入标记(Admission marker)——原子更新
UPDATE projects SET onboarding_run_status = 'running' WHERE id = ? AND onboarding_run_status IS NULL;仅当恰有一行受影响才继续(否则说明已有 run 在途)。这是至多一次(at-most-once)的并发护栏。 - 发现(Discover)——
fetch()robots.txt + sitemap.xml;抓不到时降级为浅层爬取。产品规格强调:多发现 URL(便宜)、少抓取页面。 - 读取(Read)——通过
BROWSERbinding 顺序抓取 3–5 个关键页面(首页 + 核心产品/导航页)转成 markdown。失败只打标记,绝不中断整个 run。这是全案唯一真正新增的能力:页面 → markdown(Cloudflare Browser Rendering)。规格要求诚实的「我们读不了你的站」提示 + 手动「告诉我们你做什么」兜底,保证流程永不卡死。 - 信号(Signal)——
DomainService.getOverview(总是调用,对应仓库中的 src/server/features/domain/services/DomainService.ts)+ 以抓取主题为种子做关键词研究,两者都带creditFeature: 'onboarding';getSuggestedKeywords仅当 overview 显示真实排名时才调用。这与产品规格中的 Stage 3 数据矩阵一致(见下节)。 - 合成(Synthesize)——一次 OpenRouter
generateText/streamText,输入 {profile + markdown + signal},输出策略 markdown:定位陈述、3–5 个主题簇、起始关键词表(量/难度)、按优先级排序的「下一步行动」清单。规格强调:冷启动(零排名)是独立开发者 ICP 的默认场景而非边缘情况,必须能产出可信策略。 - 持久化(Persist)——写入 v1 版本(author
onboarding),并把onboarding_run_status置为'complete'。任何异常抛出则置为'failed'(可重跑,重试受缓存保护且廉价)。
Stage 3 信号数据(v1 保持最小)
Stage 3 是原型条件式的:廉价探测站点类型,据此调整叙事目标——v1 中原型只影响「旁白」,不放大 API 调用矩阵。基线两次调用:
| 调用 | 端点 | 用途 | 时机 |
|---|---|---|---|
domain_rank_overview | /v3/dataforseo_labs/google/domain_rank_overview/live | 流量 + 排名关键词数;原型探测器 | 总是 |
keyword_ideas | /v3/dataforseo_labs/google/keyword_ideas/live | 起始关键词表,以抓取主题为种子 | 总是 |
ranked_keywords | /v3/dataforseo_labs/google/ranked_keywords/live | 当前已排名关键词 | 仅 overview 显示真实排名时(新站通常跳过) |
原型(v1 仅影响叙事):new/pre-traffic(新站/无流量)、established content site(成熟内容站)、local business(本地商家)、SaaS/product(核心 ICP)。
六、计量与护栏:'onboarding' CreditFeature 与 skipBalanceAssert
方案的关键约束:整个 run 免费(付费墙在其后),因此每次免费 onboarding 的成本必须封顶。文档给出预算:
- DataForSEO:约 $0.04–0.08(2 次 Labs live 调用;按响应信封中的真实
cost字段计量,可硬性封顶)。 - Browser Rendering 抓取:可忽略。
- LLM 合成:约 $0.05–0.15(一次调用,输入几页 markdown)。
- 合计约 $0.10–0.25 / 次 onboarding。
计量机制在原计划中保持不变:付费调用仍走现有createDataforseoClientseam(对应仓库 src/server/lib/dataforseo/client.ts),新增'onboarding'CreditFeature 为支出打标签;计量特征清单集中在 src/shared/billing-credit-features.ts,被研究、排名追踪、反链、域服务等共享。关键设计:
skipBalanceAssert标志:让零余额的新注册用户在 Signal 阶段不因INSUFFICIENT_CREDITS死掉,但仍调用trackDataforseoCost——支出被计量、但不被余额门禁拦截。- 自托管已完全跳过 Autumn(计费服务),故该逻辑对 self-host 无影响。
配套护栏(产品规格):每人一次 run、结果硬缓存、邮箱验证门禁 Stage 3(挡住 DataForSEO 花销的滥用攻击)。
七、聊天:POST /api/onboarding/chat
POST /api/onboarding/chat由 TanStackcreateFileRoute服务端 handler 承载:
- 认证:从请求解析 better-auth session;断言用户拥有
projectId(通过ProjectRepository.getProjectForOrganization做组织作用域,对应仓库 src/server/features/projects/repositories/ProjectRepository.ts)。不加新传输层,因此不增加认证攻击面。 - 流式响应:
streamText({ model: openrouter(MODEL), system: seededWithContext, messages, tools: { update_project_context } }),以 UI 消息流形式返回。 update_project_context({ markdown, note })写入新版本(authorchat)。v1 直接应用;append-only 日志保证任何不满意的改动都「一个 revert 之遥」。
客户端:AI SDKuseChat({ api: '/api/onboarding/chat' })。策略渲染在聊天上方;升级 CTA 是 UI 状态,在 v1 存在后出现。这也对应产品规格的付费墙设计:免费给完整策略(定位、主题、封顶约 15–20 个起始关键词、「下一步」清单);执行层面(追踪建议关键词的排名、完整关键词扩展、按主题的内容简报、持续的seo-coach)才被门禁。GSC 留在付费墙后,onboarding 期间不提示连接 GSC,避免「连完就撞付费墙」的诱饵式体验。
八、认证与邮箱验证
- 在
EnsuredUserContext上暴露emailVerified(来源session.user.emailVerified);自托管视为已验证。 - seed 在付费的「Signal」阶段之前断言
emailVerified;Discover + Read(免费)允许未验证用户运行。
九、MCP 暴露:get_project_context
get_project_context(projectId)—— 只读工具(readOnlyHint: true),走withMcpProjectAuth(见 src/server/mcp/project-auth.ts);解析最新版本 → R2 get → 返回 markdown。list_project_context_versions是 fast-follow。
值得注意的是,当前仓库已经实现了这一工具的成熟形态:src/server/mcp/tools/project-context.ts 中的get_project_context并非按「R2 markdown blob 版本」实现,而是基于分节式 Project Context:business_overview(业务概览)、current_goal(当前目标)、positioning(定位)、writing_preferences(写作偏好)四个类型化 section,外加 custom sections、competitors(竞品)、key pages(关键页面)与 research log(研究日志),schema 定义在 src/types/schemas/projectContext.ts,作者枚举为user|sam|mcp。服务层 src/server/features/project-context/services/ProjectContextService.ts 负责读取、批量应用补丁(applyContextUpdates,经 src/db/runBatch.ts 原子批处理)以及渲染统一 markdown digest(renderProjectContextMarkdown)。从该实现看,MCP 工具返回的text字段承载渲染好的 markdown,structuredContent提供结构数据,并带项目页跳转 meta。
产品规格中还规划了更进一步的 MCPresource(自动载入 agent 上下文),v1 只做 tool。
十、本地测试:真实服务商,无 fixture 系统
本地测试策略刻意不用 fixture provider,而是直接打真实服务商:
- 新增唯一一个 key:
OPENROUTER_API_KEY写入.env.local;wrangler login获取BROWSERbinding 权限(dev 下remote: true)。 - DataForSEO 凭据沿用现有账号;缓存让重复运行免费(12h R2 缓存)。
- 用 Playwriter skill 驱动真实 onboarding UI,逐步截图。测试域名:
openseo.so。
十一、构建顺序(stacked PRs)
- Foundation(基础)——schema + 迁移(
projects新列、project_context_versions);emailVerified上 context;'onboarding'CreditFeature + 标签 +skipBalanceAssert标志;国家 →location_code映射。无行为变更。 - Stage 0 表单——域 + 国家合并为一步,写入
projects。 - Project Context 存储——R2 blob helper + versions 仓库 +
get_project_contextMCP 工具。 - 抓取 + seed + 合成——
BROWSERbinding、scrape-to-markdown、seed 函数、OpenRouter 依赖、「生成中 → 策略」UI。 - 聊天——
/api/onboarding/chat+update_project_context工具 +useChatUI + revert。
(没有 fixture-provider PR——直接对真实服务商测试。)
十二、实际交付 vs 原始计划
文档顶部的 Update 注释(June 2026)明确记录了计划与交付的分歧,仓库源码可以印证最终形态:
- 交付形态:不再有确定性的 seed + 合成管线,也没有
claimRun运行状态护栏与skipBalanceAssert绕过;取而代之的是按需聊天——Sam 调用read_website与get_seo_metrics两个工具、在流中亲自撰写策略。Sam 的实现集中在 src/server/features/sam/SamChatAgent.ts、工具注册在 src/server/features/sam/samChatTools.ts、技能清单在 src/server/features/sam/samSkills.ts。 - 持久化与 MCP 工具:计划中的
project_context_versions存储 + R2 版本化,以及get_project_contextMCP 工具,曾被推迟到后续 PR——但从仓库现状看,get_project_context/update_project_context已作为分节式 Project Context 落地(见第九节)。 - 计量:onboarding 花销(DataForSEO和LLM tokens)通过正常余额门禁从组织的 onboarding-plan trial credits 中扣减。
- 产品侧:0005 号规格同样标注交付偏移——策略展示在聊天中,Sam 使用
read_website+get_seo_metrics按需分析,而非分阶段「synthesize → persist」管线。
十三、Out of scope(v1)
原计划明确排除:Think/DO/Workflows、sub-agents、计划性排名追踪、GSC 增强策略、多语言、自动内容生成、MCP contextresource(仅 tool)。产品规格的 v1 排除项还包括:会话式/迭代式 agent 循环、竞品与本地商家分析、多语言策略、自动内容生成——这些将作为核心激活循环验证后的增量改进。
参考路径速查
- 技术实现计划:specs/0006-onboarding-agent-implementation.md|产品规格:specs/0005-onboarding-agent.md
- Sam 聊天实现:src/server/features/sam/SamChatAgent.ts、src/server/features/sam/samChatTools.ts
- MCP Project Context 工具:src/server/mcp/tools/project-context.ts、src/server/mcp/project-auth.ts
- Project Context 服务与 schema:src/server/features/project-context/services/ProjectContextService.ts、src/types/schemas/projectContext.ts
- 计量与 DataForSEO:src/shared/billing-credit-features.ts、src/server/lib/dataforseo/client.ts
- 位置/语言默认值:src/shared/keyword-locations.ts
【免费下载链接】open-seoOpen source alternative to Semrush and Ahrefs项目地址: https://gitcode.com/GitHub_Trending/op/open-seo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考