proper streaming filter
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { KeyCheckerBase } from "../key-checker-base";
|
import { KeyCheckerBase } from "../key-checker-base";
|
||||||
import type { OpenAIKey, OpenAIKeyProvider } from "./provider";
|
import type { OpenAIKey, OpenAIKeyProvider, OpenAIKeyUpdate } from "./provider";
|
||||||
import { OpenAIModelFamily, getOpenAIModelFamily } from "../../models";
|
import { OpenAIModelFamily, getOpenAIModelFamily } from "../../models";
|
||||||
import { getAxiosInstance } from "../../network";
|
import { getAxiosInstance } from "../../network";
|
||||||
|
|
||||||
@@ -51,29 +51,38 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
this.testLiveness(key),
|
this.testLiveness(key),
|
||||||
this.maybeCreateOrganizationClones(key),
|
this.maybeCreateOrganizationClones(key),
|
||||||
]);
|
]);
|
||||||
const updates = {
|
const updates: OpenAIKeyUpdate = {
|
||||||
modelFamilies: provisionedModels,
|
modelFamilies: provisionedModels,
|
||||||
isTrial: livenessTest.rateLimit <= 250,
|
isTrial: livenessTest.rateLimit <= 250,
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the model list claims to include gpt-image-1, verify organization status with streaming test
|
// Test organization verification status for all keys
|
||||||
if (provisionedModels.includes("gpt-image") || provisionedModels.includes("o3")) {
|
// This is needed for GPT-5, o1, o3, and gpt-image-1 streaming restrictions
|
||||||
try {
|
try {
|
||||||
const isVerifiedOrg = await this.testVerifiedOrg(key);
|
const isVerifiedOrg = await this.testVerifiedOrg(key);
|
||||||
if (!isVerifiedOrg) {
|
// Always set the organizationVerified field for all keys
|
||||||
// Only remove gpt-image from unverified orgs - they can still use o3, just not stream it
|
updates.organizationVerified = isVerifiedOrg;
|
||||||
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
|
||||||
updates.modelFamilies = updatedFamilies;
|
// Only remove gpt-image from unverified orgs if they have it
|
||||||
this.log.warn({ key: key.hash }, "Key's organization is not verified. Removing gpt-image-1 from available models.");
|
if (!isVerifiedOrg && provisionedModels.includes("gpt-image")) {
|
||||||
} else {
|
|
||||||
this.log.info({ key: key.hash }, "Verified organization status for key. Can use gpt-image-1 and o3 streaming.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If test fails, assume no access to be safe, but only for gpt-image
|
|
||||||
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
||||||
updates.modelFamilies = updatedFamilies;
|
updates.modelFamilies = updatedFamilies;
|
||||||
this.log.error({ key: key.hash, error }, "Error testing organization verification status. Removing gpt-image-1 from available models.");
|
this.log.warn({ key: key.hash }, "Key's organization is not verified. Removing gpt-image-1 from available models.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isVerifiedOrg) {
|
||||||
|
this.log.info({ key: key.hash }, "Verified organization status for key. Can use streaming for GPT-5, o1, o3, and gpt-image-1.");
|
||||||
|
} else {
|
||||||
|
this.log.warn({ key: key.hash }, "Key's organization is not verified. Streaming restricted for GPT-5, o1, o3, and gpt-image-1.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If test fails, assume no access to be safe
|
||||||
|
updates.organizationVerified = false;
|
||||||
|
if (provisionedModels.includes("gpt-image")) {
|
||||||
|
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
||||||
|
updates.modelFamilies = updatedFamilies;
|
||||||
|
}
|
||||||
|
this.log.error({ key: key.hash, error }, "Error testing organization verification status. Assuming not verified for safety.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateKey(key.hash, updates);
|
this.updateKey(key.hash, updates);
|
||||||
@@ -334,17 +343,17 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether the key's organization is verified by attempting to stream from the o3 model.
|
* Tests whether the key's organization is verified by attempting to stream from the gpt-5-mini model.
|
||||||
* Only verified organizations can stream from o3, so this is a reliable test for both
|
* Only verified organizations can stream from GPT-5 models, so this is a reliable test for both
|
||||||
* o3 streaming and gpt-image-1 access (which also requires verified organization status).
|
* GPT-5 streaming and gpt-image-1 access (which also requires verified organization status).
|
||||||
* Returns true if the organization is verified.
|
* Returns true if the organization is verified.
|
||||||
*/
|
*/
|
||||||
public async testVerifiedOrg(key: OpenAIKey): Promise<boolean> {
|
public async testVerifiedOrg(key: OpenAIKey): Promise<boolean> {
|
||||||
this.log.info({ key: key.hash }, "Testing organization verification status via o3 streaming");
|
this.log.info({ key: key.hash }, "Testing organization verification status via gpt-5-mini streaming");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
model: "o3",
|
model: "gpt-5-nano",
|
||||||
messages: [{ role: "user", content: "Hi" }],
|
messages: [{ role: "user", content: "Hi" }],
|
||||||
max_completion_tokens: 1,
|
max_completion_tokens: 1,
|
||||||
stream: true
|
stream: true
|
||||||
@@ -366,7 +375,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{ key: key.hash, status: response.status },
|
{ key: key.hash, status: response.status },
|
||||||
`Organization is verified. Streaming o3 request succeeded with status code ${response.status}`
|
`Organization is verified. Streaming gpt-5-mini request succeeded with status code ${response.status}`
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -379,7 +388,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
if (errorMessage.includes("organization must be verified")) {
|
if (errorMessage.includes("organization must be verified")) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{ key: key.hash, status: response.status, error: errorMessage },
|
{ key: key.hash, status: response.status, error: errorMessage },
|
||||||
"Organization is not verified: verification required for streaming o3"
|
"Organization is not verified: verification required for streaming gpt-5-mini"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -391,7 +400,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{ key: key.hash, status: response.status, error: errorMessage },
|
{ key: key.hash, status: response.status, error: errorMessage },
|
||||||
"Organization is not verified: cannot stream with o3"
|
"Organization is not verified: cannot stream with gpt-5-mini"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -433,7 +442,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{ key: key.hash, status, error: errorMessage },
|
{ key: key.hash, status, error: errorMessage },
|
||||||
"Organization is not verified: cannot stream with o3"
|
"Organization is not verified: cannot stream with gpt-5-mini"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface OpenAIKey extends Key {
|
|||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
/** Whether this is a free trial key. These are prioritized over paid keys if they can fulfill the request. */
|
/** Whether this is a free trial key. These are prioritized over paid keys if they can fulfill the request. */
|
||||||
isTrial: boolean;
|
isTrial: boolean;
|
||||||
|
/** Whether the organization associated with this key is verified. Verified organizations can use streaming for GPT-5 models and gpt-image-1. */
|
||||||
|
organizationVerified?: boolean;
|
||||||
/** Set when key check returns a non-transient 429. */
|
/** Set when key check returns a non-transient 429. */
|
||||||
isOverQuota: boolean;
|
isOverQuota: boolean;
|
||||||
/**
|
/**
|
||||||
@@ -146,7 +148,9 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
|||||||
|
|
||||||
// GPT-5 models (gpt-5, gpt-5-mini, gpt-5-nano) require verified keys for streaming
|
// GPT-5 models (gpt-5, gpt-5-mini, gpt-5-nano) require verified keys for streaming
|
||||||
const isGpt5Model = /^gpt-5(-mini|-nano)?(-\d{4}-\d{2}-\d{2})?$/.test(model) || model === "gpt-5-chat-latest";
|
const isGpt5Model = /^gpt-5(-mini|-nano)?(-\d{4}-\d{2}-\d{2})?$/.test(model) || model === "gpt-5-chat-latest";
|
||||||
const isGpt5StreamingRequest = isGpt5Model && streaming;
|
const isO1Model = /^o1(-mini|-preview)?(-\d{4}-\d{2}-\d{2})?$/.test(model);
|
||||||
|
const isO3Model = /^o3(-mini)?(-\d{4}-\d{2}-\d{2})?$/.test(model);
|
||||||
|
const requiresVerifiedStreaming = (isGpt5Model || isO1Model || isO3Model) && streaming;
|
||||||
|
|
||||||
// First, filter keys based on basic criteria
|
// First, filter keys based on basic criteria
|
||||||
let availableKeys = this.keys.filter(
|
let availableKeys = this.keys.filter(
|
||||||
@@ -174,9 +178,8 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Filter to only include keys from verified organizations
|
// Filter to only include keys from verified organizations
|
||||||
// A key is from a verified organization if our verification test didn't remove gpt-image
|
// Use the organizationVerified field which is set by the key checker
|
||||||
// This is the critical filter that ensures only verified org keys are used
|
const verifiedKeys = availableKeys.filter(key => key.organizationVerified === true);
|
||||||
const verifiedKeys = availableKeys.filter(key => key.modelFamilies.includes("gpt-image"));
|
|
||||||
|
|
||||||
if (verifiedKeys.length > 0) {
|
if (verifiedKeys.length > 0) {
|
||||||
this.log.info(
|
this.log.info(
|
||||||
@@ -192,28 +195,28 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For GPT-5 streaming requests, we need to use only verified keys
|
// For streaming requests with models that require verified organizations
|
||||||
// GPT-5 models (gpt-5, gpt-5-mini, gpt-5-nano) require verified organizations for streaming
|
// GPT-5, o1, and o3 models require verified organizations for streaming
|
||||||
if (isGpt5StreamingRequest) {
|
if (requiresVerifiedStreaming) {
|
||||||
this.log.debug(
|
this.log.debug(
|
||||||
{ model, keyCount: availableKeys.length, streaming },
|
{ model, keyCount: availableKeys.length, streaming },
|
||||||
"Filtering keys for GPT-5 streaming request to ensure verified organization status"
|
"Filtering keys for streaming request to ensure verified organization status"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter to only include keys from verified organizations
|
// Filter to only include keys from verified organizations
|
||||||
// We piggyback on the existing verification logic: verified keys still have gpt-image access
|
// Use the organizationVerified field which is set by the key checker
|
||||||
const verifiedKeys = availableKeys.filter(key => key.modelFamilies.includes("gpt-image"));
|
const verifiedKeys = availableKeys.filter(key => key.organizationVerified === true);
|
||||||
|
|
||||||
if (verifiedKeys.length > 0) {
|
if (verifiedKeys.length > 0) {
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{ model, totalKeys: availableKeys.length, verifiedKeys: verifiedKeys.length, streaming },
|
{ model, totalKeys: availableKeys.length, verifiedKeys: verifiedKeys.length, streaming },
|
||||||
"Using only verified organization keys for GPT-5 streaming request"
|
"Using only verified organization keys for streaming request"
|
||||||
);
|
);
|
||||||
availableKeys = verifiedKeys;
|
availableKeys = verifiedKeys;
|
||||||
} else {
|
} else {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
{ model, totalKeys: availableKeys.length, streaming },
|
{ model, totalKeys: availableKeys.length, streaming },
|
||||||
"No verified organization keys available for GPT-5 streaming request"
|
"No verified organization keys available for streaming request"
|
||||||
);
|
);
|
||||||
// Set availableKeys to empty array to trigger the error below
|
// Set availableKeys to empty array to trigger the error below
|
||||||
availableKeys = [];
|
availableKeys = [];
|
||||||
@@ -221,10 +224,9 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (availableKeys.length === 0) {
|
if (availableKeys.length === 0) {
|
||||||
// Provide specific error message for GPT-5 streaming requests
|
if (requiresVerifiedStreaming) {
|
||||||
if (isGpt5StreamingRequest) {
|
|
||||||
throw new PaymentRequiredError(
|
throw new PaymentRequiredError(
|
||||||
`No verified OpenAI keys available for streaming ${model}. GPT-5 models require verified organization keys for streaming. Please disable streaming.`
|
"No verified OpenAI keys available for streaming GPT-5, o1, or o3 models. Only verified organizations can stream these models. Please disable streaming or contact support to verify your organization."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new PaymentRequiredError(
|
throw new PaymentRequiredError(
|
||||||
|
|||||||
Reference in New Issue
Block a user