fix: tune sub2api codex pool concurrency
This commit is contained in:
@@ -74,6 +74,7 @@ interface CodexPoolConfig {
|
||||
apiKeySecretName: string;
|
||||
apiKeySecretKey: string;
|
||||
minOwnerBalanceUsd: number;
|
||||
minOwnerConcurrency: number;
|
||||
defaultAccountCapacity: number;
|
||||
defaultTempUnschedulable: CodexTempUnschedulablePolicy;
|
||||
profiles: CodexPoolProfileConfig[];
|
||||
@@ -262,6 +263,7 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
|
||||
apiKeySecretName: pool.apiKeySecretName,
|
||||
apiKeySecretKey: pool.apiKeySecretKey,
|
||||
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
defaultAccountCapacity: pool.defaultAccountCapacity,
|
||||
},
|
||||
profiles: profiles.map((profile) => ({
|
||||
@@ -567,6 +569,7 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
apiKeySecretName: stringValue(pool.apiKeySecretName) ?? defaults.apiKeySecretName,
|
||||
apiKeySecretKey: stringValue(pool.apiKeySecretKey) ?? defaults.apiKeySecretKey,
|
||||
minOwnerBalanceUsd: numberValue(pool.minOwnerBalanceUsd) ?? defaults.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: readAccountCapacity(pool.minOwnerConcurrency, "pool.minOwnerConcurrency"),
|
||||
defaultAccountCapacity: readAccountCapacity(pool.defaultAccountCapacity, "pool.defaultAccountCapacity"),
|
||||
defaultTempUnschedulable,
|
||||
profiles: readProfileConfig(parsed.profiles, defaults.profiles, defaultTempUnschedulable),
|
||||
@@ -579,6 +582,10 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
throw new Error(`${codexPoolConfigPath}.pool.apiKeySecretKey must be a valid secret key name`);
|
||||
}
|
||||
if (config.minOwnerBalanceUsd <= 0) throw new Error(`${codexPoolConfigPath}.pool.minOwnerBalanceUsd must be > 0`);
|
||||
const declaredAccountCapacity = config.profiles.reduce((total, profile) => total + (profile.capacity ?? config.defaultAccountCapacity), 0);
|
||||
if (declaredAccountCapacity > 0 && config.minOwnerConcurrency < declaredAccountCapacity) {
|
||||
throw new Error(`${codexPoolConfigPath}.pool.minOwnerConcurrency must be >= the sum of declared account capacities (${declaredAccountCapacity})`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -589,6 +596,8 @@ function defaultCodexPoolConfig(): CodexPoolConfig {
|
||||
apiKeySecretName: defaultPoolApiKeySecretName,
|
||||
apiKeySecretKey: defaultPoolApiKeySecretKey,
|
||||
minOwnerBalanceUsd: defaultMinOwnerBalanceUsd,
|
||||
// Only used for the no-YAML fallback path; real pool configs must declare pool.minOwnerConcurrency.
|
||||
minOwnerConcurrency: 1,
|
||||
defaultAccountCapacity: 5,
|
||||
defaultTempUnschedulable: defaultCodexTempUnschedulablePolicy(),
|
||||
profiles: [],
|
||||
@@ -986,6 +995,7 @@ function poolTarget(pool = readCodexPoolConfig()): Record<string, unknown> {
|
||||
groupName: pool.groupName,
|
||||
apiKeyName: pool.apiKeyName,
|
||||
apiKeySecret: `${namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
defaultAccountCapacity: pool.defaultAccountCapacity,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
@@ -1698,6 +1708,7 @@ POOL_API_KEY_NAME = "${pool.apiKeyName}"
|
||||
POOL_API_KEY_SECRET_NAME = "${pool.apiKeySecretName}"
|
||||
POOL_API_KEY_SECRET_KEY = "${pool.apiKeySecretKey}"
|
||||
MIN_OWNER_BALANCE_USD = ${JSON.stringify(pool.minOwnerBalanceUsd)}
|
||||
MIN_OWNER_CONCURRENCY = ${JSON.stringify(pool.minOwnerConcurrency)}
|
||||
POOL_DEFAULT_ACCOUNT_CAPACITY = ${JSON.stringify(pool.defaultAccountCapacity)}
|
||||
EXPECTED_ACCOUNT_CAPACITIES = ${JSON.stringify(desiredAccountCapacityMap(pool))}
|
||||
EXPECTED_ACCOUNT_WS_MODES = json.loads(${JSON.stringify(JSON.stringify(desiredAccountWebSocketsV2ModeMap(pool)))})
|
||||
@@ -2105,6 +2116,37 @@ def ensure_pool_owner_balance(token, user_id):
|
||||
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
|
||||
}
|
||||
|
||||
def ensure_pool_owner_concurrency(token, user_id):
|
||||
user = get_admin_user(token, user_id)
|
||||
try:
|
||||
current = int(user.get("concurrency") or 0)
|
||||
except Exception:
|
||||
current = 0
|
||||
if current >= MIN_OWNER_CONCURRENCY:
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "kept-existing",
|
||||
"userId": user_id,
|
||||
"concurrencyBefore": current,
|
||||
"concurrencyAfter": current,
|
||||
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
|
||||
}
|
||||
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/users/{user_id}", bearer=token, payload={
|
||||
"concurrency": MIN_OWNER_CONCURRENCY,
|
||||
}), "set API key owner concurrency")
|
||||
try:
|
||||
after = int(updated.get("concurrency") or MIN_OWNER_CONCURRENCY) if isinstance(updated, dict) else MIN_OWNER_CONCURRENCY
|
||||
except Exception:
|
||||
after = MIN_OWNER_CONCURRENCY
|
||||
return {
|
||||
"ok": after >= MIN_OWNER_CONCURRENCY,
|
||||
"action": "set",
|
||||
"userId": user_id,
|
||||
"concurrencyBefore": current,
|
||||
"concurrencyAfter": after,
|
||||
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
|
||||
}
|
||||
|
||||
def validate_gateway(api_key):
|
||||
resp = curl_api("GET", "/v1/models", bearer=api_key)
|
||||
parsed = resp.get("json")
|
||||
@@ -2369,9 +2411,10 @@ def run_sync():
|
||||
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id)
|
||||
api_key_result = ensure_sub2api_api_key(token, api_key, group_id)
|
||||
owner_balance = ensure_pool_owner_balance(token, api_key_result["userId"])
|
||||
owner_concurrency = ensure_pool_owner_concurrency(token, api_key_result["userId"])
|
||||
gateway = validate_gateway(api_key)
|
||||
return {
|
||||
"ok": gateway["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"ok": gateway["ok"] is True and owner_concurrency["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"mode": "sync",
|
||||
"namespace": NAMESPACE,
|
||||
"serviceDns": SERVICE_DNS,
|
||||
@@ -2403,6 +2446,7 @@ def run_sync():
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"ownerBalance": owner_balance,
|
||||
"ownerConcurrency": owner_concurrency,
|
||||
"validation": {"gatewayModels": gateway},
|
||||
}
|
||||
|
||||
@@ -2413,14 +2457,16 @@ def run_validate():
|
||||
admin_email, token = login()
|
||||
key_item = next((item for item in list_user_keys(token) if item.get("key") == api_key), None)
|
||||
owner_balance = None
|
||||
owner_concurrency = None
|
||||
if key_item is not None and key_item.get("user_id") is not None:
|
||||
owner_balance = ensure_pool_owner_balance(token, key_item["user_id"])
|
||||
owner_concurrency = ensure_pool_owner_concurrency(token, key_item["user_id"])
|
||||
capacity_status = account_capacity_status(token)
|
||||
ws_v2_status = account_ws_v2_status(token)
|
||||
temp_unschedulable_status = account_temp_unschedulable_status(token)
|
||||
gateway = validate_gateway(api_key)
|
||||
return {
|
||||
"ok": gateway["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"ok": gateway["ok"] is True and (owner_concurrency is None or owner_concurrency["ok"] is True) and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"mode": "validate",
|
||||
"namespace": NAMESPACE,
|
||||
"serviceDns": SERVICE_DNS,
|
||||
@@ -2434,6 +2480,7 @@ def run_validate():
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"ownerBalance": owner_balance,
|
||||
"ownerConcurrency": owner_concurrency,
|
||||
"capacity": capacity_status,
|
||||
"webSocketsV2": ws_v2_status,
|
||||
"tempUnschedulable": temp_unschedulable_status,
|
||||
|
||||
Reference in New Issue
Block a user