fix(teacher): 区分词元预算与传输字节避免短问题被拦截

This commit is contained in:
2026-09-24 11:09:29 +08:00
parent eb3dc85a2f
commit de72b1cd94
10 changed files with 187 additions and 35 deletions

View File

@@ -11,7 +11,7 @@ import {
TeacherError,
type TeacherAccount,
} from './config-client';
import { estimateTeacherTokens, type TeacherModelMessage, type TeacherToolCall } from './context';
import { estimateTeacherTextTokens, estimateTeacherTokens, TEACHER_ESTIMATED_BYTES_PER_TOKEN, type TeacherModelMessage, type TeacherToolCall } from './context';
import { createTeacherReadTools, type TeacherReadAccess, type TeacherReadTools } from './read-tools';
interface TeacherModelConfig {
@@ -74,8 +74,9 @@ export async function prepareTeacherModel(
capability.limits?.contextWindow ? capability.limits.contextWindow - outputLimit : Infinity
);
const tools = access ? createTeacherReadTools(access) : undefined;
const toolBudget = tools ? Buffer.byteLength(JSON.stringify(tools.definitions), 'utf8') + 64 : 0;
const toolBudget = tools ? estimateTeacherTextTokens(JSON.stringify(tools.definitions)) + 64 : 0;
return {
measureInput: estimateTeacherTokens,
// Leave room for a read result and its native tool-call envelope.
inputLimit: inputLimit - toolBudget - (tools ? Math.min(2400, Math.floor(inputLimit / 4)) : 0),
run: (messages: TeacherModelMessage[], signal: AbortSignal, onText: (text: string) => void) => {
@@ -97,7 +98,7 @@ export async function streamTeacherReply(
options?: { tools?: TeacherReadTools; inputLimit: number; finalOnly?: boolean; assertCurrent(): void }
): Promise<PublicUsage | undefined> {
const tools = options?.tools;
const toolBudget = tools ? Buffer.byteLength(JSON.stringify(tools.definitions), 'utf8') + 64 : 0;
const toolBudget = tools ? estimateTeacherTextTokens(JSON.stringify(tools.definitions)) + 64 : 0;
const reads: TeacherModelMessage[][] = [];
let usage: PublicUsage | undefined;
// Six read rounds, then one final text response. No recursive agent or Pi session.
@@ -129,8 +130,11 @@ export async function streamTeacherReply(
}
const batch: TeacherModelMessage[] = [{ role: 'assistant', content: result.text, tool_calls: result.calls,
...(result.reasoning ? { reasoning_content: result.reasoning } : {}) }];
// Tools bound their UTF-8 result bytes; convert the remaining token estimate
// back to bytes while keeping the existing per-result byte cap.
const resultBudget = Math.min(2400, Math.floor(((options?.inputLimit ?? Infinity)
- toolBudget - estimateTeacherTokens([...messages, ...batch]) - 64 * result.calls.length) / result.calls.length));
- toolBudget - estimateTeacherTokens([...messages, ...batch]) - 64 * result.calls.length)
* TEACHER_ESTIMATED_BYTES_PER_TOKEN / result.calls.length));
if (resultBudget < 128) {
throw new TeacherError(422, 'teacher_context_too_long', '老师读取的内容超过上下文预算,请缩小问题范围或联系运营增加预算。');
}