给 OCS 网课助手 用的 AI 题库。部署在 Cloudflare Workers 上,把 OCS 发来的题目转交给大模型作答,再把答案返回给 OCS 自动填写。
- 支持单选、多选、判断、填空,图片题需要使用支持视觉输入的模型
- 兼容所有 OpenAI 格式的接口,例如 OpenAI、DeepSeek、通义千问(阿里云百炼)、硅基流动
- 模型不支持图片或 JSON 输出时,会自动降级重试
- 可以配置多个备用服务商和 Key,当前的失败时按顺序自动换下一个
- 自带日志页,可以查看每道题的题目、答案、理由、耗时和 token 用量
- Cloudflare 免费套餐足够个人使用
本项目采用 BYOK(Bring Your Own Key)模式:API Key 由你在 OCS 中填写,每次搜题时随请求一起发送给 Worker,再由 Worker 转发给大模型服务商。服务端不保存 Key,也不写入日志。
因此,请只使用自己部署的实例,不要把 Key 填进别人部署的地址。别人的 Worker 能看到你的 Key。
- 部署到 Cloudflare,得到一个域名,例如
quiz.example.com - 在 OCS 中配置题库,填入域名、访问 Token 和你的大模型 API 信息
- 放行 OCS 的跨域请求,否则 OCS 会提示「题库连接失败」
- 一个 Cloudflare 账号,免费套餐即可
- Node.js 22 或更高版本(Wrangler 的要求)
- 一个托管在 Cloudflare 上的域名。可选,但在中国大陆使用时强烈建议准备,原因见绑定自定义域名
1. 下载代码并安装依赖
git clone https://github.com/swim233/ocs-quiz.git
cd ocs-quiz
npm install
npm --prefix web install # 日志页前端的依赖,不装会导致部署失败2. 登录 Cloudflare
npx wrangler login浏览器会弹出授权页面,点击允许即可。
3. 创建 D1 数据库(用于保存日志)
npx wrangler d1 create ocs-quiz-db命令会输出一个 database_id。打开 wrangler.toml,用它替换掉文件中原有的 database_id:
[[d1_databases]]
binding = "DB"
database_name = "ocs-quiz-db"
database_id = "这里换成你自己的 database_id"表结构会在第一次写日志时自动创建,不需要手动初始化。
4. 部署
npm run deploy部署成功后会输出 Worker 地址,形如 https://ocs-quiz.<你的子域>.workers.dev。
5. 设置访问 Token
项目有两个相互独立的 Token:
| Token | 用途 | 未设置时 |
|---|---|---|
AUTH_TOKEN |
搜题请求鉴权,填在 OCS 题库配置的 Authorization 中 |
任何知道地址的人都能调用搜题接口 |
WEBUI_TOKEN |
日志页登录 | 日志页无法访问 |
npx wrangler secret put AUTH_TOKEN
npx wrangler secret put WEBUI_TOKEN按提示分别输入两个不同的、足够长的随机字符串,可以用 openssl rand -hex 16 生成。设置后立即生效,不需要重新部署。两个 Token 分开以后,把 OCS 题库配置分享给别人时,对方也看不到你的日志。
Warning
不设置 AUTH_TOKEN 时,任何知道地址的人都能把你的 Worker 当作中转站使用,占用你的 Cloudflare 免费额度,日志也会被他人的请求刷满。
6. 检查部署
浏览器打开 https://<你的域名>/api/health,页面显示 {"ok":true} 就说明部署成功。
OCS 运行在你自己的浏览器里,所以浏览器必须能访问到 Worker。*.workers.dev 域名在中国大陆通常无法直接访问,建议绑定自己的域名。该域名需要已托管在 Cloudflare。
以下两种方式任选其一:
-
控制台:Workers 和 Pages →
ocs-quiz→ 设置(Settings)→ 域和路由(Domains & Routes)→ 添加(Add)→ 自定义域(Custom domain),填入quiz.example.com -
配置文件:在
wrangler.toml顶部([assets]之前)加上下面的配置,然后重新执行npm run deployroutes = [ { pattern = "quiz.example.com", custom_domain = true } ]
绑定后,后文所有的 <你的域名> 都填这个自定义域名。
以下变量都在 wrangler.toml 的 [vars] 中,修改后需要重新执行 npm run deploy:
| 变量 | 默认值 | 说明 |
|---|---|---|
LLM_TEMPERATURE |
0 |
模型温度 |
LLM_TIMEOUT_MS |
110000 |
单次搜题的总时限(毫秒)。配置了备用服务商时,所有尝试共享这个时限。见常见问题中的说明 |
VISION_ENABLED |
true |
是否把题目中的图片发给模型;设为 false 时只发送文字 |
LOG_ENABLED |
true |
是否记录日志;设为 false 时不写入 D1 |
如果完全不需要日志,也可以删掉 wrangler.toml 中的整个 [[d1_databases]] 段,并跳过第 3 步。
git pull
npm install && npm --prefix web install
npm run deploy浏览器打开:
https://<你的域名>/ocs-config.json
页面会返回一段 JSON,其中的地址已经是你的域名。复制它,下一步要修改:
[
{
"name": "OCS Quiz (LLM)",
"homepage": "https://quiz.example.com",
"url": "https://quiz.example.com/api/search",
"method": "post",
"contentType": "json",
"type": "GM_xmlhttpRequest",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer <YOUR_TOKEN>"
},
"data": {
"title": "${title}",
"options": "${options}",
"type": "${type}",
"apiKey": "",
"baseUrl": "",
"model": "",
"thinkEffort": ""
},
"handler": "return (res)=> res.code === 0 ? [res.data.question, res.data.answers.join('#'), { ai: true, tags: res.data.tags }] : [res.msg, undefined]"
}
]在地址后加上
?token=<你的 AUTH_TOKEN>再打开,返回的 JSON 会直接填好 Token。
| 位置 | 填什么 |
|---|---|
headers.Authorization |
把 <YOUR_TOKEN> 换成部署时设置的 AUTH_TOKEN,保留前面的 Bearer 。没有设置 AUTH_TOKEN 时可以删掉这一行 |
data.apiKey |
大模型服务商的 API Key,必填(只用 providers 时可留空,见下方说明) |
data.baseUrl |
接口地址,必填(只用 providers 时可留空),填法见下方说明 |
data.model |
模型名称,必填(只用 providers 时可留空),以服务商文档为准。要做图片题,请选择支持视觉输入的模型 |
data.thinkEffort |
思考强度,可选。会原样作为 reasoning_effort 参数发给服务商(如 OpenAI 的 low / medium / high)。不确定时请留空,服务商不支持该参数时可能导致请求报错 |
apiKey、baseUrl、model 三项要么都填,要么都留空。都留空时,必须配置备用服务商,只填一部分会报错。
其余字段(${title} 等占位符、handler)保持原样,不要改。
baseUrl 怎么填? Worker 会在 baseUrl 后面拼上 /chat/completions 发起请求,所以只填到 /chat/completions 之前的部分,一般以 /v1 结尾:
| 服务商 | baseUrl |
|---|---|
| OpenAI | https://api.openai.com/v1 |
| DeepSeek | https://api.deepseek.com |
| 阿里云百炼(通义千问) | https://dashscope.aliyuncs.com/compatible-mode/v1 |
| 硅基流动 | https://api.siliconflow.cn/v1 |
其他 OpenAI 兼容服务同理,以服务商文档中的 base URL 为准。
打开任意网课页面,在 OCS 悬浮窗中进入 通用 → 全局设置 → 题库配置,粘贴改好的 JSON,然后保存。
在 data 中加一个 providers 字段,就可以配置多个备用的服务商和 Key。上面填的 apiKey、baseUrl、model 是首选;首选失败时(Key 无效、限流、余额不足、服务商报错、超时等),Worker 会按顺序尝试 providers 中的每个 Key,直到有一个成功为止:
"data": {
"title": "${title}",
"options": "${options}",
"type": "${type}",
"apiKey": "sk-main",
"baseUrl": "https://api.deepseek.com",
"model": "deepseek-chat",
"thinkEffort": "",
"providers": [
{
"baseUrl": "https://api.deepseek.com",
"keys": [
{ "apiKey": "sk-backup", "model": "deepseek-chat" }
]
},
{
"baseUrl": "https://api.openai.com/v1",
"keys": [
{ "apiKey": "sk-openai-1", "model": "gpt-4o" },
{ "apiKey": "sk-openai-2", "model": "o4-mini", "thinkEffort": "low" }
]
}
]
}- 尝试顺序:先是上面的
apiKey/baseUrl/model,然后按providers数组的顺序,每组内按keys的顺序。上面三项也可以全部留空,只用providers - 什么情况会换下一个:当前的请求报错时。模型正常返回但表示无法作答时不会换,因为换个模型多半也答不出
- 同一个
baseUrl在providers中只能出现一次,比较时忽略末尾的/。它可以和上面的baseUrl相同,比如给同一个服务商配一个备用 Key thinkEffort每个 Key 单独设置,不写就是不设置,不会沿用上面的thinkEffort- 不要写
null:OCS 遇到null会在发出请求前直接报错。不需要的字段请填""或者不写 - 配置写错时整体报错:请求时会检查全部配置,有错就一次列出所有问题(如
providers[1].keys[0] 缺少 model,序号从 0 开始),不会发出任何请求 - 总时限共享:所有尝试共用
LLM_TIMEOUT_MS(默认 110 秒),时间用完后剩下的候选不再尝试
每道题是哪个候选答的、有没有发生降级、前面的候选为什么失败,可以在日志页查看。
全部候选都失败时,返回的错误信息会逐个列出每个候选的错误。
订阅链接方式不支持 providers,请使用粘贴 JSON 的方式。
OCS 的题库配置也可以直接填一个链接,OCS 会在保存时读取一次。你可以把参数都写进 /ocs-config.json 的链接里(参数值需要进行 URL 编码):
https://<你的域名>/ocs-config.json?token=<TOKEN>&apiKey=<KEY>&baseUrl=<URL>&model=<MODEL>
这样更省事,但 API Key 和 Token 会以明文出现在链接里,也会留在浏览器历史记录中。介意的话请使用上面粘贴 JSON 的方式。
题库配置中的 "type": "GM_xmlhttpRequest" 表示通过脚本管理器(油猴 Tampermonkey、脚本猫等)发送跨域请求。脚本管理器只允许脚本访问它在 @connect 中声明过的域名,而 OCS 标准版的声明里没有你的域名,所以请求会被拦截,OCS 面板上显示为「题库连接失败」。
需要放行的域名是题库配置 url 中的域名:绑定了自定义域名就填自定义域名(如 quiz.example.com),否则填 ocs-quiz.<你的子域>.workers.dev。
以下三种方法任选一种。
- 点击浏览器右上角的油猴图标 → 管理面板
- 找到「OCS 网课助手」,点击右侧的编辑按钮
- 切换到顶部的「设置」标签页,找到「XHR 安全」下的「用户域名白名单」,添加你的域名
- 保存
这种方法不修改脚本代码,脚本自动更新后依然有效。不同版本油猴中的选项名称可能略有差异;如果找不到该选项,或者使用的是脚本猫,请改用方法 B 或 C。
-
同上,进入「OCS 网课助手」的代码编辑界面
-
在开头
// ==UserScript==和// ==/UserScript==之间的任意位置,加一行:// @connect quiz.example.com -
按
Ctrl + S保存
注意:OCS 脚本自动更新后,这行修改会被覆盖,需要重新添加。
OCS 官方提供一个 @connect 中带 * 通配符的版本,可以请求任意域名:
- GreasyFork:https://greasyfork.org/zh-CN/scripts/481438
- 脚本猫:https://scriptcat.org/zh-CN/script-show-page/1398
安装后,第一次搜题时脚本管理器会弹窗,询问是否允许访问你的域名,选择「总是允许」即可。
浏览器打开 https://<你的域名>/,输入 WEBUI_TOKEN 登录(注意不是 AUTH_TOKEN),就能看到最近的答题记录,包括题目、选项、图片、模型给出的答案与理由、耗时、token 用量和请求方 IP。日志页支持按状态筛选、全文搜索,默认每 3 秒自动刷新。
配置了备用服务商时,发生过降级的记录会标出「降级 ×N」,详情页会列出每次失败的尝试(序号、模型、服务商、错误和耗时)。
日志中不包含 API Key。但服务商返回的错误信息会原样记录,有的服务商会在错误信息中回显打码后的 Key 片段。没有设置 WEBUI_TOKEN 时,日志页会提示「服务端未配置 WEBUI_TOKEN」,按提示设置即可。
OCS 只要收到的不是 HTTP 200 响应,都会显示这个提示,且不会显示具体原因。按以下顺序排查:
- 跨域没有放行:见第三节。如果之前在弹窗中误点了禁止,请到脚本设置中把域名从黑名单里移除
- 域名无法访问:浏览器直接打开
https://<你的域名>/api/health,看能否返回{"ok":true}。在中国大陆打不开workers.dev时,请绑定自定义域名 - Token 错误:检查
Authorization是否为Bearer <你的 AUTH_TOKEN>,是否漏改了<YOUR_TOKEN>占位符 - 超时:见下一条
其他错误(如缺少参数、API Key 无效、模型报错)都会以文字形式显示在 OCS 面板上,按提示修改即可。
较新版本的 OCS 可以在高级设置中调整「搜题最大耗时」(默认 120 秒)。Worker 默认的总时限为 110 秒,比 OCS 的限制略短,这样超时后 OCS 面板能显示具体原因。配置了备用服务商时,所有尝试共享这 110 秒,前面的候选用掉的时间越多,留给后面的就越少。
旧版 OCS(4.11.8 之前)固定 30 秒超时。请升级 OCS,或把 wrangler.toml 中的 LLM_TIMEOUT_MS 改为 25000 左右后重新部署。
使用推理模型时,如果经常超时,可以把 thinkEffort 调低,或者换用非推理模型。
图片题需要选择支持视觉输入的模型。如果模型不支持图片,Worker 会自动改为只发送文字,此时模型看不到图片,答案可能不准确。
下面的命令可以绕过 OCS,直接测试 Worker 和 API Key 是否正常,方便判断问题出在 Worker 一侧还是 OCS/跨域一侧:
curl -X POST https://<你的域名>/api/search \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <你的 AUTH_TOKEN>' \
-d '{
"title": "1+1=?",
"options": "1\n2\n3",
"type": "single",
"apiKey": "<你的 API Key>",
"baseUrl": "https://api.deepseek.com",
"model": "<模型名称>"
}'正常时会返回类似下面的结果:
{
"code": 0,
"data": {
"question": "1+1=?",
"answers": ["2"],
"reason": "1+1 等于 2",
"model": "<模型名称>",
"latency_ms": 1234,
"usage": { "prompt_tokens": 120, "completion_tokens": 30, "cached_tokens": 0 },
"tags": [{ "text": "<模型名称>", "title": "api.deepseek.com", "color": "gray" }]
}
}失败时返回 {"code": 1, "msg": "错误原因"}。
# .dev.vars(已加入 .gitignore)
AUTH_TOKEN=dev-token
WEBUI_TOKEN=dev-webui-tokennpm run build:web # 构建日志页前端,修改前端后需要重新构建
npm run dev # 启动 Worker:http://localhost:8790
node scripts/stub-llm.mjs # 可选:启动一个假的大模型服务,baseUrl 填 http://localhost:8788/v1假的大模型服务接受任意以 /chat/completions 结尾的路径,所以 http://localhost:8788/a/v1 和 http://localhost:8788/b/v1 可以当作两个不同的服务商。apiKey 填 fail-401、fail-429、fail-500 可以模拟对应的错误,填 slow-<毫秒> 可以模拟慢响应,用来测试降级和总时限。
本项目仅供学习和技术交流使用。使用者需自行遵守所在学校、课程平台和大模型服务商的相关规定,因使用本项目产生的一切后果由使用者自行承担。
