Two Roads from BTP to GPT, and Why We Took the Shorter One
If you have been following our Azure-on-BTP work, you already have Connect Azure OpenAI to SAP BTP: Generative AI Orchestrator Setup on your reading list. That guide walks the Generative AI Orchestrator road: an orchestration deployment, a JSON pipeline with templating, grounding, masking and filtering modules, and a bring-your-own Azure option. It is the right road when you need those pipeline stages.
This guide is the other road — the one we reach for when we just want a GPT model answer, in process code, with OpenAI-compatible semantics and no orchestration payload to maintain. We call it the model adapter path: the SAP Cloud SDK for AI connects to plain SAP AI Core foundation-models deployments (the azure-openai executable running on SAP-managed hub capacity) and exposes them through adapters that subclass the official OpenAI Python client. Your application authenticates with a BTP service key; no Azure API key is involved at any point.
One honest note before anything else, because we tripped over it ourselves. This SDK has moved around under everyone. The README at github.com/SAP/ai-sdk-python now states plainly: “The SDK formerly known as generative AI Hub SDK was rebranded.” Many of you remember the adapter layer as the aifoundation module shipped as sap-ai-core-aifoundation on PyPI. As of our testing this week, that distribution returns {"message": "Not Found"} from the PyPI JSON API — it is no longer published, and there is no aifoundation directory left anywhere in the repository tree either (we pulled the full recursive tree of main on 2026-11-08 to confirm). The functionality lives in the rebranded monorepo under three packages — base, core, and gen — and the adapter itself is gen_ai_hub.proxy. Older snippets you find in blog posts will not run against today’s SDK without translation; we will keep every claim in this guide tied to what we actually verified in the current tree.
The Stack in One Table
| Layer | What it is on this path | What you touch |
|---|---|---|
| SAP AI Core | The BTP service hosting the foundation-models scenario and the azure-openai executable (SAP-managed capacity) |
Service instance on the extended plan, a service key, a resource group |
| Generative AI hub catalog | The registry of models SAP exposes through AI Core; the same catalog you browse in SAP AI Launchpad | Read: which GPT names are available and at which version |
| SAP Cloud SDK for AI (Python) | gen_ai_hub.proxy — adapter classes that subclass the native openai SDK and route calls to deployment URLs with BTP OAuth |
pip install sap-ai-sdk-gen, then normal OpenAI-style calls |
| Your app runtime | Cloud Foundry or Kyma workload holding the service key | AICORE_SERVICE_KEY or VCAP_SERVICES |
The hub’s OpenAI offering is documented at help.sap.com/docs/sap-ai-core/generative-ai/openai, and the Launchpad-side catalog view (“Models and Scenarios in Generative AI Hub”) is at help.sap.com. Read both before you pick a model name; availability differs by region, and the offering changes over time faster than any blog post can.
Setup: Service Key, Extended Plan, SDK Install
The extended service plan is the gate — the hub scenarios do not exist on Free or Standard, a constraint our sibling tutorial covers in detail too. Then it is ordinary BTP plumbing:
cf create-service ai-core extended ai-core-hub # the hub scenarios live on the extended plan
cf create-service-key ai-core-hub dev-key # one BTP key is the only credential this path needs
cf service-key ai-core-hub dev-key # clientid, clientsecret, url, identityzone, serviceurls.AI_API_URL
export AICORE_SERVICE_KEY=$(pwd)/ai-core-dev-key.json # full-key JSON file on disk
pip install sap-ai-sdk-gen # default: OpenAI-family models only
pip install "sap-ai-sdk-gen[all]" # extras [google, amazon] or [all] for other providers
The AICORE_SERVICE_KEY variable is not decoration. AICoreV2Client.from_env() — which every entry point in the SDK funnels through — resolves credentials in a fixed precedence we read in packages/core/ai_core_sdk/ai_core_v2_client.py: (1) explicit keyword arguments, (2) the AICORE_SERVICE_KEY environment variable, (3) individual environment variables, (4) a profile in the config file ($AICORE_HOME/config.json, selectable via AICORE_PROFILE and AICORE_CONFIG), (5) VCAP_SERVICES, which is why the same code runs unmodified on a Cloud Foundry deploy. The docstring also lists x.509 credentials as accepted, file path or string, for workloads that should not hold client secrets at all.
First Call Through the Adapter
from gen_ai_hub.proxy.native.openai import chat
response = chat.completions.create(
model_name="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful SAP assistant."},
{"role": "user", "content": "Summarize what an SAP sales order header contains."},
],
max_completion_tokens=64,
)
print(response.choices[0].message.content)
That import is the whole trick: gen_ai_hub.proxy.native.openai gives you ChatCompletions, Embeddings, Completions, Responses and OpenAI/AsyncOpenAI classes that subclass the corresponding resources from the official openai package and swap the transport. Under the hood a proxy client registered as gen-ai-hub attaches the BTP OAuth bearer token, the AI-Client-Type: GenAI Hub SDK (Python) telemetry header and your AI-Resource-Group header to every request. The proxy version is selectable via the LLM_PROXY_VERSION environment variable — useful the day your team adds a second proxy target.
Streaming is exactly what you would write against Azure: stream=True, then iterate chunks. Async is AsyncOpenAI from the same module. If your code already uses OpenAI(proxy_client=...) style construction (SAP’s tests do), the same adapter works inside a plain client instance.
Catalog First: Models, Configurations, Deployments
The adapter never creates anything; it discovers. What we verified in gen_ai_hub/proxy/gen_ai_hub_proxy/client.py: the client queries AI Core for RUNNING deployments in the foundation-models scenario, then reads deployment.details["resources"]["backend_details"]["model"] from each one to build its internal catalog (model name, model version, config id, config name, deployment id, URL). When a lookup misses, it refreshes once and retries — so a deployment you started two minutes ago becomes callable without restarting your process.
The low-level catalog walk and the “point me at a specific GPT” flow, using the core package directly:
from ai_core_sdk.ai_core_v2_client import AICoreV2Client
from ai_api_client_sdk.models.parameter_binding import ParameterBinding
client = AICoreV2Client.from_env()
for m in client.model.query().value: # hub catalog: names + versions per provider
print(m.key if hasattr(m, "key") else m.name)
config = client.configuration.create(
name="gpt-5-nano-latest",
scenario_id="foundation-models",
executable_id="azure-openai", # the hub's OpenAI-family executable
parameter_bindings=[
ParameterBinding.from_dict({"key": "modelName", "value": "gpt-5-nano"}),
ParameterBinding.from_dict({"key": "modelVersion", "value": "latest"}),
],
)
deployment = client.deployment.create(configuration_id=config.id) # poll until status RUNNING
This mirrors the config pattern shown in the repo’s own sample-code/sample_code/core.py, which we pulled line by line while writing this. Two operational gotchas surfaced from the source rather than the docs. First, the default scenario filter is config_names="*" — every RUNNING foundation-models configuration in your resource group joins the adapter’s catalog, so if two configurations pin the same model name, selection is ambiguous; the code’s own error text tells you to “use a more rigorous config name filter” or move the deployment to a dedicated scenario. Second, select_deployment() returns the first match and stays quiet unless you pass raise_on_multiple=True. In a shared resource group with colleagues’ old configs lying around, “quietly routed to someone’s pinned 2024 snapshot” is exactly the bug you do not want. We now keep production configurations in a dedicated resource group and match on config_name instead of model_name in application code.
Model Names and the Parameters the Adapter Edits
The lookup keys accepted by chat.completions and embeddings are the tuple we found in the deployment class: model_name, model_version, config_id, config_name, deployment_id — plus the native OpenAI argument model, which the proxy folds into the same identification before matching. Once a deployment is selected, the request is rewritten against it.
The interesting part is what the proxy removes rather than what it adds, all visible in _prepare_chat in packages/gen/gen_ai_hub/proxy/native/openai/clients.py:
- temperature is silently dropped for models that do not support it — the reasoning-model pattern is
^o\d+and the GPT-5 family is matched with^gpt-5(-mini|-nano)?$. Yourtemperature=0survives transport but not semantics on those models; if the answer looks less steerable than expected, that is why. nandmax_completion_tokensare popped when the selected model name matches^cohere--— one adapter across providers, provider quirks patched in the middle layer.- The OpenAI-family proxy pins
DEFAULT_API_VERSION = "2025-03-01-preview", with a source comment pointing to Microsoft’s API version lifecycle page. If you read about a newer preview feature and wonder why it errors, check the SDK version before checking Azure.
Shapes beyond chat, from the current sample code:
from pydantic import BaseModel
from gen_ai_hub.proxy.native.openai import chat, embeddings, responses
class Person(BaseModel):
name: str
age: int
vec = embeddings.create(model_name="text-embedding-3-small",
input="The quick brown fox jumps over the lazy dog.")
parsed = chat.completions.parse(model_name="gpt-5-nano",
messages=[{"role": "user", "content": "Tell me about John Doe, aged 30."}],
response_format=Person)
resp = responses.create(model="gpt-5-nano",
instructions="You are a helpful assistant.",
input="What is the capital of France?") # Responses API; plain model= works here
Structured output via pydantic (chat.completions.parse) and the Responses API (responses.create/responses.parse) both flow through the same adapter. The model names above are exactly what the repository’s integration tests import from its constants file — gpt-4o-mini, o3-mini, o4-mini, gpt-5, gpt-5-mini, gpt-5-nano, gpt-5.3-codex, text-embedding-3-small — which is a reasonable proxy for what is live in the hub right now, though your region may expose a different subset.
When to Use This Path, and When Not To
| Need | Adapter path (this guide) | Orchestrator path (sibling guide) |
|---|---|---|
| Plain chat/embedding calls, OpenAI SDK shape | Yes — that is the whole design | Works, but you maintain a pipeline payload for no gain |
| Grounding on SAP/SharePoint/Drive docs | No first-party module here | Yes — orchestration grounding modules |
| PII masking, prompt templating, input/output filtering | No | Yes |
| Model failover chains across providers | Manual (you own the retry + selection) | Built-in orchestration fallbacks |
| Calling your own Azure subscription (BYO) | Not on this path | Covered in the sibling guide |
If the middle rows are your requirement, stop here and read Connect Azure OpenAI to SAP BTP: Generative AI Orchestrator Setup — it is the complement, not a duplicate: same AI Core substrate, different entry point, and it goes deep on Entra ID and BYO keys, which this route deliberately never touches. If you are building multi-agent flows where model access is only one tool behind an agent, our SAP A2A Protocol and Agent Integration piece shows the protocol layer that sits above either choice.
What This Guide Does Not Cover
Several sharp corners, stated plainly so you are not surprised. We do not cover the orchestration service beyond the pointer above — no templating, grounding, masking, or fallback configuration. We do not cover bring-your-own Azure OpenAI resources or Entra ID token flows; on the adapter path there is no Azure credential to manage. We do not cover the Java or JavaScript flavors of the SDK, only the Python monorepo packages. The SDK also ships batch_service, document_grounding, evaluations and a prompt_registry module — real and interesting, but each deserves its own field report. The Launchpad admin plane (model restriction configurations, usage dashboards, registry governance) is out of scope here too; we touched it in the sibling tutorial. And we do not pretend to know your region’s exact model list — the catalog moves faster than any static post, including this one; check the hub documentation before you commit a name to code.
FAQ
Is the model adapter path the same thing as the Generative AI hub?
No, but they share plumbing. The hub is the SAP-managed catalog and capacity: azure-openai-family deployments hosted by SAP inside the foundation-models scenario of AI Core. The adapter is a client-side pattern: gen_ai_hub.proxy classes that wrap the official OpenAI Python SDK, translate your model_name into one of those hub deployments, and stamp the BTP OAuth headers. You can also call the same deployments with raw REST and never use the SDK; the adapter is convenience plus transport, not a different service tier.
Do I need an Azure subscription and Azure OpenAI access approval for this?
Not for the adapter path itself. GPT capacity is SAP-managed and consumed through your AI Core entitlement, authenticated with a BTP service key — no Azure key, no Entra app, no Azure quota ticket. You need your own Azure subscription only when the requirement is your subscription’s quota, your region, or your data boundary (BYO), which is the sibling tutorial’s territory.
What happened to the old sap-ai-core packages and the aifoundation module?
Rebranded and republished. The PyPI distributions sap-ai-core and sap-ai-core-aifoundation return Not Found as we write this, and the repository README states the generative AI Hub SDK was rebranded to the SAP Cloud SDK for AI. Install sap-ai-sdk-gen for model access (extras [google], [amazon], or [all]), with the underlying clients available as the core and base packages of the same monorepo. Code written against the old sap.aicore.clients.aifoundation import paths must be translated to gen_ai_hub.proxy.native.*.
Why does my prompt behave differently after I switched model_name from gpt-4o-mini to gpt-5?
Two things compose. First, the adapter drops temperature for families matching its reasoning-model and GPT-5 patterns, so a deliberately low setting vanishes. Second, model routing: model_name resolves against every RUNNING foundation-models deployment in your resource group, and multiple matches return the first unless you set raise_on_multiple=True. If colleagues also create configurations, pin the deployment with config_name or deployment_id in application code, or isolate your resource group.
References
- SAP Cloud SDK for AI (Python) repository — README rebrand note, package layout, proxy source, sample code: https://github.com/SAP/ai-sdk-python
- Adapter sample code (chat, streaming, structured, Responses API, embeddings): https://github.com/SAP/ai-sdk-python/blob/main/sample-code/sample_code/openai.py
- Core config/deployment sample code: https://github.com/SAP/ai-sdk-python/blob/main/sample-code/sample_code/core.py
- PyPI: sap-ai-sdk-gen: https://pypi.org/project/sap-ai-sdk-gen/
- Hosted SDK documentation on SAP Help Portal: https://help.sap.com/doc/generative-ai-hub-sdk/CLOUD/en-US/index.html
- OpenAI models in the Generative AI hub: https://help.sap.com/docs/sap-ai-core/generative-ai/openai
- Models and Scenarios in Generative AI Hub (AI Launchpad docs): https://help.sap.com/docs/ai-launchpad/sap-ai-launchpad/models-and-scenarios-in-generative-ai-hub-fef463b24bff4f44a33e98bb1e4f3148?locale=en-US
- About SAP AI Core (BTP documentation): https://help.sap.com/docs/btp/sap-ai-core/about-sap-ai-core
- SAP Learning — Discovering SAP’s Generative AI Hub: https://learning.sap.com/courses/discovering-sap-s-generative-ai-hub
- SAP Learning — Solve your business problems using prompts and LLMs in SAP Generative AI Hub: https://learning.sap.com/courses/solve-your-business-problems-using-prompts-and-llms-in-sap-generative-ai-hub
- SAP Learning — Using Advanced AI Techniques with SAP’s Generative AI Hub: https://learning.sap.com/courses/using-advance-ai-techniques-with-sap-s-generative-ai-hub
- SAP Developers — Consume orchestration V2 (contrast path): https://developers.sap.com/tutorials/ai-core-orchestration-consumption-v2.html
- Microsoft Learn — Azure OpenAI models: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/models
- Microsoft Learn — Azure OpenAI API version lifecycle: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle
- Microsoft Learn — Work with chat completion models: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/chatgpt
Next Steps
- Getting Started with SAP Joule: M365 Copilot and Azure — what SAP’s own copilot layer does on top of these model choices
- Browse all BTP tutorials