打通 Boss 直聘落档挂载与真值→投影映射,并修正带 tools 时的推理强度。

落档自动把产物挂进 context_order;创作期用 declare_variable/declare_map 钉目录与分档;真值变更持续重投影。顺带强制 tools 请求 reasoning_effort=none,并支持消息 restart。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
moran
2026-09-09 17:50:07 +08:00
co-authored by Cursor
parent bf0b218f7c
commit beb4c332e6
23 changed files with 1780 additions and 95 deletions
+13 -6
View File
@@ -5,6 +5,7 @@
import {
applyRejectedGenerationField,
isToolsReasoningEffortConflictError,
parseRejectedGenerationField,
} from "./generation-compat.js";
@@ -168,7 +169,7 @@ async function postOnce(
/**
* 探测请求始终带 reasoning_effort=low。
* 若供应商拒收该字段,剥掉后再试一次。
* 若供应商拒收该字段,剥掉后再试;tools 冲突则改 none 再试。
*/
async function postChat(
config: ProbeConfig,
@@ -179,14 +180,20 @@ async function postChat(
reasoning_effort: CAPABILITY_PROBE_REASONING_EFFORT,
};
const first = await postOnce(config, withEffort);
if (first.ok || first.status !== 400) return first;
const rejected = parseRejectedGenerationField(first.text);
if (rejected !== "reasoning_effort") return first;
if (first.ok || (first.status !== 400 && first.status !== 422)) return first;
const retryBody = { ...withEffort };
const tried = new Set<string>();
if (!applyRejectedGenerationField(retryBody, "reasoning_effort", tried)) {
if (isToolsReasoningEffortConflictError(first.text, first.status)) {
retryBody.reasoning_effort = "none";
tried.add("reasoning_effort_none");
return postOnce(config, retryBody);
}
const rejected = parseRejectedGenerationField(first.text);
if (!rejected) return first;
if (!applyRejectedGenerationField(retryBody, rejected, tried)) {
return first;
}
return postOnce(config, retryBody);
+3
View File
@@ -3,6 +3,7 @@ import type { GenerationParameters } from "../types/preset.js";
import {
applyOpenAiGeneration,
fetchWithGenerationCompat,
forceReasoningEffortNoneWhenTools,
sanitizeReasoningEffort,
} from "./generation-compat.js";
import { consumeOpenAiToolStream } from "./stream-complete.js";
@@ -147,6 +148,8 @@ export function buildRequestBody(
applyOpenAiGeneration(body, options?.generation ?? {}, config.model, {
reasoningEffort: config.reasoningEffort,
});
// 对齐 imyai:带 tools 时强制 none(省略字段网关仍可能注入默认 effort)
forceReasoningEffortNoneWhenTools(body);
if (options?.responseFormat === "json_object") {
body.response_format = { type: "json_object" };
+82 -3
View File
@@ -6,7 +6,8 @@ import type { GenerationParameters } from "../types/preset.js";
* - top_k、min_p、repetition_penalty 不是官方 Chat Completions 字段,默认不发
* - 推理模型去掉 temperature / top_p / penalty
* - o 系列与 gpt-5 用 max_completion_tokens
* - 仍 400 时按错误信息剥掉不支持字段再试
* - 带 tools 时强制 reasoning_effort=none(gpt-5.6-luna 等网关:省略字段仍会注入默认 effort)
* - 仍 400 时按错误信息剥掉不支持字段再试;tools+reasoning 冲突则改 none 再试
*/
const REASONING_EFFORT_VALUES = [
@@ -157,6 +158,46 @@ export function applyOpenAiGeneration(
}
}
/** chat/completions + function tools 时须显式 none,否则网关可能注入默认 effort 并 400。 */
export function forceReasoningEffortNoneWhenTools(
body: Record<string, unknown>,
): void {
const tools = body.tools;
if (!Array.isArray(tools) || tools.length === 0) return;
body.reasoning_effort = "none";
}
/**
* gpt-5.6-luna 等:/v1/chat/completions 下 function tools 不能与非 none 的
* reasoning_effort 同用。需显式 reasoning_effort='none'。
*/
export function isToolsReasoningEffortConflictError(
errorText: string,
status?: number,
): boolean {
if (status != null && status !== 400 && status !== 422) return false;
let parsed: unknown;
try {
parsed = JSON.parse(errorText);
} catch {
parsed = undefined;
}
const message = `${readErrorMessage(parsed)}\n${errorText}`.toLowerCase();
if (!message.trim()) return false;
const mentionsTools =
/\b(function\s+)?tools?\b/.test(message) ||
/tool_choice|function tools/.test(message);
const mentionsReasoningEffort = /reasoning_effort/.test(message);
const conflict =
/not support|unsupported|does not support|not supported|not allowed|cannot|can't/.test(
message,
);
return mentionsTools && mentionsReasoningEffort && conflict;
}
function readErrorMessage(raw: unknown): string {
if (!raw || typeof raw !== "object") return "";
const err = (raw as { error?: unknown }).error;
@@ -238,12 +279,42 @@ export function applyRejectedGenerationField(
return true;
}
// tools + reasoning_effort 冲突:删字段不够,须显式 none
if (
field === "reasoning_effort" &&
Array.isArray(body.tools) &&
body.tools.length > 0 &&
body.reasoning_effort !== "none" &&
!tried.has("reasoning_effort_none")
) {
body.reasoning_effort = "none";
tried.add("reasoning_effort_none");
return true;
}
if (!(field in body)) return false;
delete body[field];
tried.add(field);
return true;
}
function applyToolsReasoningEffortNone(
body: Record<string, unknown>,
tried: Set<string>,
): boolean {
if (
!Array.isArray(body.tools) ||
body.tools.length === 0 ||
body.reasoning_effort === "none" ||
tried.has("reasoning_effort_none")
) {
return false;
}
body.reasoning_effort = "none";
tried.add("reasoning_effort_none");
return true;
}
function replayResponse(source: Response, text: string): Response {
return new Response(text, {
status: source.status,
@@ -262,11 +333,19 @@ export async function fetchWithGenerationCompat(
for (let attempt = 0; attempt < 8; attempt++) {
last = await send(body);
if (last.ok || last.status !== 400) return last;
if (last.ok || (last.status !== 400 && last.status !== 422)) return last;
const text = await last.text();
const next = { ...body };
if (isToolsReasoningEffortConflictError(text, last.status)) {
if (applyToolsReasoningEffortNone(next, tried)) {
body = next;
continue;
}
}
const field = parseRejectedGenerationField(text);
if (!field) return replayResponse(last, text);
const next = { ...body };
if (!applyRejectedGenerationField(next, field, tried)) {
return replayResponse(last, text);
}