Add AI model pricing, Bedrock provider, and InferenceProvider/ModelFamily split (#18155)
## Summary - **Model pricing overhaul**: All model constants updated with accurate pricing in dollars per 1M tokens, including cached input rates, cache creation rates, and tiered >200k context pricing - **New providers**: Added Google (Gemini 3.x), Mistral, and AWS Bedrock as inference providers. Bedrock serves Claude Opus 4.6 and Sonnet 4.6 via AWS, with proper credential handling following the existing S3/SES pattern - **InferenceProvider/ModelFamily split**: Refactored `ModelProvider` into two orthogonal enums — `InferenceProvider` (who serves the model: auth, SDK, metadata format) and `ModelFamily` (who created it: token counting semantics). This eliminates growing `||` chains for token normalization checks like `excludesCachedTokens` - **Billing improvements**: Reasoning tokens charged at output rate, cache token discounts applied accurately, real errors thrown to Sentry on billing failures ## Test plan - [x] All existing unit tests updated and passing (23 tests across 3 test files) - [x] Lint passes for both twenty-server and twenty-front - [ ] CI checks pass Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+6
-4
@@ -4,8 +4,9 @@ import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/
|
||||
|
||||
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
||||
import {
|
||||
InferenceProvider,
|
||||
ModelFamily,
|
||||
type ModelId,
|
||||
ModelProvider,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
import { ClientConfigController } from './client-config.controller';
|
||||
@@ -52,9 +53,10 @@ describe('ClientConfigController', () => {
|
||||
{
|
||||
modelId: 'gpt-4o' as ModelId,
|
||||
label: 'GPT-4o',
|
||||
provider: ModelProvider.OPENAI,
|
||||
inputCostPer1kTokensInCredits: 2.5,
|
||||
outputCostPer1kTokensInCredits: 10.0,
|
||||
modelFamily: ModelFamily.OPENAI,
|
||||
inferenceProvider: InferenceProvider.OPENAI,
|
||||
inputCostPerMillionTokensInCredits: 2500000,
|
||||
outputCostPerMillionTokensInCredits: 10000000,
|
||||
},
|
||||
],
|
||||
authProviders: {
|
||||
|
||||
+15
-7
@@ -7,16 +7,21 @@ import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { AuthProvidersDTO } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
|
||||
import {
|
||||
InferenceProvider,
|
||||
ModelFamily,
|
||||
ModelId,
|
||||
ModelProvider,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
registerEnumType(FeatureFlagKey, {
|
||||
name: 'FeatureFlagKey',
|
||||
});
|
||||
|
||||
registerEnumType(ModelProvider, {
|
||||
name: 'ModelProvider',
|
||||
registerEnumType(InferenceProvider, {
|
||||
name: 'InferenceProvider',
|
||||
});
|
||||
|
||||
registerEnumType(ModelFamily, {
|
||||
name: 'ModelFamily',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
@@ -36,14 +41,17 @@ export class ClientAIModelConfig {
|
||||
@Field(() => String)
|
||||
label: string;
|
||||
|
||||
@Field(() => ModelProvider)
|
||||
provider: ModelProvider;
|
||||
@Field(() => ModelFamily, { nullable: true })
|
||||
modelFamily?: ModelFamily;
|
||||
|
||||
@Field(() => InferenceProvider)
|
||||
inferenceProvider: InferenceProvider;
|
||||
|
||||
@Field(() => Number)
|
||||
inputCostPer1kTokensInCredits: number;
|
||||
inputCostPerMillionTokensInCredits: number;
|
||||
|
||||
@Field(() => Number)
|
||||
outputCostPer1kTokensInCredits: number;
|
||||
outputCostPerMillionTokensInCredits: number;
|
||||
|
||||
@Field(() => NativeModelCapabilities, { nullable: true })
|
||||
nativeCapabilities?: NativeModelCapabilities;
|
||||
|
||||
+16
-15
@@ -12,12 +12,12 @@ import {
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { convertCentsToBillingCredits } from 'src/engine/metadata-modules/ai/ai-billing/utils/convert-cents-to-billing-credits.util';
|
||||
import { convertDollarsToBillingCredits } from 'src/engine/metadata-modules/ai/ai-billing/utils/convert-dollars-to-billing-credits.util';
|
||||
import {
|
||||
AI_MODELS,
|
||||
DEFAULT_FAST_MODEL,
|
||||
DEFAULT_SMART_MODEL,
|
||||
ModelProvider,
|
||||
InferenceProvider,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@@ -54,16 +54,17 @@ export class ClientConfigService {
|
||||
return {
|
||||
modelId: registeredModel.modelId,
|
||||
label: builtInModel?.label || registeredModel.modelId,
|
||||
provider: registeredModel.provider,
|
||||
modelFamily: builtInModel?.modelFamily,
|
||||
inferenceProvider: registeredModel.inferenceProvider,
|
||||
nativeCapabilities: builtInModel?.nativeCapabilities,
|
||||
inputCostPer1kTokensInCredits: builtInModel
|
||||
? convertCentsToBillingCredits(
|
||||
builtInModel.inputCostPer1kTokensInCents,
|
||||
inputCostPerMillionTokensInCredits: builtInModel
|
||||
? convertDollarsToBillingCredits(
|
||||
builtInModel.inputCostPerMillionTokens,
|
||||
)
|
||||
: 0,
|
||||
outputCostPer1kTokensInCredits: builtInModel
|
||||
? convertCentsToBillingCredits(
|
||||
builtInModel.outputCostPer1kTokensInCents,
|
||||
outputCostPerMillionTokensInCredits: builtInModel
|
||||
? convertDollarsToBillingCredits(
|
||||
builtInModel.outputCostPerMillionTokens,
|
||||
)
|
||||
: 0,
|
||||
deprecated: builtInModel?.deprecated,
|
||||
@@ -96,16 +97,16 @@ export class ClientConfigService {
|
||||
{
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
label: `Smart (${defaultPerformanceModelLabel})`,
|
||||
provider: ModelProvider.NONE,
|
||||
inputCostPer1kTokensInCredits: 0,
|
||||
outputCostPer1kTokensInCredits: 0,
|
||||
inferenceProvider: InferenceProvider.NONE,
|
||||
inputCostPerMillionTokensInCredits: 0,
|
||||
outputCostPerMillionTokensInCredits: 0,
|
||||
},
|
||||
{
|
||||
modelId: DEFAULT_FAST_MODEL,
|
||||
label: `Fast (${defaultSpeedModelLabel})`,
|
||||
provider: ModelProvider.NONE,
|
||||
inputCostPer1kTokensInCredits: 0,
|
||||
outputCostPer1kTokensInCredits: 0,
|
||||
inferenceProvider: InferenceProvider.NONE,
|
||||
inputCostPerMillionTokensInCredits: 0,
|
||||
outputCostPerMillionTokensInCredits: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1196,7 +1196,7 @@ export class ConfigVariables {
|
||||
})
|
||||
@IsOptional()
|
||||
DEFAULT_AI_SPEED_MODEL_ID =
|
||||
'gpt-4.1-mini,claude-haiku-4-5-20251001,grok-3-mini';
|
||||
'gpt-5-mini,claude-haiku-4-5-20251001,gemini-3-flash-preview,grok-4-1-fast-reasoning,mistral-large-latest';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
@@ -1205,7 +1205,8 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
DEFAULT_AI_PERFORMANCE_MODEL_ID = 'gpt-4.1,claude-sonnet-4-5-20250929,grok-4';
|
||||
DEFAULT_AI_PERFORMANCE_MODEL_ID =
|
||||
'gpt-5.2,claude-sonnet-4-6,gemini-3.1-pro-preview,grok-4,mistral-large-latest';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
@@ -1271,6 +1272,60 @@ export class ConfigVariables {
|
||||
@IsOptional()
|
||||
GROQ_API_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
isSensitive: true,
|
||||
description: 'API key for Google AI (Gemini) integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
GOOGLE_API_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
isSensitive: true,
|
||||
description: 'API key for Mistral AI integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
MISTRAL_API_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
description: 'AWS region for Bedrock integration (e.g., us-east-1)',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsAWSRegion()
|
||||
@IsOptional()
|
||||
AWS_BEDROCK_REGION: AwsRegion;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
isSensitive: true,
|
||||
description: 'AWS access key ID for Bedrock authentication',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
AWS_BEDROCK_ACCESS_KEY_ID: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
isSensitive: true,
|
||||
description: 'AWS secret access key for Bedrock authentication',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
AWS_BEDROCK_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
isSensitive: true,
|
||||
description: 'AWS session token for Bedrock (for IAM role-based auth)',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsOptional()
|
||||
AWS_BEDROCK_SESSION_TOKEN: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVER_CONFIG,
|
||||
description: 'Enable or disable multi-workspace support',
|
||||
|
||||
Reference in New Issue
Block a user