news 2026/9/11 2:46:36

StaffML Interviewer Worker 部署实战:将 Ask Interviewer 面板背后的 LLM 网关发布到 Cloudflare

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
StaffML Interviewer Worker 部署实战:将 Ask Interviewer 面板背后的 LLM 网关发布到 Cloudflare

StaffML Interviewer Worker 部署实战:将 Ask Interviewer 面板背后的 LLM 网关发布到 Cloudflare

【免费下载链接】cs249r_bookMachine Learning Systems项目地址: https://gitcode.com/GitHub_Trending/cs/cs249r_book

本指南完整讲解如何将 StaffML(本仓库interviews/下的机器学习系统面试练习应用)的Ask Interviewer Worker首次部署到 Cloudflare,涵盖 KV 命名空间创建、Wrangler 配置、冒烟测试、客户端接线,以及后续通过适配器模式接入 Groq / OpenAI / Anthropic / Gemini / OpenRouter 甚至本地自托管模型的完整升级路径。读完你不仅能独立完成一次零成本部署,还能掌握其基于 KV 的限流器、服务端强制的苏格拉底式系统提示词、优先级降级链与 CORS 收敛等生产级运维细节。文中所有命令、配置与代码均以当前仓库实际内容为准。

Worker 是什么:一个多 Provider 的 LLM 网关

StaffML 的 Mock Interview 模式中有一个 "Ask Interviewer" 面板:候选人可以就题目场景向"面试官"提出澄清性问题(约束、规模、延迟预算、SLO、流量模式、硬件、团队规模、时间线等)。这个 Worker 就是面板背后的边缘函数——它接收question + context,用一段服务端强制的苏格拉底式系统提示词约束模型"只回答澄清问题、绝不替候选人解题",然后把请求转发给优先级链上第一个可用的 LLM Provider,并返回带厂商归属与隐私声明的 JSON。

从源码看,整个 Worker 是一个单一文件实现(约 750 行,见 interviews/staffml/worker/src/index.ts),采用适配器(Adapter)模式:

  • 六个内置适配器:groq → openai → anthropic → gemini → openrouter → cf-workers-ai(源码中的适配器注册表);
  • 四种请求形状(RequestShape):openai-compatanthropicgeminicf-workers-ai(类型定义);
  • Cloudflare Workers AI 始终作为兜底:即使没有任何 API Key,也总能以 Llama 3.1 8B 免费应答;
  • KV 后端限流器:每 IP 每小时 / 每 IP 每天 / 全局每天 三层计数;
  • 三个公开端点:GET /healthPOST /askPOST /waitlist(另有/interview现场面试指挥端点,见下文)。

Worker 的部署配置见 interviews/staffml/worker/wrangler.toml,依赖声明见 interviews/staffml/worker/package.json(Node >= 22,Wrangler ^4.120.1)。

部署前提

  • 一个免费的 Cloudflare 账号;
  • 本地安装 Node.js 18+(注意仓库内 worker/package.json 的engines字段要求Node >= 22,请以实际仓库要求为准);
  • 大约 10 分钟;
  • 成本:Cloudflare 免费额度内$0/月

首次部署:五步上线

第 1 步:安装 Wrangler 与依赖

cd interviews/staffml/worker npm install npx wrangler login

wrangler login会打开浏览器授权 Wrangler 访问你的 Cloudflare 账号。仓库的 package.json 已内置deploydevtailtypes四个脚本,对应wrangler deploywrangler devwrangler tailwrangler types

第 2 步:创建限流 KV 命名空间

npx wrangler kv namespace create RATE_LIMIT_KV

Wrangler 语法提醒:旧文档与旧版 Wrangler v2 使用带冒号的kv:namespace。Wrangler v3.60+ 改用空格分隔的子命令:kv namespace createkv key list等。如果看到Unknown arguments: kv:namespace,升级 Wrangler 或改用上面这种新语法。

命令输出类似:

🌀 Creating namespace with title "staffml-interviewer-RATE_LIMIT_KV" ✨ Success! Add the following to your configuration file in your kv_namespaces array: { binding = "RATE_LIMIT_KV", id = "abc123def456..." }

复制id,粘贴进 interviews/staffml/worker/wrangler.toml 的kv_namespaces数组,替换占位符:

