feat/enterprise: make per-subscription model allowlists no-op (#62912)

With #62911, per-enterprise-subscription model allowlists are no longer
respected, so we can safely update the UI to remove mentions of
allowlists, and also update our various allowlist-evaluation mechanisms
in `licensing` and GraphQL resolvers to just provide a wildcard
allowlist instead. It's not strictly required, but will make how the
model allowlists work more clearer/explicit.

Because we have a [planned migration for all this state to Enterprise
Portal](https://linear.app/sourcegraph/project/kr-enterprise-portal-manages-all-enterprise-subscriptions-12f1d5047bd2/overview),
we're not making any database changes.

Part of https://linear.app/sourcegraph/issue/CORE-135

### Context

In https://sourcegraph.slack.com/archives/C05SZB829D0/p1715638980052279
we shared a decision we landed on as part of #62263:

> Ignoring (then removing) per-subscription model allowlists: As part of
the API discussions, we've also surfaced some opportunities for
improvements - to make it easier to roll out new models to Enterprise,
we're not including per-subscription model allowlists in the new API,
and as part of the Cody Gateway migration (by end-of-June), we will
update Cody Gateway to stop enforcing per-subscription model allowlists.
Cody Gateway will still retain a Cody-Gateway-wide model allowlist.
[@chrsmith](https://sourcegraph.slack.com/team/U061QHKUBJ8) is working
on a broader design here and will have more to share on this later.

This means there is one less thing for us to migrate as part of
https://github.com/sourcegraph/sourcegraph/pull/62934, and avoids the
need to add an API field that will be removed shortly post-migration.

As part of this, rolling out new models to Enterprise customers no
longer require additional code/override changes.

## Test plan

Various tests pass.

Visual inspection of `sg start dotcom`:

- **Before:** <img width="947" alt="image"
src="https://github.com/sourcegraph/sourcegraph/assets/23356519/2dc0ab72-c77d-4c0e-a57e-4c336041da4e">
-
**After:**![image](https://github.com/sourcegraph/sourcegraph/assets/23356519/ab2e6d97-38c7-42df-9a5f-2448793db0cc)
This commit is contained in:
Robert Lin 2024-06-05 10:08:58 -07:00 committed by GitHub
parent 27211dea73
commit b574300714
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 22 additions and 253 deletions

View File

@ -1068,7 +1068,6 @@ ts_project(
"src/enterprise/site-admin/dotcom/customers/SiteAdminCustomersPage.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/CodyGatewayRateLimitModal.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/CodyServicesSection.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/ModelBadges.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/SiteAdminCreateProductSubscriptionPage.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/SiteAdminGenerateProductLicenseForSubscriptionForm.tsx",
"src/enterprise/site-admin/dotcom/productSubscriptions/SiteAdminLicenseKeyLookupPage.tsx",

View File

@ -13,7 +13,6 @@ import type {
} from '../../../../graphql-operations'
import { UPDATE_CODY_GATEWAY_CONFIG } from './backend'
import { ModelBadges } from './ModelBadges'
import { numberFormatter, prettyInterval } from './utils'
export interface CodyGatewayRateLimitModalProps {
@ -39,11 +38,6 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
setLimitInterval(parseInt(event.target.value, 10))
}, [])
const [allowedModels, setAllowedModels] = useState<string>(current?.allowedModels?.join(',') ?? '')
const onChangeAllowedModels = useCallback<React.ChangeEventHandler<HTMLInputElement>>(event => {
setAllowedModels(event.target.value)
}, [])
const [updateCodyGatewayConfig, { loading, error }] = useMutation<
UpdateCodyGatewayConfigResult,
UpdateCodyGatewayConfigVariables
@ -62,7 +56,6 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
? {
chatCompletionsRateLimit: String(limit),
chatCompletionsRateLimitIntervalSeconds: limitInterval,
chatCompletionsAllowedModels: splitModels(allowedModels),
}
: {}),
@ -70,7 +63,6 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
? {
codeCompletionsRateLimit: String(limit),
codeCompletionsRateLimitIntervalSeconds: limitInterval,
codeCompletionsAllowedModels: splitModels(allowedModels),
}
: {}),
@ -78,7 +70,6 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
? {
embeddingsRateLimit: String(limit),
embeddingsRateLimitIntervalSeconds: limitInterval,
embeddingsAllowedModels: splitModels(allowedModels),
}
: {}),
},
@ -91,7 +82,7 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
logger.error(error)
}
},
[updateCodyGatewayConfig, productSubscriptionID, limit, limitInterval, afterSave, allowedModels, mode]
[updateCodyGatewayConfig, productSubscriptionID, limit, limitInterval, afterSave, mode]
)
return (
@ -152,28 +143,6 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
}
/>
</div>
<div className="form-group">
<Input
id="allowedModels"
name="allowedModels"
type="text"
autoComplete="off"
spellCheck="false"
required={true}
disabled={loading}
min={1}
label="Allowed models"
description="Comma separated list of the models the subscription can use. This normally doesn't need to be changed."
value={allowedModels}
onChange={onChangeAllowedModels}
message={
<ModelBadges
models={splitModels(allowedModels)}
mode={mode === 'embeddings' ? 'embeddings' : 'completions'}
/>
}
/>
</div>
<div className="d-flex justify-content-end">
<Button disabled={loading} className="mr-2" onClick={onCancel} outline={true} variant="secondary">
Cancel
@ -191,10 +160,3 @@ export const CodyGatewayRateLimitModal: React.FunctionComponent<
</Modal>
)
}
function splitModels(allowedModels: string): string[] {
if (allowedModels === '') {
return []
}
return allowedModels.split(',').map(model => model.trim())
}

