mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 16:09:34 +00:00
feat(groups): add deny-or-downgrade control for reasoning effort ceiling
OpenAI/Composite groups can now reject requests that exceed the max reasoning effort instead of always rewriting them down to the ceiling. Existing groups keep the previous auto-downgrade behavior.
This commit is contained in:
@@ -17,6 +17,22 @@
|
||||
<p class="input-hint">{{ t("admin.groups.form.maxReasoningEffortHint") }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label :for="`${idPrefix}-over-limit`" class="input-label">
|
||||
{{ t("admin.groups.form.maxReasoningEffortOverLimit") }}
|
||||
</label>
|
||||
<Select
|
||||
:id="`${idPrefix}-over-limit`"
|
||||
:model-value="overLimit"
|
||||
:options="overLimitOptions"
|
||||
:aria-label="t('admin.groups.form.maxReasoningEffortOverLimit')"
|
||||
:searchable="false"
|
||||
:disabled="!maxEffort"
|
||||
@update:model-value="updateOverLimit"
|
||||
/>
|
||||
<p class="input-hint">{{ t("admin.groups.form.maxReasoningEffortOverLimitHint") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between gap-3">
|
||||
<label class="input-label mb-0">
|
||||
@@ -120,6 +136,8 @@ import Select from "@/components/common/Select.vue";
|
||||
import {
|
||||
createReasoningEffortMappingRow,
|
||||
reasoningEffortOptionsForPlatform,
|
||||
reasoningEffortOverLimitDeny,
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
validateReasoningEffortMappings,
|
||||
type ReasoningEffortMappingErrorCode,
|
||||
type ReasoningEffortMappingRow,
|
||||
@@ -129,11 +147,13 @@ const props = defineProps<{
|
||||
idPrefix: string;
|
||||
platform: GroupPlatform;
|
||||
maxEffort: string;
|
||||
overLimit: string;
|
||||
mappings: ReasoningEffortMappingRow[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:maxEffort", value: string): void;
|
||||
(event: "update:overLimit", value: string): void;
|
||||
(event: "update:mappings", value: ReasoningEffortMappingRow[]): void;
|
||||
}>();
|
||||
|
||||
@@ -142,6 +162,16 @@ const showValidation = ref(false);
|
||||
const reasoningEffortOptions = computed(() =>
|
||||
reasoningEffortOptionsForPlatform(props.platform),
|
||||
);
|
||||
const overLimitOptions = computed(() => [
|
||||
{
|
||||
value: reasoningEffortOverLimitDowngrade,
|
||||
label: t("admin.groups.form.maxReasoningEffortOverLimitDowngrade"),
|
||||
},
|
||||
{
|
||||
value: reasoningEffortOverLimitDeny,
|
||||
label: t("admin.groups.form.maxReasoningEffortOverLimitDeny"),
|
||||
},
|
||||
]);
|
||||
const validationErrors = computed(() =>
|
||||
validateReasoningEffortMappings(props.mappings, props.platform),
|
||||
);
|
||||
@@ -153,6 +183,13 @@ const updateMaxEffort = (value: string | number | boolean | null) => {
|
||||
emit("update:maxEffort", asString(value));
|
||||
};
|
||||
|
||||
const updateOverLimit = (value: string | number | boolean | null) => {
|
||||
emit(
|
||||
"update:overLimit",
|
||||
asString(value) || reasoningEffortOverLimitDowngrade,
|
||||
);
|
||||
};
|
||||
|
||||
const updateMapping = (
|
||||
id: string,
|
||||
field: "from" | "to",
|
||||
|
||||
@@ -859,7 +859,11 @@ export default {
|
||||
rpmLimitHint: 'Max requests per minute for each user in this group; 0 = unlimited. Once set, it takes over per-user rate limiting in this group (overrides the user-level rpm_limit fallback).',
|
||||
maxReasoningEffort: 'Max reasoning effort',
|
||||
maxReasoningEffortUnlimited: 'Unlimited (follow request)',
|
||||
maxReasoningEffortHint: 'Limits explicit OpenAI reasoning effort requests only. For Composite groups, it applies only to requests resolved to OpenAI. Higher values are capped; omitted effort stays omitted. The ceiling takes precedence over reasoning effort mappings.',
|
||||
maxReasoningEffortHint: 'Limits explicit OpenAI reasoning effort requests only. For Composite groups, it applies only to requests resolved to OpenAI. Omitted effort stays omitted. The ceiling takes precedence over reasoning effort mappings.',
|
||||
maxReasoningEffortOverLimit: 'Over-limit access control',
|
||||
maxReasoningEffortOverLimitDowngrade: 'Automatically downgrade when over limit',
|
||||
maxReasoningEffortOverLimitDeny: 'Deny access',
|
||||
maxReasoningEffortOverLimitHint: 'Applies after a ceiling is set. Downgrade rewrites values above the ceiling to the ceiling. Deny rejects the request.',
|
||||
reasoningEffortMappings: 'Reasoning effort mappings',
|
||||
addReasoningEffortMapping: 'Add mapping',
|
||||
removeReasoningEffortMapping: 'Remove mapping',
|
||||
|
||||
@@ -841,7 +841,11 @@ export default {
|
||||
rpmLimitHint: '每用户在本分组每分钟最大请求数,0 = 不限制;一旦设置即接管该用户的限流(覆盖用户级 rpm_limit)',
|
||||
maxReasoningEffort: '推理强度上限',
|
||||
maxReasoningEffortUnlimited: '不限制(跟随请求)',
|
||||
maxReasoningEffortHint: '仅限制客户端主动请求的 OpenAI reasoning effort;Composite 分组仅对解析到 OpenAI 的请求生效。超过上限时自动降档,不会为缺省请求主动开启推理。上限优先级高于推理强度映射。',
|
||||
maxReasoningEffortHint: '仅限制客户端主动请求的 OpenAI reasoning effort;Composite 分组仅对解析到 OpenAI 的请求生效。不会为缺省请求主动开启推理。上限优先级高于推理强度映射。',
|
||||
maxReasoningEffortOverLimit: '超限访问控制',
|
||||
maxReasoningEffortOverLimitDowngrade: '超过上限时自动降档',
|
||||
maxReasoningEffortOverLimitDeny: '拒绝访问',
|
||||
maxReasoningEffortOverLimitHint: '设置上限后生效。自动降档会将超过上限的请求改写为上限值后转发;拒绝访问则直接返回错误。',
|
||||
reasoningEffortMappings: '推理强度映射',
|
||||
addReasoningEffortMapping: '添加映射',
|
||||
removeReasoningEffortMapping: '删除映射',
|
||||
|
||||
@@ -557,6 +557,7 @@ export interface Group {
|
||||
rate_multiplier: number
|
||||
rpm_limit?: number // Group-level RPM cap (0 = unlimited); overrides user-level rpm_limit when set
|
||||
max_reasoning_effort?: string // OpenAI/Codex reasoning ceiling; empty means unlimited
|
||||
max_reasoning_effort_over_limit?: string // downgrade (default) or deny when over the ceiling
|
||||
reasoning_effort_mappings?: ReasoningEffortMapping[]
|
||||
is_exclusive: boolean
|
||||
status: 'active' | 'inactive'
|
||||
@@ -818,6 +819,7 @@ export interface CreateGroupRequest {
|
||||
model_routing_enabled?: boolean
|
||||
rpm_limit?: number
|
||||
max_reasoning_effort?: string
|
||||
max_reasoning_effort_over_limit?: string
|
||||
reasoning_effort_mappings?: ReasoningEffortMapping[]
|
||||
require_oauth_only?: boolean
|
||||
require_privacy_set?: boolean
|
||||
@@ -880,6 +882,7 @@ export interface UpdateGroupRequest {
|
||||
model_routing_enabled?: boolean
|
||||
rpm_limit?: number
|
||||
max_reasoning_effort?: string
|
||||
max_reasoning_effort_over_limit?: string
|
||||
reasoning_effort_mappings?: ReasoningEffortMapping[]
|
||||
require_oauth_only?: boolean
|
||||
require_privacy_set?: boolean
|
||||
|
||||
@@ -631,6 +631,7 @@
|
||||
id-prefix="create-group-reasoning"
|
||||
:platform="createForm.platform"
|
||||
v-model:max-effort="createForm.max_reasoning_effort"
|
||||
v-model:over-limit="createForm.max_reasoning_effort_over_limit"
|
||||
v-model:mappings="createForm.reasoning_effort_mappings"
|
||||
/>
|
||||
<div
|
||||
@@ -2361,6 +2362,7 @@
|
||||
id-prefix="edit-group-reasoning"
|
||||
:platform="editForm.platform"
|
||||
v-model:max-effort="editForm.max_reasoning_effort"
|
||||
v-model:over-limit="editForm.max_reasoning_effort_over_limit"
|
||||
v-model:mappings="editForm.reasoning_effort_mappings"
|
||||
/>
|
||||
<div v-if="editForm.subscription_type !== 'subscription'">
|
||||
@@ -4495,8 +4497,10 @@ import {
|
||||
} from "./groupsProfitControl";
|
||||
import {
|
||||
normalizeReasoningEffortForPlatform,
|
||||
normalizeReasoningEffortOverLimit,
|
||||
reasoningEffortMappingsToAPI,
|
||||
reasoningEffortMappingsToRows,
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
supportsReasoningEffortPolicyPlatform,
|
||||
type ReasoningEffortMappingRow,
|
||||
} from "./groupsReasoningEffort";
|
||||
@@ -5105,6 +5109,7 @@ const createForm = reactive({
|
||||
// 分组级 RPM 限制(每用户每分钟最大请求数;0 = 不限制)
|
||||
rpm_limit: 0 as number,
|
||||
max_reasoning_effort: "",
|
||||
max_reasoning_effort_over_limit: reasoningEffortOverLimitDowngrade,
|
||||
reasoning_effort_mappings: [] as ReasoningEffortMappingRow[],
|
||||
});
|
||||
|
||||
@@ -5467,6 +5472,7 @@ const editForm = reactive({
|
||||
// 分组级 RPM 限制(每用户每分钟最大请求数;0 = 不限制)
|
||||
rpm_limit: 0 as number,
|
||||
max_reasoning_effort: "",
|
||||
max_reasoning_effort_over_limit: reasoningEffortOverLimitDowngrade,
|
||||
reasoning_effort_mappings: [] as ReasoningEffortMappingRow[],
|
||||
});
|
||||
|
||||
@@ -5907,6 +5913,7 @@ const closeCreateModal = () => {
|
||||
createForm.copy_accounts_from_group_ids = [];
|
||||
createForm.rpm_limit = 0;
|
||||
createForm.max_reasoning_effort = "";
|
||||
createForm.max_reasoning_effort_over_limit = reasoningEffortOverLimitDowngrade;
|
||||
createForm.reasoning_effort_mappings = [];
|
||||
createReasoningEffortPolicyRef.value?.resetValidation();
|
||||
resetModelsListState(createModelsListState);
|
||||
@@ -6177,6 +6184,9 @@ const handleEdit = async (group: AdminGroup) => {
|
||||
group.platform,
|
||||
group.max_reasoning_effort,
|
||||
);
|
||||
editForm.max_reasoning_effort_over_limit = normalizeReasoningEffortOverLimit(
|
||||
group.max_reasoning_effort_over_limit,
|
||||
);
|
||||
editForm.reasoning_effort_mappings = reasoningEffortMappingsToRows(
|
||||
group.reasoning_effort_mappings,
|
||||
group.platform,
|
||||
@@ -6198,6 +6208,7 @@ const closeEditModal = () => {
|
||||
showEditModal.value = false;
|
||||
editingGroup.value = null;
|
||||
editForm.max_reasoning_effort = "";
|
||||
editForm.max_reasoning_effort_over_limit = reasoningEffortOverLimitDowngrade;
|
||||
editForm.reasoning_effort_mappings = [];
|
||||
editReasoningEffortPolicyRef.value?.resetValidation();
|
||||
editModelRoutingRules.value = [];
|
||||
@@ -6673,6 +6684,13 @@ watch(
|
||||
newVal,
|
||||
createForm.max_reasoning_effort,
|
||||
);
|
||||
createForm.max_reasoning_effort_over_limit = supportsReasoningEffortPolicyPlatform(
|
||||
newVal,
|
||||
)
|
||||
? normalizeReasoningEffortOverLimit(
|
||||
createForm.max_reasoning_effort_over_limit,
|
||||
)
|
||||
: reasoningEffortOverLimitDowngrade;
|
||||
createForm.reasoning_effort_mappings = reasoningEffortMappingsToRows(
|
||||
reasoningEffortMappingsToAPI(createForm.reasoning_effort_mappings),
|
||||
newVal,
|
||||
@@ -6723,6 +6741,13 @@ watch(
|
||||
newVal,
|
||||
editForm.max_reasoning_effort,
|
||||
);
|
||||
editForm.max_reasoning_effort_over_limit = supportsReasoningEffortPolicyPlatform(
|
||||
newVal,
|
||||
)
|
||||
? normalizeReasoningEffortOverLimit(
|
||||
editForm.max_reasoning_effort_over_limit,
|
||||
)
|
||||
: reasoningEffortOverLimitDowngrade;
|
||||
editForm.reasoning_effort_mappings = reasoningEffortMappingsToRows(
|
||||
reasoningEffortMappingsToAPI(editForm.reasoning_effort_mappings),
|
||||
newVal,
|
||||
|
||||
@@ -3,9 +3,12 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createReasoningEffortMappingRow,
|
||||
normalizeReasoningEffortForPlatform,
|
||||
normalizeReasoningEffortOverLimit,
|
||||
reasoningEffortMappingsToAPI,
|
||||
reasoningEffortMappingsToRows,
|
||||
reasoningEffortOptionsForPlatform,
|
||||
reasoningEffortOverLimitDeny,
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
supportsReasoningEffortPolicyPlatform,
|
||||
validateReasoningEffortMappings,
|
||||
} from "../groupsReasoningEffort";
|
||||
@@ -89,4 +92,22 @@ describe("groupsReasoningEffort", () => {
|
||||
[row.id]: { from: "unsupportedFrom" },
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes over-limit access control to downgrade or deny", () => {
|
||||
expect(normalizeReasoningEffortOverLimit(undefined)).toBe(
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
);
|
||||
expect(normalizeReasoningEffortOverLimit("")).toBe(
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
);
|
||||
expect(normalizeReasoningEffortOverLimit(" downgrade ")).toBe(
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
);
|
||||
expect(normalizeReasoningEffortOverLimit("DENY")).toBe(
|
||||
reasoningEffortOverLimitDeny,
|
||||
);
|
||||
expect(normalizeReasoningEffortOverLimit("block")).toBe(
|
||||
reasoningEffortOverLimitDowngrade,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,9 @@ const openAIReasoningEffortValues = [
|
||||
"max",
|
||||
] as const;
|
||||
|
||||
export const reasoningEffortOverLimitDowngrade = "downgrade";
|
||||
export const reasoningEffortOverLimitDeny = "deny";
|
||||
|
||||
const reasoningEffortValuesForPlatform = (
|
||||
platform: GroupPlatform,
|
||||
): readonly string[] =>
|
||||
@@ -41,6 +44,14 @@ export function normalizeReasoningEffortForPlatform(
|
||||
: "";
|
||||
}
|
||||
|
||||
export function normalizeReasoningEffortOverLimit(
|
||||
value: string | null | undefined,
|
||||
): string {
|
||||
return value?.trim().toLowerCase() === reasoningEffortOverLimitDeny
|
||||
? reasoningEffortOverLimitDeny
|
||||
: reasoningEffortOverLimitDowngrade;
|
||||
}
|
||||
|
||||
export interface ReasoningEffortMappingRow extends ReasoningEffortMapping {
|
||||
id: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user