Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting default model providers #421

Merged
merged 14 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/source/users/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,28 @@ The `--response-path` option is a [JSONPath](https://goessner.net/articles/JsonP
You can specify an allowlist, to only allow only a certain list of providers, or
a blocklist, to block some providers.

### Configuring default models and API keys

This configuration allows for setting a default language and embedding models, and their corresponding API keys.
These values are offered as a starting point for users, so they don't have to select the models and API keys, however,
the selections they make in the settings panel will take precedence over these values.

Specify default language model
```
jupyter lab --AiExtension.default_language_model=bedrock-chat:anthropic.claude-v2
```

Specify default embedding model
```
jupyter lab --AiExtension.default_embedding_model=bedrock:amazon.titan-embed-text-v1
```

Specify default API keys
```
jupyter lab --AiExtension.default_api_keys={'OPENAI_API_KEY': 'sk-abcd'}
```


### Blocklisting providers

This configuration allows for blocking specific providers in the settings panel.
Expand Down
34 changes: 26 additions & 8 deletions packages/jupyter-ai/jupyter_ai/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(
blocked_providers: Optional[List[str]],
allowed_models: Optional[List[str]],
blocked_models: Optional[List[str]],
defaults: dict,
*args,
**kwargs,
):
Expand All @@ -120,6 +121,8 @@ def __init__(
self._blocked_providers = blocked_providers
self._allowed_models = allowed_models
self._blocked_models = blocked_models
self._defaults = defaults
"""Provider defaults."""
3coins marked this conversation as resolved.
Show resolved Hide resolved

self._last_read: Optional[int] = None
"""When the server last read the config file. If the file was not
Expand All @@ -146,14 +149,20 @@ def _init_validator(self) -> Validator:
self.validator = Validator(schema)

def _init_config(self):
default_config = self._init_defaults()
if os.path.exists(self.config_path):
self._process_existing_config()
self._process_existing_config(default_config)
else:
self._create_default_config()
self._create_default_config(default_config)

def _process_existing_config(self):
def _process_existing_config(self, default_config):
with open(self.config_path, encoding="utf-8") as f:
config = GlobalConfig(**json.loads(f.read()))
existing_config = json.loads(f.read())
merged_config = Merger.merge(
default_config,
{k: v for k, v in existing_config.items() if v is not None},
)
config = GlobalConfig(**merged_config)
validated_config = self._validate_lm_em_id(config)

# re-write to the file to validate the config and apply any
Expand Down Expand Up @@ -192,14 +201,23 @@ def _validate_lm_em_id(self, config):

return config

def _create_default_config(self):
properties = self.validator.schema.get("properties", {})
def _create_default_config(self, default_config):
self._write_config(GlobalConfig(**default_config))

def _init_defaults(self):
field_list = GlobalConfig.__fields__.keys()
properties = self.validator.schema.get("properties", {})
field_dict = {
field: properties.get(field).get("default") for field in field_list
}
default_config = GlobalConfig(**field_dict)
self._write_config(default_config)
if self._defaults is None:
return field_dict

for field in field_list:
default_value = self._defaults.get(field)
if default_value is not None:
field_dict[field] = default_value
return field_dict

def _read_config(self) -> GlobalConfig:
"""Returns the user's current configuration as a GlobalConfig object.
Expand Down
40 changes: 40 additions & 0 deletions packages/jupyter-ai/jupyter_ai/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,38 @@ class AiExtension(ExtensionApp):
config=True,
)

default_language_model = Unicode(
default_value=None,
allow_none=True,
help="""
Default language model to use, as string in the format
<provider-id>:<model-id>, defaults to None.
""",
config=True,
)

default_embeddings_model = Unicode(
default_value=None,
allow_none=True,
help="""
Default embeddings model to use, as string in the format
<provider-id>:<model-id>, defaults to None.
""",
config=True,
)

default_api_keys = Dict(
key_trait=Unicode(),
value_trait=Unicode(),
default_value=None,
allow_none=True,
help="""
Default API keys for model providers, as a dictionary,
in the format `<key-name>:<key-value>`. Defaults to None.
""",
config=True,
)

def initialize_settings(self):
start = time.time()

Expand All @@ -124,6 +156,13 @@ def initialize_settings(self):
self.settings["model_parameters"] = self.model_parameters
self.log.info(f"Configured model parameters: {self.model_parameters}")

defaults = {
"model_provider_id": self.default_language_model,
"embeddings_provider_id": self.default_embeddings_model,
"api_keys": self.default_api_keys,
"fields": self.model_parameters,
}

# Fetch LM & EM providers
self.settings["lm_providers"] = get_lm_providers(
log=self.log, restrictions=restrictions
Expand All @@ -142,6 +181,7 @@ def initialize_settings(self):
blocked_providers=self.blocked_providers,
allowed_models=self.allowed_models,
blocked_models=self.blocked_models,
defaults=defaults,
)

self.log.info("Registered providers.")
Expand Down
80 changes: 80 additions & 0 deletions packages/jupyter-ai/jupyter_ai/tests/test_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ def common_cm_kwargs(config_path, schema_path):
"blocked_providers": None,
"allowed_models": None,
"blocked_models": None,
"restrictions": {"allowed_providers": None, "blocked_providers": None},
"defaults": {
"model_provider_id": None,
"embeddings_provider_id": None,
"api_keys": None,
"fields": None,
},
}


