retry capacity-style upstream errors

This commit is contained in:
2026-06-29 15:54:36 +08:00
parent 778068af42
commit 283c14b6b4
8 changed files with 384 additions and 24 deletions
+44
View File
@@ -15,6 +15,8 @@ type GatewayConfig = {
model_remap?: string;
endpoints?: string[];
reasoning_equals?: number[];
retryable_status_codes?: number[];
retryable_error_messages?: string[];
non_stream_status_code?: number;
log_match?: boolean;
};
@@ -114,6 +116,8 @@ type ProfileFormModel = {
request_history_limit?: string;
model_remap?: string;
reasoning_equals?: string;
retryable_status_codes?: string;
retryable_error_messages?: string[];
endpoints?: string[];
};
@@ -179,11 +183,15 @@ type ProfileFormState = {
request_history_limit: string;
model_remap: string;
reasoning_equals: string;
retryable_status_codes: string;
retryable_error_messages: string;
endpoints: string;
};
type RuleFormState = {
reasoning_equals: string;
retryable_status_codes: string;
retryable_error_messages: string;
endpoints: string;
non_stream_status_code: string;
log_match: boolean;
@@ -263,6 +271,8 @@ const defaultProfileForm: ProfileFormState = {
request_history_limit: "0",
model_remap: "",
reasoning_equals: "516,1034,1552",
retryable_status_codes: "429,503",
retryable_error_messages: "Selected model is at capacity. Please try a different model.",
endpoints: "/responses\n/chat/completions\n/v1/responses\n/v1/chat/completions",
};
@@ -368,6 +378,12 @@ function profileFormFromStatus(status: StatusPayload | null): ProfileFormState {
reasoning_equals: Array.isArray(config.reasoning_equals)
? config.reasoning_equals.join(",")
: defaultProfileForm.reasoning_equals,
retryable_status_codes: Array.isArray(config.retryable_status_codes)
? config.retryable_status_codes.join(",")
: defaultProfileForm.retryable_status_codes,
retryable_error_messages: Array.isArray(config.retryable_error_messages)
? config.retryable_error_messages.join("\n")
: defaultProfileForm.retryable_error_messages,
endpoints: Array.isArray(config.endpoints) ? config.endpoints.join("\n") : defaultProfileForm.endpoints,
};
}
@@ -376,6 +392,12 @@ function ruleFormFromStatus(status: StatusPayload | null): RuleFormState {
const config = status?.config || {};
return {
reasoning_equals: Array.isArray(config.reasoning_equals) ? config.reasoning_equals.join(", ") : "",
retryable_status_codes: Array.isArray(config.retryable_status_codes)
? config.retryable_status_codes.join(", ")
: defaultProfileForm.retryable_status_codes,
retryable_error_messages: Array.isArray(config.retryable_error_messages)
? config.retryable_error_messages.join("\n")
: defaultProfileForm.retryable_error_messages,
endpoints: Array.isArray(config.endpoints) ? config.endpoints.join("\n") : "",
non_stream_status_code: String(config.non_stream_status_code || 502),
log_match: Boolean(config.log_match),
@@ -517,6 +539,10 @@ export default function App() {
reasoning_equals: splitList(ruleForm.reasoning_equals)
.map((value) => Number.parseInt(value, 10))
.filter((value) => Number.isInteger(value)),
retryable_status_codes: splitList(ruleForm.retryable_status_codes)
.map((value) => Number.parseInt(value, 10))
.filter((value) => Number.isInteger(value)),
retryable_error_messages: splitLines(ruleForm.retryable_error_messages),
endpoints: splitLines(ruleForm.endpoints),
non_stream_status_code: Number.parseInt(ruleForm.non_stream_status_code, 10),
log_match: ruleForm.log_match,
@@ -563,6 +589,8 @@ export default function App() {
request_history_limit: Number.parseInt(profileForm.request_history_limit, 10),
model_remap: profileForm.model_remap,
reasoning_equals: splitList(profileForm.reasoning_equals),
retryable_status_codes: splitList(profileForm.retryable_status_codes),
retryable_error_messages: splitLines(profileForm.retryable_error_messages),
endpoints: splitLines(profileForm.endpoints),
}),
});
@@ -597,6 +625,10 @@ export default function App() {
request_history_limit: form.request_history_limit || defaultProfileForm.request_history_limit,
model_remap: form.model_remap || "",
reasoning_equals: form.reasoning_equals || "",
retryable_status_codes: form.retryable_status_codes || defaultProfileForm.retryable_status_codes,
retryable_error_messages: Array.isArray(form.retryable_error_messages)
? form.retryable_error_messages.join("\n")
: defaultProfileForm.retryable_error_messages,
endpoints: Array.isArray(form.endpoints) ? form.endpoints.join("\n") : "",
});
setProfileMessage({ text: "编辑后保存 profile env;如果保存的是当前运行 profile,后端会直接后台热应用。", tone: "" });
@@ -1077,6 +1109,12 @@ export default function App() {
<Field label="reasoning_equals">
<input value={profileForm.reasoning_equals} placeholder="516,1034,1552" onChange={(event) => setProfileForm({ ...profileForm, reasoning_equals: event.target.value })} />
</Field>
<Field label="retryable_status_codes">
<input value={profileForm.retryable_status_codes} placeholder="429,503" onChange={(event) => setProfileForm({ ...profileForm, retryable_status_codes: event.target.value })} />
</Field>
<Field label="retryable_error_messages" hint="每行一条错误文案;上游 JSON 错误包含任一行时会转成本地重试状态码。">
<textarea value={profileForm.retryable_error_messages} onChange={(event) => setProfileForm({ ...profileForm, retryable_error_messages: event.target.value })} />
</Field>
<Field label="endpoints">
<textarea value={profileForm.endpoints} onChange={(event) => setProfileForm({ ...profileForm, endpoints: event.target.value })} />
</Field>
@@ -1136,6 +1174,12 @@ export default function App() {
<Field label="reasoning_equals">
<input value={ruleForm.reasoning_equals} placeholder="例如:516, 1034, 1552" onChange={(event) => setRuleForm({ ...ruleForm, reasoning_equals: event.target.value })} />
</Field>
<Field label="retryable_status_codes">
<input value={ruleForm.retryable_status_codes} placeholder="例如:429, 503" onChange={(event) => setRuleForm({ ...ruleForm, retryable_status_codes: event.target.value })} />
</Field>
<Field label="retryable_error_messages" hint="每行一条错误文案;命中后会转成本地 non_stream_status_code。">
<textarea value={ruleForm.retryable_error_messages} onChange={(event) => setRuleForm({ ...ruleForm, retryable_error_messages: event.target.value })} />
</Field>
<Field label="endpoints">
<textarea value={ruleForm.endpoints} onChange={(event) => setRuleForm({ ...ruleForm, endpoints: event.target.value })} />
</Field>