refactor: 精简发给模型的上下文、隐藏接口原始报错,合并重复的删除逻辑
- 发给模型的对话只留最近 20 条,除最后一条 AI 回复外旧回复的代码块换成占位文字, 对话越长越省 token,也不会超出上下文长度 - 学生看到的 AI 报错只显示超时 / 繁忙 / 出错(状态码),完整错误写日志; 客户端自己断开导致的中止不再记错误 - 管理员查询对话必须带 task_id 或 user_id,不再一次查出全站所有对话 - “已评分或获奖不能删”和“删除一轮对话”合并到 services/cascade.ts,补上删除一轮对话的测试 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
325474bdb7
commit
ffdfbc2513
8 files changed
+122
-35
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { all, run } from "../db"
|
||||
import { all, get, run } from "../db"
|
||||
import { getPrecedingUserMessage, type MessageRow } from "./prompt"
|
||||
|
||||
// Django 的 on_delete=CASCADE 是在应用层做的,这里手动按依赖顺序删除。
|
||||
// 调用方负责包在事务里。
|
||||
@@ -15,6 +16,22 @@ export function deleteSubmissions(where: string, params: Record<string, unknown>
|
||||
run(`DELETE FROM submission_submission WHERE ${where}`, params)
|
||||
}
|
||||
|
||||
// 被评过分或获过奖的提交只有超管能删
|
||||
export const hasRatingsOrAwards = (submissionId: string) =>
|
||||
!!get(
|
||||
`SELECT 1 WHERE EXISTS (SELECT 1 FROM submission_rating WHERE submission_id = $id)
|
||||
OR EXISTS (SELECT 1 FROM submission_submissionaward WHERE submission_id = $id)`,
|
||||
{ id: submissionId },
|
||||
)
|
||||
|
||||
// 删除一轮对话:AI 回复、它前面那条用户提问,以及回复关联的提交
|
||||
export function deleteRound(asst: MessageRow) {
|
||||
const userMsg = getPrecedingUserMessage(asst)
|
||||
if (userMsg) run(`DELETE FROM prompt_message WHERE id = $id`, { id: userMsg.id })
|
||||
run(`DELETE FROM prompt_message WHERE id = $id`, { id: asst.id })
|
||||
if (asst.submission_id) deleteSubmissions("id = $id", { id: asst.submission_id })
|
||||
}
|
||||
|
||||
export function deleteTask(taskId: number) {
|
||||
const p = { task_id: taskId }
|
||||
deleteSubmissions("task_id = $task_id", p)
|
||||
|
||||
@@ -158,9 +158,36 @@ async function* streamCompletion(
|
||||
}
|
||||
}
|
||||
|
||||
// 发给模型的对话上下文:只留最近 20 条;除最后一条 AI 回复外,旧回复里的代码块换成占位文字。
|
||||
// 每次回复都是完整代码,模型只需要最新那份;否则对话越长 token 越多,最后会超出上下文长度
|
||||
const MAX_HISTORY = 20
|
||||
const CODE_BLOCK = /```[\s\S]*?```/g
|
||||
|
||||
export function trimHistory(history: ChatMessage[]): ChatMessage[] {
|
||||
const recent = history.slice(-MAX_HISTORY)
|
||||
const last = recent.findLastIndex((m) => m.role === "assistant")
|
||||
return recent.map((m, i) =>
|
||||
m.role === "assistant" && i !== last
|
||||
? { ...m, content: m.content.replace(CODE_BLOCK, "```\n(旧版本代码已省略)\n```") }
|
||||
: m,
|
||||
)
|
||||
}
|
||||
|
||||
// 给学生看的错误:不把模型接口的原始返回(可能带接口细节)原样发出去,完整信息写日志
|
||||
export function userFacingError(e: unknown) {
|
||||
console.error("LLM request failed:", e)
|
||||
const message = e instanceof Error ? e.message : String(e)
|
||||
if (message === "模型响应超时" || (e instanceof Error && e.name === "TimeoutError")) {
|
||||
return "AI 响应超时,请重试"
|
||||
}
|
||||
const status = /^Error code: (\d+)/.exec(message)?.[1]
|
||||
if (status === "429") return "AI 服务繁忙,请稍后重试"
|
||||
return status ? `AI 服务出错(${status}),请稍后重试` : "AI 服务出错,请稍后重试"
|
||||
}
|
||||
|
||||
export function streamChat(history: ChatMessage[], model?: string, signal?: AbortSignal) {
|
||||
return streamCompletion(
|
||||
[{ role: "system", content: SYSTEM_PROMPT }, ...history],
|
||||
[{ role: "system", content: SYSTEM_PROMPT }, ...trimHistory(history)],
|
||||
chatModel(model),
|
||||
signal,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user