[[kv_namespaces]] binding = "RATE_LIMIT_KV" id = "abc123def456..." # ← 粘贴你的真实 id

仓库当前已提交了真实 id(a510cc7f791c40ffa28d19cf809c0e28),自有部署请替换为你新建的 id。

第 2b 步:创建 waitlist KV 命名空间(可选但建议)

POST /waitlist端点把付费意愿调查的提交写入第二个独立的 KV 命名空间,与热路径上的限流计数器隔离(见 源码注释)。如果跳过此步,Worker 仍能正常启动,但/waitlist会返回 503,客户端 UI 会透明地降级为mailto:邮件链接,不会丢失任何提交。

npx wrangler kv namespace create WAITLIST_KV

同样把新id粘贴进wrangler.toml

[[kv_namespaces]] binding = "WAITLIST_KV" id = "xyz789abc012..." # ← 粘贴你的真实 id

事后读取 waitlist 数据(刻意不提供管理端点,用wrangler命令行拉取可以最小化 Worker 的攻击面):

npx wrangler kv key list --binding WAITLIST_KV npx wrangler kv key get --binding WAITLIST_KV "wl:2026-04-08T12:34:56.000Z:abc123..."

从源码看,waitlist 记录以wl:${ISO时间戳}:${ipHash}为键(时间戳在前,便于字典序按新旧排列;ipHash 后缀保证同一毫秒内键唯一),值是对邮箱、wouldPay(0–500)、need、提交时间、IP 哈希、User-Agent 的 JSON 序列化(handleWaitlist 实现)。邮箱采用宽松的 RFC 风格校验(looksLikeEmail),IP 以SHA-256(ip + 当日盐)截断 16 位十六进制存储,既不落库原始 IP,又能做当日去重。

第 3 步:部署 Worker

npx wrangler deploy

Wrangler 会打印部署 URL,形如:

Published staffml-interviewer https://staffml-interviewer.your-subdomain.workers.dev

