From 5f6eaf2f1d6dda04bb7f20bb74705e129a9eb008 Mon Sep 17 00:00:00 2001 From: Yamac Ay Date: Tue, 8 Sep 2026 11:48:59 +0200 Subject: [PATCH 1/4] fix docs --- .../core/ai_core_sdk/ai_core_v2_client.py | 5 +++-- packages/gen/README_sphynx.md | 19 +++++++++++++++++++ packages/gen/docs/gen_ai_hub/README.md | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/core/ai_core_sdk/ai_core_v2_client.py b/packages/core/ai_core_sdk/ai_core_v2_client.py index 4910d9d3..6d2b0b8e 100644 --- a/packages/core/ai_core_sdk/ai_core_v2_client.py +++ b/packages/core/ai_core_sdk/ai_core_v2_client.py @@ -141,8 +141,9 @@ def from_env(profile_name: str = None, The hierarchy of precedence is: 1. keyword argument 2. environment variable - 3. configuration file - 4. value from VCAP_SERVICES environment variable, if exists + 3. value from AICORE_SERVICE_KEY environment variable, if exists + 4. configuration file + 5. value from VCAP_SERVICES environment variable, if exists :param profile_name: name of the profile to use, defaults to None. If None is passed, the profile is read from the environment variable AICORE_PROFILE. If this is not set, the default profile is used. diff --git a/packages/gen/README_sphynx.md b/packages/gen/README_sphynx.md index 5bfec28e..1cd6d9f6 100644 --- a/packages/gen/README_sphynx.md +++ b/packages/gen/README_sphynx.md @@ -45,6 +45,7 @@ In the table below, you can see which models and vendor specific langchain packa There are different ways to configure the SAP AI Core access (listed in order of precedence): - environment variables +- from AICORE_SERVICE_KEY environment variable, if it exists - (profile) configuration file - from VCAP_SERVICES environment variable, if it exists @@ -72,6 +73,15 @@ as an alternative to client secret. - `AICORE_CERT_STR`: This is the content of the X.509 certificate as a string - `AICORE_KEY_STR`: This is the content of the X.509 key as a string +### AICORE_SERVICE_KEY environment variable + +If you have an SAP AI Core service key (downloaded from BTP), you can pass it as a single environment variable instead of setting each credential separately. The SDK extracts `clientid`, `clientsecret`, `url`, and `AI_API_URL` from it automatically. You still need to set `AICORE_RESOURCE_GROUP` separately, as the resource group is not part of the service key. + +```bash +export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"https://***.authentication.sap.hana.ondemand.com","serviceurls":{"AI_API_URL":"https://api.ai.***.cfapps.sap.hana.ondemand.com"}}' +export AICORE_RESOURCE_GROUP="default" +``` + ### Configuration files By default, the configuration file is located at `~/.aicore/config.json`. You can change the directory where the config file is located by setting the `AICORE_HOME` environment variable. @@ -104,6 +114,15 @@ The configuration file should be: or +```json +{ + "AICORE_SERVICE_KEY": "{\"serviceurls\": {\"AI_API_URL\": \"https://api.ai.* * *.cfapps.sap.hana.ondemand.com\"},\"clientid\": \"* * * \",\"clientsecret\": \"* * * \",\"url\": \"https://* * * .authentication.sap.hana.ondemand.com\"}", + "AICORE_RESOURCE_GROUP": "* * * " +} +``` + +or + ```json { "AICORE_AUTH_URL": "https://* * * .authentication.cert.sap.hana.ondemand.com", diff --git a/packages/gen/docs/gen_ai_hub/README.md b/packages/gen/docs/gen_ai_hub/README.md index 65727d54..e5aecb9e 100644 --- a/packages/gen/docs/gen_ai_hub/README.md +++ b/packages/gen/docs/gen_ai_hub/README.md @@ -23,6 +23,15 @@ The values can be set as environment variables are through config files. For mos The config files should be placed in AI Core home folder. Which can be set using the env var `AICORE_HOME`, it is set to `~/.aicore`, by default. +### AICORE_SERVICE_KEY environment variable + +If you have an SAP AI Core service key (downloaded from BTP), you can pass it as a single environment variable instead of setting each credential separately. The SDK extracts `clientid`, `clientsecret`, `url`, and `AI_API_URL` from it automatically. You still need to set `AICORE_RESOURCE_GROUP` separately, as the resource group is not part of the service key. + +```bash +export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"https://***.authentication.sap.hana.ondemand.com","serviceurls":{"AI_API_URL":"https://api.ai.***.cfapps.sap.hana.ondemand.com"}}' +export AICORE_RESOURCE_GROUP="default" +``` + To fetch the values from config file instead of setting environment variables, create a config under path `/config.json` ```json { @@ -36,6 +45,15 @@ To fetch the values from config file instead of setting environment variables, c or +```json +{ + "AICORE_SERVICE_KEY": "{\"serviceurls\": {\"AI_API_URL\": \"https://api.ai.* * *.cfapps.sap.hana.ondemand.com\"},\"clientid\": \"* * * \",\"clientsecret\": \"* * * \",\"url\": \"https://* * * .authentication.sap.hana.ondemand.com\"}", + "AICORE_RESOURCE_GROUP": "* * * " +} +``` + +or + ```json { "AICORE_AUTH_URL": "https://* * * .authentication.cert.sap.hana.ondemand.com", From d149bb03cedb66b2b2dda2c1e21a900170b733ad Mon Sep 17 00:00:00 2001 From: Yamac Ay Date: Wed, 9 Sep 2026 10:52:37 +0200 Subject: [PATCH 2/4] order of precedence updated --- packages/core/PYPIDESCRIPTION.md | 1 + packages/core/ai_core_sdk/ai_core_v2_client.py | 4 ++-- packages/core/ai_core_sdk/credentials.py | 4 ++-- packages/gen/README_sphynx.md | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/PYPIDESCRIPTION.md b/packages/core/PYPIDESCRIPTION.md index d337b1a9..10086fc7 100644 --- a/packages/core/PYPIDESCRIPTION.md +++ b/packages/core/PYPIDESCRIPTION.md @@ -85,6 +85,7 @@ for scenario in scenarios.resources: There are different options to persist the client credentials (in this order of precedence): - in code via keyword arguments (see above), + - from AICORE_SERVICE_KEY environment variable, if exists - environment variables, - profile configuration file. - from VCAP_SERVICES environment variable, if exists diff --git a/packages/core/ai_core_sdk/ai_core_v2_client.py b/packages/core/ai_core_sdk/ai_core_v2_client.py index 6d2b0b8e..1ae18670 100644 --- a/packages/core/ai_core_sdk/ai_core_v2_client.py +++ b/packages/core/ai_core_sdk/ai_core_v2_client.py @@ -140,8 +140,8 @@ def from_env(profile_name: str = None, A specific path to a config, that should be used, can be set via the environment variable AICORE_CONFIG. The hierarchy of precedence is: 1. keyword argument - 2. environment variable - 3. value from AICORE_SERVICE_KEY environment variable, if exists + 2. value from AICORE_SERVICE_KEY environment variable, if exists + 3. environment variable 4. configuration file 5. value from VCAP_SERVICES environment variable, if exists diff --git a/packages/core/ai_core_sdk/credentials.py b/packages/core/ai_core_sdk/credentials.py index b7363a91..83961455 100644 --- a/packages/core/ai_core_sdk/credentials.py +++ b/packages/core/ai_core_sdk/credentials.py @@ -298,11 +298,11 @@ def fetch_credentials(profile: str = None, credential_values: List[CredentialsVa sources = [ Source("kwargs", lambda cv: _str_or_none(kwargs.get(cv.name))), - Source("environment variables", - lambda cv: _str_or_none(os.environ.get(f'{AI_CORE_PREFIX}_{cv.name.upper()}'))), # A service key is already the inner credentials object, so the leading `credentials` segment is stripped. Source("service key", lambda cv, service_key = _load_service_key(): _str_or_none(_get_nested_value_safe(service_key, cv.vcap_key[1:])) if cv.vcap_key else None), + Source("environment variables", + lambda cv: _str_or_none(os.environ.get(f'{AI_CORE_PREFIX}_{cv.name.upper()}'))), Source("config file", lambda cv: _str_or_none(config.get(f'{AI_CORE_PREFIX}_{cv.name.upper()}'))), Source("VCAP service", diff --git a/packages/gen/README_sphynx.md b/packages/gen/README_sphynx.md index 1cd6d9f6..0f43b961 100644 --- a/packages/gen/README_sphynx.md +++ b/packages/gen/README_sphynx.md @@ -44,8 +44,8 @@ In the table below, you can see which models and vendor specific langchain packa There are different ways to configure the SAP AI Core access (listed in order of precedence): -- environment variables - from AICORE_SERVICE_KEY environment variable, if it exists +- environment variables - (profile) configuration file - from VCAP_SERVICES environment variable, if it exists From 77aa380fda40f570ea933ed01aeb27b23b8793d4 Mon Sep 17 00:00:00 2001 From: Yamac Ay Date: Wed, 9 Sep 2026 11:26:07 +0200 Subject: [PATCH 3/4] fix tests --- packages/core/ai_core_sdk/credentials.py | 2 +- .../core/tests/ai_core_client/test_credentials.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/ai_core_sdk/credentials.py b/packages/core/ai_core_sdk/credentials.py index 83961455..9968a965 100644 --- a/packages/core/ai_core_sdk/credentials.py +++ b/packages/core/ai_core_sdk/credentials.py @@ -283,7 +283,7 @@ def fetch_credentials(profile: str = None, credential_values: List[CredentialsVa """ Fetch credentials from a single source based on precedence. - Precedence order: kwargs > separate environment variables > service key > config file > VCAP service + Precedence order: kwargs > service key > separate environment variables > config file > VCAP service Once a source is selected (first one with any credential), all credentials come from that source only. Resource group is an exception and follows diff --git a/packages/core/tests/ai_core_client/test_credentials.py b/packages/core/tests/ai_core_client/test_credentials.py index 58e0f261..e0a8375e 100644 --- a/packages/core/tests/ai_core_client/test_credentials.py +++ b/packages/core/tests/ai_core_client/test_credentials.py @@ -331,7 +331,7 @@ def test_fetch_credentials_from_service_key_x509(self, mock_logger): mock_logger.debug.assert_any_call("Using credentials from: service key") @patch('ai_core_sdk.credentials.logger') - def test_service_key_lower_precedence_than_env_vars(self, mock_logger): + def test_service_key_higher_precedence_than_env_vars(self, mock_logger): mock_logger.debug = MagicMock() service_key = { @@ -349,12 +349,12 @@ def test_service_key_lower_precedence_than_env_vars(self, mock_logger): }): credentials = fetch_credentials() - # env vars win - self.assertEqual(credentials['client_id'], 'env-client-id') - self.assertEqual(credentials['client_secret'], 'env-client-secret') - self.assertEqual(credentials['auth_url'], 'https://env-auth-url/oauth/token') - self.assertEqual(credentials['base_url'], 'https://env-base-url/v2') - mock_logger.debug.assert_any_call("Using credentials from: environment variables") + # service key wins over individual env vars + self.assertEqual(credentials['client_id'], 'sk-client-id') + self.assertEqual(credentials['client_secret'], 'sk-client-secret') + self.assertEqual(credentials['auth_url'], 'https://sk-auth-url/oauth/token') + self.assertEqual(credentials['base_url'], 'https://sk-api-url/v2') + mock_logger.debug.assert_any_call("Using credentials from: service key") def test_service_key_invalid_json_raises(self): with patch.dict(os.environ, {ENV_VAR_AICORE_SERVICE_KEY: 'not-valid-json'}): From 69f99270b6577d54046c012ed7773f94e2eb52b0 Mon Sep 17 00:00:00 2001 From: Yamac Ay Date: Wed, 9 Sep 2026 13:49:21 +0200 Subject: [PATCH 4/4] make multiline key strings --- packages/gen/README_sphynx.md | 13 ++++++++++--- packages/gen/docs/gen_ai_hub/README.md | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/gen/README_sphynx.md b/packages/gen/README_sphynx.md index 0f43b961..9719ebc6 100644 --- a/packages/gen/README_sphynx.md +++ b/packages/gen/README_sphynx.md @@ -78,7 +78,14 @@ as an alternative to client secret. If you have an SAP AI Core service key (downloaded from BTP), you can pass it as a single environment variable instead of setting each credential separately. The SDK extracts `clientid`, `clientsecret`, `url`, and `AI_API_URL` from it automatically. You still need to set `AICORE_RESOURCE_GROUP` separately, as the resource group is not part of the service key. ```bash -export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"https://***.authentication.sap.hana.ondemand.com","serviceurls":{"AI_API_URL":"https://api.ai.***.cfapps.sap.hana.ondemand.com"}}' +export AICORE_SERVICE_KEY='{ + "serviceurls": { + "AI_API_URL": "https://api.ai.* * *.cfapps.sap.hana.ondemand.com" + }, + "clientid": "* * * ", + "clientsecret": "* * * ", + "url": "https://* * * .authentication.sap.hana.ondemand.com" +}' export AICORE_RESOURCE_GROUP="default" ``` @@ -116,8 +123,8 @@ or ```json { - "AICORE_SERVICE_KEY": "{\"serviceurls\": {\"AI_API_URL\": \"https://api.ai.* * *.cfapps.sap.hana.ondemand.com\"},\"clientid\": \"* * * \",\"clientsecret\": \"* * * \",\"url\": \"https://* * * .authentication.sap.hana.ondemand.com\"}", - "AICORE_RESOURCE_GROUP": "* * * " + "AICORE_SERVICE_KEY": "{\"serviceurls\":{\"AI_API_URL\":\"https://api.ai.***.cfapps.sap.hana.ondemand.com\"},\"clientid\":\"***\",\"clientsecret\":\"***\",\"url\":\"https://***.authentication.sap.hana.ondemand.com\"}", + "AICORE_RESOURCE_GROUP": "***" } ``` diff --git a/packages/gen/docs/gen_ai_hub/README.md b/packages/gen/docs/gen_ai_hub/README.md index e5aecb9e..6afeaec7 100644 --- a/packages/gen/docs/gen_ai_hub/README.md +++ b/packages/gen/docs/gen_ai_hub/README.md @@ -28,7 +28,14 @@ The config files should be placed in AI Core home folder. Which can be set using If you have an SAP AI Core service key (downloaded from BTP), you can pass it as a single environment variable instead of setting each credential separately. The SDK extracts `clientid`, `clientsecret`, `url`, and `AI_API_URL` from it automatically. You still need to set `AICORE_RESOURCE_GROUP` separately, as the resource group is not part of the service key. ```bash -export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"https://***.authentication.sap.hana.ondemand.com","serviceurls":{"AI_API_URL":"https://api.ai.***.cfapps.sap.hana.ondemand.com"}}' +export AICORE_SERVICE_KEY='{ + "serviceurls": { + "AI_API_URL": "https://api.ai.* * *.cfapps.sap.hana.ondemand.com" + }, + "clientid": "* * * ", + "clientsecret": "* * * ", + "url": "https://* * * .authentication.sap.hana.ondemand.com" +}', export AICORE_RESOURCE_GROUP="default" ``` @@ -47,8 +54,8 @@ or ```json { - "AICORE_SERVICE_KEY": "{\"serviceurls\": {\"AI_API_URL\": \"https://api.ai.* * *.cfapps.sap.hana.ondemand.com\"},\"clientid\": \"* * * \",\"clientsecret\": \"* * * \",\"url\": \"https://* * * .authentication.sap.hana.ondemand.com\"}", - "AICORE_RESOURCE_GROUP": "* * * " + "AICORE_SERVICE_KEY": "{\"serviceurls\":{\"AI_API_URL\":\"https://api.ai.***.cfapps.sap.hana.ondemand.com\"},\"clientid\":\"***\",\"clientsecret\":\"***\",\"url\":\"https://***.authentication.sap.hana.ondemand.com\"}", + "AICORE_RESOURCE_GROUP": "***" } ```