任务书接 AI 辅助填写:模型只产草稿,与思考型模型的额度陷阱
入档:2026-08-03 来源:蛛网之上(tiaozhuxiansheng.com)任务书系统接入 AI 辅助填写,above-the-web 仓库
b345dfd(功能)→6b91311(合并上线)→7a64cf7(改接千问 + 专有参数口子)→6b12709(单引号) 状态:已上线,/api/meta的aiAssist为true,模型qwen3.8-max(DashScope compatible-mode)
一句话总结
给既有表单接 AI,产物必须落在「人还能改」的那一格里,且服务端要把模型输出当草稿再洗一遍。 「模型说了什么」和「什么能落进表单」是两件事——前者归模型,后者归你;中间那道清洗(截长度、 日期不合法就丢、标识符重新规范化)不做,模型的每一次胡说都会变成用户的一次报错。
背景与事实(不可修改区)
-
起点:任务书系统本身跑得通,卡在「填」。发布方发一份要写七个小节、自己把「8 月 20 号」 换算成
2026-08-20;接单的人打开认领面板是一个空的「自荐说明」框。 -
交付:两处各加一个入口——说一句大白话,AI 填表单,人过一眼再改。另加草稿本机留存 (
localStorage的atw-draft:*,14 天)。 -
通道:
POST /api/ai/task-draft(仅发布方)、POST /api/ai/claim-pitch(登录即可), OpenAI 兼容协议单次结构化输出调用(response_format.json_schema),零第三方依赖。 -
验证链:服务端
node:test50 项(新增 13,用假助手deps.assistant注入)→ 本机起真服务 + 假上游跑通完整链路 → 真浏览器(chrome-headless-shell + CDP,注入登录态)28 项界面断言 → 上线后拿真 key 对千问打两枪(/models定模型 id、chat/completions验json_schema与思考开关)。 -
实测数据(
qwen3.8-max,同一份「写完整八小节任务书」的请求):reasoning_tokens completion_tokens 结果 默认(开思考) 235 285 三字段的小请求就快撑满 300 额度 enable_thinking:false0 951 完整八小节、1483 字正文,4096 绰绰有余 -
诚实边界(未验证项):
- 没有以真实管理员身份在线上点过那两个按钮(手上没有线上 admin 口令)。线上验证只到
/api/meta的aiAssist:true,加上绕过应用、直接对千问的两次 curl 探针。 assist.js与真千问的接缝没在同一个进程里跑过:本机全链路对的是假上游,真上游对的是 我手工拼的同形状裸 HTTP 请求。两半各自验过,接缝是推断。6b12709那次单引号修正对生产侧的实际影响为零(systemd 本来就读对了),修的是本地 开发那条读法。写进档是因为坑本身值钱,不是因为它在线上炸过。
- 没有以真实管理员身份在线上点过那两个按钮(手上没有线上 admin 口令)。线上验证只到
六条可复用 insight
1. AI 只填表单,不替人做决定(核心)
这条要落在三个具体位置上,少一个都会漏:
- 产物落在人可编辑的控件里,不落在数据库。生成完东西还在
textarea里,改不改、发不发 都是人点的。库里没有任何一条路径是模型直接写进去的,所以这两个接口不记流转记录、只记审计。 - 服务端把模型输出当草稿洗一遍:标题截到字段上限、日期洗不成
YYYY-MM-DD就丢掉(不猜)、 链接后缀按站内规则重新规范化(模型给的" OJO Review!! "→ojo-review)、列表限长去空。 模型走的是strict: true的 json_schema,但 schema 只保证形状,不保证值可用。 - 不许替人编事实。报酬、截止日期、标杆链接只有发布方知道;做过什么作品、什么时候有空 只有申请人知道。没说的一律写成「(待补:……)」占位,并在框底下列一条提醒去补。
第三条不是洁癖,是产品安全:自荐说明是发给真人看、拿来定人的。一键生成的漂亮履历, 发布方一问就穿帮,对申请人的伤害大于帮助。 连界面上的例句都跟着这条走——给的是带括号的 骨架(「做过的同类:(贴一个链接)」)而不是编好的经历,因为一键填进去就能提交的示例, 本身就是在教人注水。
2. 思考型模型的 reasoning 算在 max_tokens 里(⚠️ 首次)
千问默认开思考,而 reasoning token 和正文共用同一份 max_tokens 额度。填表这种活儿不需要长考,
不关的话额度全烧在思考上,正文反而写不完——写到一半被截断,回来是半截 JSON,
JSON.parse 失败,用户看到的是一句莫名其妙的「格式不对」,会以为模型犯浑。
两条处置:
- 关掉思考,但不硬编码。加一个
ATW_AI_EXTRA_JSON口子,里面的东西原样并进请求体 (线上填'{"enable_thinking":false}')。理由是「换供应商只改两个环境变量」这句话得站得住—— 思考开关、top_p这类参数各家名字都不一样,不该为它们改代码。放在请求体最后,填了就以它为准, 这是运维手上的逃生口。 finish_reason === "length"要单独给话。截断和”模型胡说”是两回事,错误信息混在一起, 排查方向就全错了。现在回的是「这次写太长了没写完,把话说短一点再试,或者手填」,日志里带 usage。
3. 同一份配置被两种读法读出不同结果 = 静默失效(⚠️ 首次)
ATW_AI_EXTRA_JSON 的值是一段 JSON,坑在外层那对单引号不能省:
| 读法 | 裸写 {"enable_thinking":false} | 结果 |
|---|---|---|
systemd EnvironmentFile | 只在「整个值被引号包住」时才剥引号,这里原样保留 | ✅ 对 |
本地 . ./.env(POSIX shell) | 把里面的双引号吃掉,变成 {enable_thinking:false} | ❌ JSON.parse 失败 |
最坏的地方在于失败是静默的:解析不了就退回 {}(配置写坏了不该让服务起不来),于是
思考没关掉、正文写不完,而配置文件看上去一个字都没错。加上外层单引号,两种读法才一致。
判据:配置项里放结构化值(JSON、带空格、带引号)时,外层必须自己带一层引号,并且两种读法
各验一次——tr '\0' '\n' < /proc/<pid>/environ 看进程实际拿到什么,. ./.env 看本地读出什么。
这是 账号系统对标一线_多账号抽屉与外部通道半开陷阱_v1 里「配置文件里写了什么不算数,
进程里拿到什么才算数」的续集,而且更阴——那次是发件人显示名解析不确定,这次是两条读法各自
都”正常”,只是结果不同。
4. 探活是排在「写配置」之前的一道工序,不是之后(半开陷阱第三次现形)
拿到 key 的第一反应是按惯性配成 api.ofox.ai(站里 api-proxy 一直在用这家)。重启,
/meta 的 aiAssist 老老实实变成 true——功能看着是开了,可上游一调就是
401 Invalid or expired API key。那把 key 其实是千问的。
这正是 账号系统对标一线_多账号抽屉与外部通道半开陷阱_v1 那条律的第三次现形: 页面说功能开着、真调却必失败,比没有这个功能更糟。 所以发现 401 之后第一件事是把 key 注掉、 让它退回隐身,再去查——而不是留着一个点了必挂的按钮边查边挂。
正确顺序(已写进 .env.example 的注释——把顺序写在配置文件旁边,比写在事后复盘里管用):
GET /models探这把 key 能用哪些模型。免费,而且能拿到准确的模型 id(这次就是靠它 把「千问 3.8 Max」定成qwen3.8-max,同时确认了 key 归属);- 打一次真实的结构化输出调用,看
usage和finish_reason——支不支持json_schema、 思考烧多少 token,都在这一枪里; - 最后才写进
.env重启。
与 OpenAI兼容止于对话端点_多提供商视频接口分流与真key首测_v1 的「真 key 首测是一道独立工序」 是同一条纪律,本次补的是它在时间轴上的位置:首测要在配置生效之前,否则「首测」变成 「线上事故的第一现场」。
5. 降级要连入口一起收走
没配 ATW_AI_API_KEY 时,/meta 的 aiAssist 为 false,前端连按钮都不渲染,
页面上看不出少了什么,手填一路照旧;模型那头出岔子回 502 加一句「手填也能发」,
对方的原话只进日志(里面可能带用量、账号信息)。
「按钮在、点了报错」和「按钮压根不出现」,对用户是两种体验:前者是坏掉的功能,后者是没有这个功能。 同族见 reduced-motion本机陷阱_动效降级纯淡入而非跳过_v1——降级要退到一个完整可用的形态, 不是把路留在那儿让人撞。
6. 服务端读不到 git,就让已有的构建产物顺带捎一段
写自荐说明得知道这活儿要干什么,可 md 任务书的正文在 git 里,平台服务读不到(库里的 body
只有站内新建那批才有)。没有为此新开同步通道,而是让本来就存在的对接产物 /tasks/index.json
多带一个字段 outline(正文纯文本节选,去掉代码块图片链接语法、截到 2400 字),
同步时写进 tasks.outline。
三条约束跟着落位:只读不改、不进任何公开接口、清单里没有这个字段(老版本构建产物)就不动 库里已有的那份,不会把它抹成空。这是 静态站接账号系统_内容归git状态归库的双源切分_v1 那条双源切分的直接复用——要跨源传数据时,先看现有的那条缝够不够宽,别急着开第二条。
三条顺手教训
- 隐私边界要有一条断言看着它。 送进 prompt 的只有任务书本身、用户的公开资料(昵称 / 简介)
和他自己写的那段话;联系方式、收款方式、邮箱一概不带。这条写在注释里会腐烂,所以
test/assist.test.js里有一条断言直接查 prompt 里不含那几个值。 - 登录后的页面用
--dump-dom验不了,令牌在localStorage,一次性 dump 没有注入时机。 改用 CDP:同一个chrome-headless-shell加--remote-debugging-port,Node 22+ 有全局WebSocket,零依赖连上/json/new拿到的webSocketDebuggerUrl,就能写localStorage、 点按钮、读 DOM、截图。补 成稿链接存进库没人读_写入方不等于展示方_v1 里那套--dump-dom姿势够不着的那一半。 - 截图验昼夜别用
Emulation.setEmulatedMedia。 本站主题看document.documentElement的data-theme(localStorage的atw-theme),不看prefers-color-scheme——设了媒体特性, 截出来两张一模一样,会以为暗色皮肤没做。直接setAttribute('data-theme','dark')。 同族见 reduced-motion本机陷阱_动效降级纯淡入而非跳过_v1:本机/无头环境的媒体特性, 和产品真正的开关未必是同一个。
对外版
08_对外分发/任务书AI辅助填写_粉丝群公告.png(长图 2240×4250,附 .html 渲染源码)——
发粉丝群的更新公告,走 07_skill存档/report-longimage 出图。对外版把 insight 1 的
「不替你编经历」放进了全篇唯一的红线块,并如实写明当时 0 个在招任务、不诱导空点。
关联文档
- ⭐ 账号系统对标一线_多账号抽屉与外部通道半开陷阱_v1 —— 同族核心:那篇立的
「功能开关要接在能力被验证过上」在本篇第三次现形(配了 ofox 的 base URL,
aiAssist:true而调用必 401); 本篇补两条:探活工序要排在写配置之前、以及「配置两种读法结果不同」这个更阴的变体 - ⭐ OpenAI兼容止于对话端点_多提供商视频接口分流与真key首测_v1 —— 同族纪律「真 key 首测是独立工序」
的应验与补正:本次靠
GET /models免费拿到准确模型 id 并识破 key 归属,印证「标识符不许猜要拉权威列表」; 新增变体=首测的时间位置(配置生效之前) - 任务系统演进_站内新建管理台拆分首页瘦身_v1 —— 直接上篇:那篇让任务书能在站内直接发, 本篇让「发」和「接」都不必对着空框写
- 静态站接账号系统_内容归git状态归库的双源切分_v1 —— 本篇
outline字段是那条双源切分的复用: 跨源传数据先看现有的缝够不够宽 - 成稿链接存进库没人读_写入方不等于展示方_v1 —— 本机自测姿势的续篇:那篇给了
chrome-headless-shell --dump-dom,本篇补上登录态页面要走 CDP 的那一半 - ⚠️ reduced-motion本机陷阱_动效降级纯淡入而非跳过_v1 —— 同族降级设计:降级要退到完整可用形态 (没 key 就不渲染按钮),而非把坏掉的入口留在那儿;也同族「本机媒体特性 ≠ 产品开关」
- ⭐ 交付前实测证伪律_v1 —— 本篇的诚实边界即依此写:假上游只证接线,
assist.js与真千问的接缝仍是推断 - 复盘事实先行原则 —— 本文遵循:未以真实管理员身份在线上点过按钮、接缝未跑过,均写进事实冻结区