fix(consult): 第 0 步改回 auto,供应商 error 块不再被吞掉
BUG-937 撤回 thinking 模式下的 required toolChoice,只留 activeTools。BUG-938 让咨询流识别 Mastra error 块,公开码 calculation_failed,内部码进日志且不触发合同 retry。
This commit is contained in:
@@ -185,6 +185,8 @@ const knownErrorCodes = new Set([
|
||||
"timeout",
|
||||
"cancelled",
|
||||
"settlement_failed",
|
||||
"thinking_tool_choice_unsupported",
|
||||
"provider_error",
|
||||
]);
|
||||
|
||||
export function toAgentObservabilityErrorCode(error: unknown): string {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
type PublicThinkingSection,
|
||||
} from "./consultation-thinking-plan.ts";
|
||||
|
||||
type Chunk = { type?: string; payload?: Record<string, unknown>; data?: unknown };
|
||||
type Chunk = { type?: string; payload?: Record<string, unknown>; data?: unknown; error?: unknown };
|
||||
type ChunkStream = AsyncIterable<unknown> | ReadableStream<unknown>;
|
||||
|
||||
async function* readChunks(stream: ChunkStream): AsyncIterable<Chunk> {
|
||||
@@ -90,11 +90,33 @@ function isTimeoutOrAbort(error: unknown) {
|
||||
}
|
||||
|
||||
type RunFailedCode = "runtime_contract_incomplete" | "empty_answer" | "answer_truncated" | "calculation_failed";
|
||||
type ProviderStreamErrorCode = "thinking_tool_choice_unsupported" | "provider_error";
|
||||
|
||||
function providerErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function classifyProviderStreamError(error: unknown): ProviderStreamErrorCode {
|
||||
return providerErrorMessage(error).includes("Thinking mode does not support this tool_choice")
|
||||
? "thinking_tool_choice_unsupported"
|
||||
: "provider_error";
|
||||
}
|
||||
|
||||
function chunkProviderError(chunk: Chunk): unknown {
|
||||
if (chunk.error !== undefined) return chunk.error;
|
||||
return chunk.payload?.error;
|
||||
}
|
||||
|
||||
function runFailedCode(error: unknown, emitted: boolean): RunFailedCode {
|
||||
if (error instanceof Error && error.message === "runtime_contract_incomplete") return "runtime_contract_incomplete";
|
||||
if (error instanceof Error && error.message === "empty_answer") return "empty_answer";
|
||||
if (error instanceof Error && error.message === "answer_truncated") return "answer_truncated";
|
||||
if (
|
||||
error instanceof Error
|
||||
&& (error.message === "thinking_tool_choice_unsupported" || error.message === "provider_error")
|
||||
) {
|
||||
return "calculation_failed";
|
||||
}
|
||||
if (emitted && isTimeoutOrAbort(error)) return "answer_truncated";
|
||||
return "calculation_failed";
|
||||
}
|
||||
@@ -383,6 +405,21 @@ export function streamAgentResponse(options: StreamAgentResponseOptions) {
|
||||
const stepCountBeforeAttempt = options.state.modelStepCount;
|
||||
try {
|
||||
for await (const chunk of readChunks(stream)) {
|
||||
if (chunk.type === "error") {
|
||||
const raw = chunkProviderError(chunk);
|
||||
const code = classifyProviderStreamError(raw);
|
||||
appendConsultationRuntimeStep(options.state, {
|
||||
kind: "validation",
|
||||
name: "model-stream-error",
|
||||
status: "failed",
|
||||
});
|
||||
console.error("[consult-provider-error]", {
|
||||
requestId: options.requestId,
|
||||
code,
|
||||
messageHead: providerErrorMessage(raw).slice(0, 200),
|
||||
});
|
||||
throw new Error(code);
|
||||
}
|
||||
for (const event of mapChunk(chunk, options, startedAt, toolErrors)) send(controller, event);
|
||||
flushThinkingPlan(controller);
|
||||
if (chunk.type === "step-finish") options.state.modelStepCount += 1;
|
||||
|
||||
Reference in New Issue
Block a user