Tutorial · 2026

SAP AI Core + Azure OpenAI via the Model Adapter Path

Wire Azure OpenAI GPT models into BTP apps through the SAP Cloud SDK for AI adapter layer and AI Core foundation-models deployments - a hands-on field guide.

intermediate 11 min read Updated Nov 8, 2026

30-second summary

  • The adapter path means the SAP Cloud SDK for AI (pip package sap-ai-sdk-gen) wrapping the native OpenAI client classes against SAP AI Core foundation-models deployments - your Azure key never enters the picture.
  • We verified the November 2026 repo state: the SDK was rebranded, the old aifoundation module is gone, and the model adapter now lives under the gen_ai_hub.proxy namespace.
  • The adapter resolves model_name (or config_name/deployment_id) against RUNNING deployments in your resource group and quietly drops parameters a chosen model does not support, such as temperature on reasoning models.
  • Switch to the orchestration road the moment you need grounding, masking, or prompt templating - the sibling tutorial covers that route end to end.

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)?$. Your temperature=0 survives transport but not semantics on those models; if the answer looks less steerable than expected, that is why.
  • n and max_completion_tokens are 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

Next Steps