复制这个 URL,下一步要用。仓库的 wrangler.toml 中workers_dev = true,保留了 workers.dev URL 作为回退;同时配置了两条自定义路由mlsysbook.ai/api/staffml-interviewermlsysbook.ai/api/staffml-interviewer/*(分别覆盖裸路径与通配子路径)。Worker 在入口会剥离/api/staffml-interviewer前缀,使/health/ask/waitlist的路由匹配在两种 URL 形态下完全一致(源码中的前缀剥离逻辑)。你自己部署时默认只有 workers.dev URL,自定义路由需自行在 Cloudflare 配置 zone 路由。

第 4 步:冒烟测试

curl https://staffml-interviewer.your-subdomain.workers.dev/health

预期响应:

{ "ok": true, "providers": ["cf-workers-ai"], "waitlist": true }

解读:

  • providers中出现cf-workers-ai,说明Workers AI 绑定已生效,Worker 正在以默认的 Llama 3.1 8B 兜底模型运行;
  • "waitlist": true表示WAITLIST_KV绑定已配置;false意味着/waitlist会返回 503,直到你补齐命名空间。

再实测一个真实提问:

curl -X POST https://staffml-interviewer.your-subdomain.workers.dev/ask \ -H "Content-Type: application/json" \ -d '{ "question": "what is the typical latency budget?", "context": "Real-time inference for an ad ranking model" }'

返回的 JSON 包含answerprovidervendorLabelmodelLabelprivacyNote五个字段(对应 AskResponse 接口)。vendorLabel/modelLabel/privacyNote来自适配器配置,前端面板会原样展示为模型归属与隐私声明。

/ask端点的输入约束(来自 请求校验代码):

字段约束
question必填字符串,≤ 1000 字符
context可选,≤ 4000 字符
history可选,最多 16 条(8 轮问答),每条 ≤ 1000 字符
mode"interview"(默认,苏格拉底式)/"study"(家教讲解模式)
canonicalAnswer仅在study模式下被接受(≤ 4000 字符);interview模式下被静默丢弃,防止恶意客户端借此泄露答案

请求体还有 16 KB 的硬性上限:先按Content-Length头做廉价预检,再对实际解码后的字节数做权威校验(因为客户端可能省略该头或使用 chunked 传输),超限返回 413,Content-Type非 JSON 返回 415,JSON 非法返回 400(parseJsonRequest 实现)。

第 5 步:把 StaffML 客户端接到 Worker

在 StaffML 的构建环境中(本地npm run dev或生产 CI)设置环境变量:

export NEXT_PUBLIC_INTERVIEWER_ENDPOINT=https://staffml-interviewer.your-subdomain.workers.dev

或写入interviews/staffml/.env.local

NEXT_PUBLIC_INTERVIEWER_ENDPOINT=https://staffml-interviewer.your-subdomain.workers.dev

然后重新构建 StaffML。客户端组件 interviews/staffml/src/components/AskInterviewer.tsx 会在运行时自动探测该变量:未设置时面板进入JOURNAL 模式(只记录澄清问题、不发 AI 调用);设置后进入HOSTED 模式(每次澄清都 POST 到 Worker);调用失败(限流、503、网络错误)时进入FALLBACK 模式——内联显示友好错误,并突出 "Copy as prompt" 按钮作为万能安全网(该按钮始终可用,且把苏格拉底提示词一并嵌入复制内容,用户粘贴到任意 LLM 都能获得同等约束)。该组件内置的默认端点是https://mlsysbook.ai/api/staffml-interviewer,CORS 白名单默认已放行localhost:3000/3001/3002等开发端口。

升级模型:适配器模式让加 Provider 成为一条命令

Worker 的核心设计是一条命令切换 Provider——无需改代码、无需重新部署体操。背后的机制是:orderedAdapters(env)每次请求时PROVIDER_PRIORITY顺序遍历适配器注册表,只要某个适配器的 API Key 存在于环境变量中就把它纳入候选链;第一个候选成功即返回,失败则自动落到链上下一个(优先级排序与降级逻辑)。

Groq(推荐的下一步升级——Llama 3.1 70B,免费额度)

  1. 在 Groq 控制台申请免费 Key;

  2. 运行:

    cd interviews/staffml/worker npx wrangler secret put GROQ_API_KEY
  3. 粘贴 Key。此后每个请求都会优先走 Groq,出错时回退到 Cloudflare Workers AI。

验证:

curl https://staffml-interviewer.your-subdomain.workers.dev/health

"providers"应变为["groq", "cf-workers-ai"]。源码中 Groq 适配器默认模型为llama-3.3-70b-versatilevendorLabel: "Groq",隐私声明"Groq does not train on API inputs."),并支持GROQ_MODEL/GROQ_BASE_URL覆盖(适配器条目)。

OpenAI

npx wrangler secret put OPENAI_API_KEY # 默认模型: gpt-4o-mini

Anthropic Claude

npx wrangler secret put ANTHROPIC_API_KEY # 默认模型: claude-3-5-haiku-latest

Anthropic 走专用的anthropic请求形状:系统提示词放在独立的system字段、要求 user/assistant 严格交替,Worker 会做同角色合并、剔除 stray system 消息、并在必要时前置一条(begin)user 消息以满足协议要求(callAnthropic 实现)。

Google Gemini

npx wrangler secret put GEMINI_API_KEY # 默认模型: gemini-1.5-flash # 注意: Google 免费 Gemini API 可能使用提示词改进产品, # 这一点会通过面板展示的 privacyNote 明确告知用户。

Gemini 同样需要 user/model 交替,Worker 把 assistant 角色映射为model,系统提示词放入systemInstruction(callGemini 实现)。

OpenRouter(数十种模型任选)

npx wrangler secret put OPENROUTER_API_KEY # 默认模型: meta-llama/llama-3.1-70b-instruct # 换模型: npx wrangler secret put OPENROUTER_MODEL

本地自托管(Ollama、vLLM、llama.cpp server)

npx wrangler secret put OPENAI_API_KEY # 任意非空字符串 npx wrangler secret put OPENAI_BASE_URL # http://your-host:11434/v1 npx wrangler secret put OPENAI_MODEL # llama3.1:70b

OpenAI 适配器兼容 OpenAI API 协议,而 Ollama 与绝大多数自托管服务同样说该协议,因此零代码改动即可把请求路由到本地机器。

自定义 Provider 的三步配方

任何实现了 OpenAI 兼容/chat/completions形状的服务(Together AI、DeepSeek、Fireworks、Cerebras、Mistral La Plateforme、xAI、Perplexity、vLLM、LiteLLM 等)都只需要三步:

  1. 在 src/index.ts 的ADAPTERS数组追加一条条目(含namevendorLabelmodelLabelprivacyNoterequestShape: "openai-compat"defaultBaseUrldefaultModelapiKeyEnvbaseUrlEnvmodelEnv);
  2. 在文件顶部的Env接口补充对应的*_API_KEY/*_MODEL/*_BASE_URL可选字段;
  3. npx wrangler secret put XXX_API_KEYnpx wrangler deploy

若 Provider 不兼容 OpenAI 形状(如 AWS Bedrock、Azure OpenAI 的/deployments/<name>/路由、Vertex AI),则需要新增一种RequestShape并参照现存的callOpenAICompat/callAnthropic/callGemini/callCloudflareWorkersAi实现一个约 30 行的callXxx函数,再接入callAdapter的 switch。worker/README.md(interviews/staffml/worker/README.md)提供了 Together、DeepSeek、Fireworks 等多个可直接粘贴的适配器模板。

调整优先级链

默认链为groq → openai → anthropic → gemini → openrouter → cf-workers-ai。覆盖方式:

npx wrangler secret put PROVIDER_PRIORITY anthropic,gemini,cf-workers-ai

逗号分隔的 Provider 名列表,第一个可用者胜出cf-workers-ai无论是否列入都会作为兜底被自动追加(orderedAdapters);若某个 Provider 请求失败(超时、5xx、响应非法),自动落到链上下一个。

厂商赞助接入

如果某厂商(Google、Cloudflare、Anthropic 等)捐赠了更高配额 Key:

  1. wrangler secret put VENDOR_API_KEY
  2. (可选)wrangler secret put PROVIDER_PRIORITY vendor,...让赞助厂商排第一
  3. (可选)wrangler secret put GLOBAL_DAILY_CEILING 100000提高限流上限
  4. 完成。厂商名 + 模型名 + 隐私声明会自动作为面板归属信息展示。

用户可见的归属信息来自 worker/src/index.ts 适配器配置中的vendorLabelmodelLabelprivacyNote。若厂商需要定制文案(如 "Powered by Google AI"),改这三个字段后重新部署即可。源码注释还标注了TODO(multi-key-rotation):当前每个 Provider 只支持一个 Key,多 Key 轮换是预留的扩展方向(源码注释)。

运维手册

实时日志

cd interviews/staffml/worker npx wrangler tail

流式输出 Worker 处理的每一个请求,包括错误与限流命中。

轮换密钥

npx wrangler secret put GROQ_API_KEY # 粘贴新 Key,旧 Key 即被替换

无需重新部署——Secret 约 30 秒内传播生效。

移除某个 Provider

npx wrangler secret delete GROQ_API_KEY

下一次请求开始 Worker 即停用 Groq,自动落到优先级链上下一个可用 Provider。

调整限流参数

npx wrangler secret put RATE_LIMIT_PER_HOUR 30 # 默认 10 npx wrangler secret put RATE_LIMIT_PER_DAY 200 # 默认 60 npx wrangler secret put GLOBAL_DAILY_CEILING 50000 # 默认 8000

下一次请求即生效,无需重新部署。限流器在 KV 中维护三层计数器:rl:hour:${ip}:${小时}rl:day:${ip}:${日期}rl:global:${日期},每次请求约 3 读 + 3 写,TTL 分别设为 1 小时 +5 分钟缓冲与 1 天 +1 小时缓冲(checkRateLimit 实现)。两处实现细节值得注意:

  • 配置解析使用parseIntOrDefault,非法字符串回退默认值,避免 NaN 比较导致限流失效的 fail-open
  • RATE_LIMIT_KV是必选绑定:缺失时checkRateLimit直接返回limiter_unavailable/ask/interview一律fail closed(返回 503),杜绝未计量地烧 LLM 花费(源码注释)。

另外 waitlist 端点自带独立限流:每 IP 每小时 1 次提交,复用RATE_LIMIT_KV但使用wl:hour:前缀,与/ask计数器互不干扰(handleWaitlist 限流段)。

收紧 CORS 到指定来源

默认ALLOWED_ORIGINS为显式白名单:staffml.aiwww.staffml.aimlsysbook.ai与一组本地开发端口(3000–3002、127.0.0.1),未命中来源时回显白名单首项并携带Vary: Origin(corsHeaders 实现)。需要更宽或更窄的策略(如 preview 部署、staging 域名)时:

npx wrangler secret put ALLOWED_ORIGINS https://staffml.ai,https://harvard-edge.github.io

源码注释明确说明:默认从*改为显式白名单,是为了避免任何第三方网站借访问者的 IP 调用本 Worker 来耗尽全局限流预算(CORS 设计说明)。

/ask外的端点速览

端点用途关键细节
GET /health健康检查返回{ ok, providers, waitlist }
POST /ask澄清提问见上文请求/响应格式
POST /waitlist付费意愿调查每 IP 每小时 1 次;无管理端点
POST /interview现场面试指挥驱动完整对话式模拟面试,请求体上限 64 KB,输出上限 800 tokens;同样强制限流(handleInterview)

/interview端点的buildConductorPrompt会把当前题目、链式上下文(position/total/topic)、已覆盖/未覆盖领域与实时评分注入提示词,并要求模型在消息后用---CONDUCTOR_META---定界符附加结构化 JSON 元数据(intentnextActionareaRatings等),供前端驱动"答得好就进阶、答得差就退阶"的自适应面试流程(buildConductorPrompt 与元数据解析)。

成本核算

当前规模下:$0/月。Cloudflare 免费计划包含:

  • 每天 100,000 次 Worker 请求;
  • 每天 10,000 Workers AI 神经元(按提示词大小约合每天 100–300 次 LLM 调用);
  • 每天 100,000 次 KV 读、1,000 次 KV 写。

KV 限流器每次请求约 3 读 + 3 写。按默认 8,000 的全局每日上限计算,即每天 24k 读 + 24k 写——远在免费额度之内。若 Workers AI 的用量增长超过免费额度,下一步是接入上文任一可选 Provider(多数自带慷慨的免费额度),或升级 Cloudflare Workers 付费计划($5/月,Workers AI 上限提升到每月 1,000 万神经元)。另有可选的本地方案:把流量路由到自托管 Ollama / vLLM,成本完全由自有硬件决定。

彻底拆除

如需完全移除 Worker:

cd interviews/staffml/worker npx wrangler delete npx wrangler kv namespace delete --binding RATE_LIMIT_KV npx wrangler kv namespace delete --binding WAITLIST_KV

然后在 StaffML 构建环境中取消NEXT_PUBLIC_INTERVIEWER_ENDPOINT。Ask Interviewer 面板会探测到端点缺失,自动降级为仅记录日志的 JOURNAL 模式——这正是 AskInterviewer.tsx 设计的三态行为之一,拆除后应用依然可用,只是不再发起 AI 调用。

小结

从零开始部署 StaffML Interviewer Worker 的核心要点可概括为四条:两个 KV 命名空间(限流 + waitlist,均可选但建议齐全)、一条wrangler deploy一个NEXT_PUBLIC_INTERVIEWER_ENDPOINT环境变量完成客户端接线,以及按需wrangler secret put逐步升级模型 Provider。整个架构把"成本可控(KV 限流 + Workers AI 免费兜底)、防作弊(服务端苏格拉底提示词不可绕过)、降级优雅(Provider 失败链式回退、客户端三态自愈)"三者固化在约 750 行的单文件实现中,无论是个人练习项目还是对外开放的面板服务,都是一套可直接复用、可审计、可随时拆除的参考部署方案。

【免费下载链接】cs249r_bookMachine Learning Systems项目地址: https://gitcode.com/GitHub_Trending/cs/cs249r_book

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 2:41:04

Spring Boot+SSM+Vue实战:美容院美妆商城系统设计与部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 2:40:35

Xshell运维实战:会话管理、快捷键与自动化脚本提效指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 2:39:21

Nginx日志切分方案详解:logrotate配置、Docker实践与踩坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 2:35:21

50V高耐压LDO替代选型:CSM7375F50SR替换TPL8031的实战要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华