LLM 学习工作流(三):AI 请求 payload 构造器
这一步在做什么
这一步把"前端页面里的状态"整理成了"发给 AI 工作流层的标准请求体"。
最终新增的文件是:
src/utils/ai-payloads.jssrc/utils/ai.jstests/ai-payloads.test.js
为什么这一步重要
AI 项目里很常见的一个坏味道是:
- 页面里临时拼一个对象
- 直接发给后端
- 下一个页面又拼另一种格式
时间一长,前端和后端之间就会出现很多"长得差不多但不完全一样"的请求体。
这一步的目的就是先把协议收紧:
- 日计划请求长什么样
- 复盘请求长什么样
- 前端统一怎么调 AI 工作流函数
这一步具体做了什么
1. 新增 buildDailyPlanPayload
用于构造"生成今日学习计划"的请求体。
它现在会返回:
{
action: 'generateDailyPlan',
runId,
profile,
progressSnapshot,
}
2. 新增 buildReflectionPayload
用于构造"生成学习复盘"的请求体。
它现在会返回:
{
action: 'summarizeReflection',
runId,
learnedWords,
quizAnswers,
}
3. 新增 invokeLearningWorkflow
统一通过 Supabase Edge Function 调 learning-workflow:
supabase.functions.invoke('learning-workflow', { body })
这一步的意义不是"少写几行代码",而是把 AI 调用入口集中起来,后面如果你要做:
- 错误处理
- 埋点
- 重试
- trace id
都可以从这个统一入口下手。
先写了什么测试
这一步仍然是先测后写。
先写的测试只有两个,专门卡 payload builder:
test('buildDailyPlanPayload normalizes the workflow request', ...)
test('buildReflectionPayload includes quiz answers and learned words', ...)
然后先跑:
node --test tests/ai-payloads.test.js -v
第一次失败是对的,因为这时 src/utils/ai-payloads.js 还不存在。
为什么这一步要保持"很小"
这一层最容易犯的错是过早扩展。
比如一开始就加:
- 十几个 payload builder
- 很复杂的请求校验器
- 一个大而全的 AI SDK 封装层
这些都不是现在最需要的。
当前阶段只需要先把两种最明确的请求定下来:
- 今日计划
- 学习复盘
这就是典型的 YAGNI。
这一步学到了什么
1. AI 请求协议要尽早结构化
你越早把请求体结构固定下来,后面就越容易做:
- Edge Function
- JSON schema
- prompt 输入整理
- 错误排查
如果一直在页面里临时拼对象,后面会非常乱。
2. action 字段就是工作流入口开关
这一步的 payload 里有一个非常关键的字段:
action
它其实就是服务端工作流分发的入口。
例如:
generateDailyPlansummarizeReflection
后面服务端收到请求时,就可以根据这个字段决定:
- 走哪个 prompt
- 用哪个 schema
- 读哪些上下文
3. 统一调用入口比"直接调函数"更重要
invokeLearningWorkflow 现在看起来很薄,只是包了一层:
supabase.functions.invoke(...)
但这种统一入口在 AI 项目里特别重要,因为后面几乎一定会加:
- 超时处理
- 统一错误格式
- 调试日志
- 重试策略
- 请求标识
当前的边界约定
Daily Plan 请求
buildDailyPlanPayload({
profile: { currentLevel: 'A2', targetGoal: '前端面试英语' },
progressSnapshot: { dueWords: 8 },
})
会得到:
{
action: 'generateDailyPlan',
runId: null,
profile: { currentLevel: 'A2', targetGoal: '前端面试英语' },
progressSnapshot: { dueWords: 8 },
}
Reflection 请求
buildReflectionPayload({
runId: 'run-1',
learnedWords: ['api', 'cache'],
quizAnswers: [{ id: 'q1', correct: false }],
})
会得到:
{
action: 'summarizeReflection',
runId: 'run-1',
learnedWords: ['api', 'cache'],
quizAnswers: [{ id: 'q1', correct: false }],
}
这一步的局限是什么
评审里保留了一个小风险,但不阻塞继续开发:
src/utils/ai.js还没有独立单测
原因也很实际:
- 现在这层只是一个很薄的转发器
- 当前任务计划只要求先测 payload builder
- 等下一步接上 Edge Function 或 mockable 边界,再给它补单测更划算
这也说明一个实践原则:
不是所有代码都要在同一时刻测到同样深。
当前优先级更高的是先把协议形状固定住。
你可以自己复现什么
运行这一步的测试
node --test tests/ai-payloads.test.js -v
看这一步的提交
git show --stat 9330dd5e
这一步你应该学会什么
- 为什么 AI 请求协议要尽早固定
- 为什么
action适合作为工作流分发入口 - 为什么统一的 AI 调用入口很重要
- 为什么这一步要故意做得很小
- 怎么把"页面状态"变成"后端可消费的结构化 payload"
下一步会做什么
下一步是 Task 4:
- 定义 AI 输出 schema
- 搭起 Supabase Edge Function 骨架
- 把"前端标准请求体"真正接到"服务端 AI 工作流入口"
到那一步,你会开始真正进入 AI 应用开发里最关键的一层:
结构化输入 -> 结构化输出 -> 服务端编排