View File

@ -4,4 +4,5 @@
padding-right: 0.5rem;
}
margin-bottom: 1rem;
width: 100%;
}

View File

@ -50,7 +50,6 @@ import {
UPDATE_CODY_GATEWAY_CONFIG,
} from './backend'
import { CodyGatewayRateLimitModal } from './CodyGatewayRateLimitModal'
import { ModelBadges } from './ModelBadges'
import { numberFormatter, prettyInterval } from './utils'
import styles from './CodyServicesSection.module.scss'
@ -154,7 +153,6 @@ export const CodyServicesSection: React.FunctionComponent<Props> = ({
<th>Feature</th>
<th>Source</th>
<th>Rate limit</th>
<th>Allowed models</th>
{viewerCanAdminister && <th>Actions</th>}
</tr>
</thead>
@ -189,7 +187,6 @@ export const CodyServicesSection: React.FunctionComponent<Props> = ({
<th>Feature</th>
<th>Source</th>
<th>Rate limit</th>
<th>Allowed models</th>
{viewerCanAdminister && <th>Actions</th>}
</tr>
</thead>
@ -369,12 +366,6 @@ const RateLimitRow: React.FunctionComponent<RateLimitRowProps> = ({
{mode === 'embeddings' ? 'tokens' : 'requests'} /{' '}
{prettyInterval(rateLimit.intervalSeconds)}
</td>
<td>
<ModelBadges
models={rateLimit.allowedModels}
mode={mode === 'embeddings' ? 'embeddings' : 'completions'}
/>
</td>
{viewerCanAdminister && (
<td>
<Button

View File

@ -1,86 +0,0 @@
import type React from 'react'
import { Badge } from '@sourcegraph/wildcard'
export interface ModelBadgesProps {
models: string[]
mode: 'completions' | 'embeddings'
}
export const ModelBadges: React.FunctionComponent<ModelBadgesProps> = ({ models, mode }) => (
<>
{models.map(model => (
<Badge variant={modelBadgeVariant(model, mode)} className="mr-1" key={model}>
{model}
</Badge>
))}
</>
)
function modelBadgeVariant(model: string, mode: 'completions' | 'embeddings'): 'secondary' | 'danger' {
if (mode === 'completions') {
switch (model) {
// See here: https://console.anthropic.com/docs/api/reference
// for currently available Anthropic models. Note that we also need to
// allow list the models on the Cody Gateway side.
case 'anthropic/claude-v1':
case 'anthropic/claude-v1-100k':
case 'anthropic/claude-v1.0':
case 'anthropic/claude-v1.2':
case 'anthropic/claude-v1.3':
case 'anthropic/claude-v1.3-100k':
case 'anthropic/claude-2':
case 'anthropic/claude-2.0':
case 'anthropic/claude-2.1':
case 'anthropic/claude-instant-v1':
case 'anthropic/claude-instant-1':
case 'anthropic/claude-instant-v1-100k':
case 'anthropic/claude-instant-v1.0':
case 'anthropic/claude-instant-v1.1':
case 'anthropic/claude-instant-v1.1-100k':
case 'anthropic/claude-instant-v1.2':
case 'anthropic/claude-instant-1.2':
case 'anthropic/claude-3-sonnet-20240229':
case 'anthropic/claude-3-opus-20240229':
case 'anthropic/claude-3-haiku-20240307':
// See here: https://platform.openai.com/docs/models/model-endpoint-compatibility
// for currently available Anthropic models. Note that we also need to
// allow list the models on the Cody Gateway side.
case 'openai/gpt-4':
case 'openai/gpt-3.5-turbo':
case 'openai/gpt-4o':
case 'openai/gpt-4-turbo':
case 'openai/gpt-4-turbo-preview':
// For currently available Google Gemini models,
// see: https://ai.google.dev/gemini-api/docs/models/gemini
case 'google/gemini-1.5-flash-latest':
case 'google/gemini-1.5-pro-latest':
case 'google/gemini-pro-latest':
// Virtual models that are translated by Cody Gateway and allow access to all StarCoder
// models hosted for us by Fireworks.
case 'fireworks/starcoder':
// Bespoke alternative models hosted for us by Fireworks. These are also allowed on the
// Cody Gateway side
case 'fireworks/accounts/fireworks/models/llama-v2-7b-code':
case 'fireworks/accounts/fireworks/models/llama-v2-13b-code':
case 'fireworks/accounts/fireworks/models/llama-v2-13b-code-instruct':
case 'fireworks/accounts/fireworks/models/llama-v2-34b-code-instruct':
case 'fireworks/accounts/fireworks/models/mistral-7b-instruct-4k':
case 'fireworks/accounts/fireworks/models/mixtral-8x7b-instruct':
case 'fireworks/accounts/fireworks/models/mixtral-8x22b-instruct': {
return 'secondary'
}
default: {
return 'danger'
}
}
}
switch (model) {
case 'openai/text-embedding-ada-002': {
return 'secondary'
}
default: {
return 'danger'
}
}
}

View File

@ -484,6 +484,7 @@ input UpdateCodyGatewayAccessInput {
for this subscription.
"""
chatCompletionsAllowedModels: [String!]
@deprecated(reason: "Enterprise no longer have per-subscription model allowlists.")
"""
Override default requests per time interval.
@ -501,6 +502,7 @@ input UpdateCodyGatewayAccessInput {
for this subscription.
"""
codeCompletionsAllowedModels: [String!]
@deprecated(reason: "Enterprise no longer have per-subscription model allowlists.")
"""
Override default requests per time interval for embeddings generation.
@ -517,6 +519,7 @@ input UpdateCodyGatewayAccessInput {
Override the set of allowed models for embeddings generation for this subscription.
"""
embeddingsAllowedModels: [String!]
@deprecated(reason: "Enterprise no longer have per-subscription model allowlists.")
}
"""

View File

@ -63,10 +63,6 @@ func (r codyGatewayAccessResolver) ChatCompletionsRateLimit(ctx context.Context)
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.IntervalSeconds = *rateLimitOverrides.ChatRateLimit.RateIntervalSeconds
}
if rateLimitOverrides.ChatRateLimit.AllowedModels != nil {
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.AllowedModels = rateLimitOverrides.ChatRateLimit.AllowedModels
}
return &codyGatewayRateLimitResolver{
feature: types.CompletionsFeatureChat,
@ -107,10 +103,6 @@ func (r codyGatewayAccessResolver) CodeCompletionsRateLimit(ctx context.Context)
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.IntervalSeconds = *rateLimitOverrides.CodeRateLimit.RateIntervalSeconds
}
if rateLimitOverrides.CodeRateLimit.AllowedModels != nil {
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.AllowedModels = rateLimitOverrides.CodeRateLimit.AllowedModels
}
return &codyGatewayRateLimitResolver{
feature: types.CompletionsFeatureCode,
@ -151,10 +143,6 @@ func (r codyGatewayAccessResolver) EmbeddingsRateLimit(ctx context.Context) (gra
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.IntervalSeconds = *rateLimitOverrides.EmbeddingsRateLimit.RateIntervalSeconds
}
if rateLimitOverrides.EmbeddingsRateLimit.AllowedModels != nil {
source = graphqlbackend.CodyGatewayRateLimitSourceOverride
rateLimit.AllowedModels = rateLimitOverrides.EmbeddingsRateLimit.AllowedModels
}
return &codyGatewayRateLimitResolver{
actorID: r.sub.UUID(),

View File

@ -1,7 +1,6 @@
package licensing
import (
"slices"
"time"
)
@ -11,6 +10,8 @@ import (
type CodyGatewayRateLimit struct {
// AllowedModels is a list of allowed models for the given feature in the
// format "$PROVIDER/$MODEL_NAME", for example "anthropic/claude-2".
// A single-item slice with value '*' means that all models in the Cody
// Gateway allowlist are allowed.
AllowedModels []string
Limit int64
@ -30,28 +31,7 @@ func NewCodyGatewayChatRateLimit(plan Plan, userCount *int) CodyGatewayRateLimit
if uc < 1 {
uc = 1
}
models := []string{
"anthropic/claude-v1",
"anthropic/claude-2",
"anthropic/claude-2.0",
"anthropic/claude-2.1",
"anthropic/claude-instant-v1",
"anthropic/claude-instant-1",
"anthropic/claude-instant-1.2",
"anthropic/claude-3-sonnet-20240229",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-haiku-20240307",
"openai/gpt-3.5-turbo",
"openai/gpt-4",
"openai/gpt-4o",
"openai/gpt-4-turbo",
"openai/gpt-4-turbo-preview",
"google/gemini-1.5-pro-latest",
"google/gemini-1.5-flash-latest",
"google/gemini-pro-latest",
}
models := []string{"*"} // allow all models that are allowlisted by Cody Gateway
switch plan {
// TODO: This is just an example for now.
case PlanEnterprise1,
@ -81,16 +61,7 @@ func NewCodyGatewayCodeRateLimit(plan Plan, userCount *int, licenseTags []string
if uc < 1 {
uc = 1
}
models := []string{
"anthropic/claude-instant-v1",
"anthropic/claude-instant-1",
"anthropic/claude-instant-1.2",
"fireworks/starcoder",
}
// Switch on GPT models by default if the customer license has the GPT tag.
if slices.Contains(licenseTags, GPTLLMAccessTag) {
models = append(models, "openai/gpt-3.5-turbo")
}
models := []string{"*"} // allow all models allowlisted by Cody Gateway
switch plan {
// TODO: This is just an example for now.
case PlanEnterprise1,
@ -125,7 +96,7 @@ func NewCodyGatewayEmbeddingsRateLimit(plan Plan, userCount *int, licenseTags []
uc = 1
}
models := []string{"openai/text-embedding-ada-002"}
models := []string{"*"} // allow all models allowlisted by Cody Gateway
switch plan {
// TODO: This is just an example for now.
case PlanEnterprise1,

View File

@ -20,26 +20,7 @@ func TestNewCodyGatewayChatRateLimit(t *testing.T) {
plan: PlanEnterprise1,
userCount: pointers.Ptr(50),
want: CodyGatewayRateLimit{
AllowedModels: []string{
"anthropic/claude-v1",
"anthropic/claude-2",
"anthropic/claude-2.0",
"anthropic/claude-2.1",
"anthropic/claude-instant-v1",
"anthropic/claude-instant-1",
"anthropic/claude-instant-1.2",
"anthropic/claude-3-sonnet-20240229",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-haiku-20240307",
"openai/gpt-3.5-turbo",
"openai/gpt-4",
"openai/gpt-4o",
"openai/gpt-4-turbo",
"openai/gpt-4-turbo-preview",
"google/gemini-1.5-pro-latest",
"google/gemini-1.5-flash-latest",
"google/gemini-pro-latest",
},
AllowedModels: []string{"*"},
Limit: 2500,
IntervalSeconds: 60 * 60 * 24,
},
@ -48,26 +29,7 @@ func TestNewCodyGatewayChatRateLimit(t *testing.T) {
name: "Enterprise plan with no user count",
plan: PlanEnterprise1,
want: CodyGatewayRateLimit{
AllowedModels: []string{
"anthropic/claude-v1",
"anthropic/claude-2",
"anthropic/claude-2.0",
"anthropic/claude-2.1",
"anthropic/claude-instant-v1",
"anthropic/claude-instant-1",
"anthropic/claude-instant-1.2",
"anthropic/claude-3-sonnet-20240229",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-haiku-20240307",
"openai/gpt-3.5-turbo",
"openai/gpt-4",
"openai/gpt-4o",
"openai/gpt-4-turbo",
"openai/gpt-4-turbo-preview",
"google/gemini-1.5-pro-latest",
"google/gemini-1.5-flash-latest",
"google/gemini-pro-latest",
},
AllowedModels: []string{"*"},
Limit: 50,
IntervalSeconds: 60 * 60 * 24,
},
@ -76,26 +38,7 @@ func TestNewCodyGatewayChatRateLimit(t *testing.T) {
name: "Non-enterprise plan with no user count",
plan: "unknown",
want: CodyGatewayRateLimit{
AllowedModels: []string{
"anthropic/claude-v1",
"anthropic/claude-2",
"anthropic/claude-2.0",
"anthropic/claude-2.1",
"anthropic/claude-instant-v1",
"anthropic/claude-instant-1",
"anthropic/claude-instant-1.2",
"anthropic/claude-3-sonnet-20240229",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-haiku-20240307",
"openai/gpt-3.5-turbo",
"openai/gpt-4",
"openai/gpt-4o",
"openai/gpt-4-turbo",
"openai/gpt-4-turbo-preview",
"google/gemini-1.5-pro-latest",
"google/gemini-1.5-flash-latest",
"google/gemini-pro-latest",
},
AllowedModels: []string{"*"},
Limit: 10,
IntervalSeconds: 60 * 60 * 24,
},
@ -120,12 +63,12 @@ func TestCodyGatewayCodeRateLimit(t *testing.T) {
want CodyGatewayRateLimit
}{
{
name: "Enterprise plan with GPT tag and user count",
name: "Enterprise plan with legacy GPT tag and user count",
plan: PlanEnterprise1,
userCount: pointers.Ptr(50),
licenseTags: []string{GPTLLMAccessTag},
licenseTags: []string{"gpt"},
want: CodyGatewayRateLimit{
AllowedModels: []string{"anthropic/claude-instant-v1", "anthropic/claude-instant-1", "anthropic/claude-instant-1.2", "fireworks/starcoder", "openai/gpt-3.5-turbo"},
AllowedModels: []string{"*"},
Limit: 50000,
IntervalSeconds: 60 * 60 * 24,
},
@ -135,7 +78,7 @@ func TestCodyGatewayCodeRateLimit(t *testing.T) {
plan: PlanEnterprise1,
userCount: pointers.Ptr(50),
want: CodyGatewayRateLimit{
AllowedModels: []string{"anthropic/claude-instant-v1", "anthropic/claude-instant-1", "anthropic/claude-instant-1.2", "fireworks/starcoder"},
AllowedModels: []string{"*"},
Limit: 50000,
IntervalSeconds: 60 * 60 * 24,
},
@ -144,7 +87,7 @@ func TestCodyGatewayCodeRateLimit(t *testing.T) {
name: "Enterprise plan with no user count",
plan: PlanEnterprise1,
want: CodyGatewayRateLimit{
AllowedModels: []string{"anthropic/claude-instant-v1", "anthropic/claude-instant-1", "anthropic/claude-instant-1.2", "fireworks/starcoder"},
AllowedModels: []string{"*"},
Limit: 1000,
IntervalSeconds: 60 * 60 * 24,
},
@ -153,7 +96,7 @@ func TestCodyGatewayCodeRateLimit(t *testing.T) {
name: "Non-enterprise plan with no GPT tag and no user count",
plan: "unknown",
want: CodyGatewayRateLimit{
AllowedModels: []string{"anthropic/claude-instant-v1", "anthropic/claude-instant-1", "anthropic/claude-instant-1.2", "fireworks/starcoder"},
AllowedModels: []string{"*"},
Limit: 1000,
IntervalSeconds: 60 * 60 * 24,
},
@ -182,7 +125,7 @@ func TestCodyGatewayEmbeddingsRateLimit(t *testing.T) {
plan: PlanEnterprise1,
userCount: pointers.Ptr(50),
want: CodyGatewayRateLimit{
AllowedModels: []string{"openai/text-embedding-ada-002"},
AllowedModels: []string{"*"},
Limit: 20 * 50 * 10_000_000 / 30,
IntervalSeconds: 60 * 60 * 24,
},
@ -191,7 +134,7 @@ func TestCodyGatewayEmbeddingsRateLimit(t *testing.T) {
name: "Enterprise plan with no user count",
plan: PlanEnterprise1,
want: CodyGatewayRateLimit{
AllowedModels: []string{"openai/text-embedding-ada-002"},
AllowedModels: []string{"*"},
Limit: 1 * 20 * 10_000_000 / 30,
IntervalSeconds: 60 * 60 * 24,
},
@ -200,7 +143,7 @@ func TestCodyGatewayEmbeddingsRateLimit(t *testing.T) {
name: "Non-enterprise plan with no user count",
plan: "unknown",
want: CodyGatewayRateLimit{
AllowedModels: []string{"openai/text-embedding-ada-002"},
AllowedModels: []string{"*"},
Limit: 1 * 10 * 10_000_000 / 30,
IntervalSeconds: 60 * 60 * 24,
},

View File

@ -14,9 +14,6 @@ const (
InternalTag = "internal"
// DevTag denotes licenses used in development environments
DevTag = "dev"
// GPTLLMAccessTag is the license tag that indicates that the licensed instance
// should be allowed by default to use GPT models in Cody Gateway.
GPTLLMAccessTag = "gpt"
// TelemetryEventsExportDisabledTag disables telemery events export EXCEPT
// for Cody-related events, which we are always allowed to export as part of