Fix training plan generation flow

这个提交包含在:
cryptocommuniums-afk
2026-03-14 23:16:19 +08:00
父节点 6943754838
当前提交 1cc863e60e
修改 8 个文件,包含 429 行新增19 行删除

查看文件

@@ -8,6 +8,50 @@ import { invokeLLM } from "./_core/llm";
import { storagePut } from "./storage";
import * as db from "./db";
import { nanoid } from "nanoid";
import {
normalizeAdjustedPlanResponse,
normalizeTrainingPlanResponse,
} from "./trainingPlan";
async function invokeStructuredPlan<T>(params: {
baseMessages: Array<{ role: "system" | "user"; content: string }>;
responseFormat: {
type: "json_schema";
json_schema: {
name: string;
strict: true;
schema: Record<string, unknown>;
};
};
parse: (content: unknown) => T;
}) {
let lastError: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
const retryHint =
attempt === 0 || !(lastError instanceof Error)
? []
: [{
role: "user" as const,
content:
`上一次输出无法被系统解析,错误是:${lastError.message}` +
"请只返回一个合法、完整、可解析的 JSON 对象,不要包含额外说明、注释或 Markdown 代码块。",
}];
const response = await invokeLLM({
messages: [...params.baseMessages, ...retryHint],
response_format: params.responseFormat,
});
try {
return params.parse(response.choices[0]?.message?.content);
} catch (error) {
lastError = error;
}
}
throw lastError instanceof Error ? lastError : new Error("Failed to parse structured LLM response");
}
export const appRouter = router({
system: systemRouter,
@@ -85,12 +129,12 @@ ${recentScores.length > 0 ? `- 用户最近的分析数据: ${JSON.stringify(rec
请返回JSON格式,包含每天的训练内容。`;
const response = await invokeLLM({
messages: [
const parsed = await invokeStructuredPlan({
baseMessages: [
{ role: "system", content: "你是网球训练计划生成器。返回严格的JSON格式。" },
{ role: "user", content: prompt },
],
response_format: {
responseFormat: {
type: "json_schema",
json_schema: {
name: "training_plan",
@@ -123,12 +167,12 @@ ${recentScores.length > 0 ? `- 用户最近的分析数据: ${JSON.stringify(rec
},
},
},
parse: (content) => normalizeTrainingPlanResponse({
content,
fallbackTitle: `${input.durationDays}天训练计划`,
}),
});
const content = response.choices[0]?.message?.content;
const parsed = typeof content === "string" ? JSON.parse(content) : null;
if (!parsed) throw new Error("Failed to generate training plan");
const planId = await db.createTrainingPlan({
userId: user.id,
title: parsed.title,
@@ -173,12 +217,12 @@ ${recentScores.length > 0 ? `- 用户最近的分析数据: ${JSON.stringify(rec
请根据分析结果调整训练计划,增加针对薄弱环节的训练,返回与原计划相同格式的JSON。`;
const response = await invokeLLM({
messages: [
{ role: "system", content: "你是网球评分生成器。返回严格的JSON格式。" },
const parsed = await invokeStructuredPlan({
baseMessages: [
{ role: "system", content: "你是网球训练计划调整器。返回严格的JSON格式。" },
{ role: "user", content: prompt },
],
response_format: {
responseFormat: {
type: "json_schema",
json_schema: {
name: "adjusted_plan",
@@ -212,12 +256,12 @@ ${recentScores.length > 0 ? `- 用户最近的分析数据: ${JSON.stringify(rec
},
},
},
parse: (content) => normalizeAdjustedPlanResponse({
content,
fallbackTitle: currentPlan.title,
}),
});
const content = response.choices[0]?.message?.content;
const parsed = typeof content === "string" ? JSON.parse(content) : null;
if (!parsed) throw new Error("Failed to adjust plan");
await db.updateTrainingPlan(input.planId, {
exercises: parsed.exercises,
adjustmentNotes: parsed.adjustmentNotes,