@pytest.fixture
def cm_kargs_with_defaults(config_path, schema_path, common_cm_kwargs):
"""Kwargs that are commonly used when initializing the CM."""
log = logging.getLogger()
lm_providers = get_lm_providers()
em_providers = get_em_providers()
return {
**common_cm_kwargs,
"defaults": {
"model_provider_id": "bedrock-chat:anthropic.claude-v1",
"embeddings_provider_id": "bedrock:amazon.titan-embed-text-v1",
"api_keys": {"OPENAI_API_KEY": "open-ai-key-value"},
"fields": {
"bedrock-chat:anthropic.claude-v1": {
"credentials_profile_name": "default",
"region_name": "us-west-2",
}
},
},
}


Expand Down Expand Up @@ -70,6 +99,12 @@ def cm_with_allowlists(common_cm_kwargs):
return ConfigManager(**kwargs)


@pytest.fixture
def cm_with_defaults(cm_kargs_with_defaults):
"""The default ConfigManager instance, with an empty config and config schema."""
return ConfigManager(**cm_kargs_with_defaults)


@pytest.fixture(autouse=True)
def reset(config_path, schema_path):
"""Fixture that deletes the config and config schema after each test."""
Expand Down Expand Up @@ -184,6 +219,51 @@ def test_init_with_allowlists(cm: ConfigManager, common_cm_kwargs):
assert test_cm.em_gid == None


def test_init_with_default_values(
cm_with_defaults: ConfigManager,
config_path: str,
schema_path: str,
common_cm_kwargs,
):
"""
Test that the ConfigManager initializes with the expected default values.

Args:
cm_with_defaults (ConfigManager): A ConfigManager instance with default values.
config_path (str): The path to the configuration file.
schema_path (str): The path to the schema file.
"""
config_response = cm_with_defaults.get_config()
# assert config response
assert config_response.model_provider_id == "bedrock-chat:anthropic.claude-v1"
assert (
config_response.embeddings_provider_id == "bedrock:amazon.titan-embed-text-v1"
)
assert config_response.api_keys == ["OPENAI_API_KEY"]
assert config_response.fields == {
"bedrock-chat:anthropic.claude-v1": {
"credentials_profile_name": "default",
"region_name": "us-west-2",
}
}

del cm_with_defaults

log = logging.getLogger()
lm_providers = get_lm_providers()
em_providers = get_em_providers()
kwargs = {
**common_cm_kwargs,
"defaults": {"model_provider_id": "bedrock-chat:anthropic.claude-v2"},
}
cm_with_defaults_override = ConfigManager(**kwargs)

assert (
cm_with_defaults_override.get_config().model_provider_id
== "bedrock-chat:anthropic.claude-v1"
)


def test_property_access_on_default_config(cm: ConfigManager):
"""Asserts that the CM behaves well with an empty, default
configuration."""
Expand Down
Loading