fix: 按 DeepSeek 文档为每种调用指定是否思考,修复分类器拿不到结果

deepseek-flash 默认先思考(reasoning_effort high),思考也占输出 token,思考模式下 temperature 无效。
分类器只给 10 个 token,全被思考用掉,正文为空,切到新后端后的提问都没有层级。

- 生成代码:开思考;引导:关思考,回得快;分类:关思考,temperature 0 才生效
- thinking 只发给 DeepSeek,豆包请求不变
- 实测 deepseek-flash:分类约 1.2s,引导约 1.5s;固定 68 条样本两次一致 65 条

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-27 20:53:30 +08:00
1 parent b765cbf1aa
commit 6b2ffbb3ec
3 files changed
+27 -15

No files matched your search

+2 -2
View File
@@ -37,8 +37,8 @@ async function callLlm(content: string): Promise<number | null> {
{ role: "system", content: CLASSIFY_SYSTEM_PROMPT },
{ role: "user", content },
],
// 同一句提示词要得到同一个层级
{ maxTokens: 10, temperature: 0, timeoutMs: 30_000 },
// 只回一个数字,不用思考;关了思考 temperature 才生效,同一句提示词得到同一个层级
{ thinking: false, maxTokens: 10, temperature: 0, timeoutMs: 30_000 },
)
const match = /[1-6]/.exec(text)
if (!match) {
+18 -11
View File
@@ -59,9 +59,12 @@ export interface ChatMessage {
content: string
}
// DeepSeek(deepseek-flash)默认先思考再回答,思考的内容在 reasoning_content 里,也算输出 token;
// 思考模式下 temperature 不起作用。thinking 只发给 DeepSeek,豆包不传
interface CompletionOptions {
model?: string
stream: boolean
thinking?: boolean
maxTokens?: number
temperature?: number
signal?: AbortSignal
@@ -82,6 +85,7 @@ function buildRequest(messages: ChatMessage[], opts: CompletionOptions) {
messages,
stream: opts.stream,
}
if (!ark && opts.thinking !== undefined) body.thinking = { type: opts.thinking ? "enabled" : "disabled" }
if (opts.maxTokens) body.max_tokens = opts.maxTokens
if (opts.temperature !== undefined) body.temperature = opts.temperature
const apiKey = ark ? config.arkApiKey : config.llmApiKey
@@ -109,8 +113,7 @@ async function ensureOk(res: Response) {
// 读取 OpenAI 兼容接口的 SSE 流,逐段产出文本
async function* streamCompletion(
messages: ChatMessage[],
model?: string,
signal?: AbortSignal,
{ model, thinking, signal }: { model?: string; thinking?: boolean; signal?: AbortSignal },
): AsyncGenerator<string> {
// 带思考的模型生成整页代码可能超过两分钟,只要一直有数据就不该中断
const idle = new AbortController()
@@ -125,6 +128,7 @@ async function* streamCompletion(
const res = await buildRequest(messages, {
model,
stream: true,
thinking,
signal: AbortSignal.any(signals),
timeoutMs: STREAM_MAX_MS,
})
@@ -141,6 +145,7 @@ async function* streamCompletion(
if (data === "[DONE]") return true
const json = JSON.parse(data)
if (json.error) throw new Error(json.error.message ?? JSON.stringify(json.error))
// 思考阶段只有 delta.reasoning_content,不发给学生(它也会重置空闲计时)
const content = json.choices?.[0]?.delta?.content
if (content) yield content
}
@@ -187,29 +192,31 @@ export function userFacingError(e: unknown) {
return status ? `AI 服务出错(${status}),请稍后重试` : "AI 服务出错,请稍后重试"
}
// 生成整页代码:开着思考,质量优先
export function streamChat(history: ChatMessage[], model?: string, signal?: AbortSignal) {
return streamCompletion(
[{ role: "system", content: SYSTEM_PROMPT }, ...trimHistory(history)],
chatModel(model),
return streamCompletion([{ role: "system", content: SYSTEM_PROMPT }, ...trimHistory(history)], {
model: chatModel(model),
thinking: true,
signal,
)
})
}
// 引导只是简短追问,不用思考,回得快
export function streamGuidance(history: ChatMessage[], signal?: AbortSignal) {
return streamCompletion(
[{ role: "system", content: GUIDANCE_SYSTEM_PROMPT }, ...history],
undefined,
return streamCompletion([{ role: "system", content: GUIDANCE_SYSTEM_PROMPT }, ...history], {
thinking: false,
signal,
)
})
}
export async function complete(
messages: ChatMessage[],
opts: { model?: string; maxTokens?: number; temperature?: number; timeoutMs?: number } = {},
opts: { model?: string; thinking?: boolean; maxTokens?: number; temperature?: number; timeoutMs?: number } = {},
): Promise<string> {
const res = await buildRequest(messages, {
model: opts.model ?? config.llmModel,
stream: false,
thinking: opts.thinking,
maxTokens: opts.maxTokens,
temperature: opts.temperature,
timeoutMs: opts.timeoutMs,
+7 -2
View File
@@ -5,6 +5,7 @@ import {
extractCode,
parseGuidanceResponse,
streamChat,
streamGuidance,
trimHistory,
userFacingError,
} from "../src/services/llm"
@@ -77,12 +78,13 @@ test("CSV 转义", () => {
})
describe("streamChat", () => {
test("不认识的模型换成默认模型,最后一行没有换行也能读到", async () => {
test("不认识的模型换成默认模型,最后一行没有换行也能读到;生成代码开思考,引导不开", async () => {
let model = ""
let thinking: unknown
const server = Bun.serve({
port: 0,
async fetch(req) {
model = ((await req.json()) as any).model
;({ model, thinking } = (await req.json()) as any)
const line = (s: string) => `data: ${JSON.stringify({ choices: [{ delta: { content: s } }] })}`
// 一行拆在两个分块之间,最后一行不带换行
const body = `${line("你好")}\n\n${line("世")}\n${line("界")}`
@@ -108,6 +110,9 @@ describe("streamChat", () => {
for await (const s of streamChat([{ role: "user", content: "hi" }], "deepseek-v4-flash-thinking")) out += s
expect(out).toBe("你好世界")
expect(model).toBe(config.llmModel)
expect(thinking).toEqual({ type: "enabled" })
for await (const _ of streamGuidance([{ role: "user", content: "hi" }]));
expect(thinking).toEqual({ type: "disabled" })
} finally {
Object.assign(config, saved)
server.stop(true)