Background
SAP RPT-1.6 is SAP's foundation model for multi-target tabular prediction: it handles classification and regression across multiple columns simultaneously, trained on relational enterprise data patterns. It is available via SAP AI Core and can be called programmatically using the official ai-api-client-sdk Python package.
The SDK abstracts authentication, REST calls, and payload handling, which sounds like it should make things straightforward. In practice, it introduces three silent bugs that make RPT-1.6 calls fail in ways that are hard to diagnose because none of them are documented anywhere publicly. This post covers each bug, its root cause, and the exact fix.
The environment used throughout:
- Python 3.11
ai-core-sdk+ai-api-client-sdk(latest pip release at time of writing)- RPT-1.6 deployed on AI Core (
scenario: foundation-models,executable: aicore-sap)
The prediction scenario used as example:
- Sales order table data.
- Prediction fields: “Sales Group”, “Shipping Point”, “Payment Terms”, “Incoterms”.
- Prediction field placeholder: [PREDICT].
Bug 1: Wrong Payload Schema
Symptom
The API returns a 400 Bad Request or a validation error immediately on the first call, regardless of what data you send.
Root cause
The RPT-1.6 /predict endpoint does not accept a flat body with top-level task_type and data keys, a structure that some older SDK examples and internal docs suggest. The correct schema wraps the target column configuration inside a prediction_config object, and the row data goes under a rows key (not data).
Fix
payload = {
“prediction_config”: {
“target_columns”: [
{
“name”: “Sales Group”,
“task_type”: “classification”,
“prediction_placeholder”: “[PREDICT]”
},
{
“name”: “Shipping Point”,
“task_type”: “classification”,
“prediction_placeholder”: “[PREDICT]”
},
{
“name”: “Payment Terms”,
“task_type”: “classification”,
“prediction_placeholder”: “[PREDICT]”
},
{
“name”: “Incoterms”,
“task_type”: “classification”,
“prediction_placeholder”: “[PREDICT]”
}
]
},
“index_column”: “Order ID”,
“rows”: df.to_dict(orient=“records”) # “rows”, not “data”
}Each entry in target_columns must have name, task_type, and prediction_placeholder. Rows that should receive a prediction carry the placeholder string in those columns; all other rows provide training context.
Bug 2: SDK Silently Camelizes the Request Body
Symptom
The API returns a 400 or 422 even with a correctly structured payload. Logging the raw outbound request body reveals that keys like prediction_config have been transformed to predictionConfig, target_columns to targetColumns, and so on.
Root cause
ai_api_client_sdk/helpers/rest_client.py, lines 86–87, runs humps.camelize() on every outbound payload before sending it. This is on by default and undocumented. RPT-1.6 expects snake_case keys throughout; camelCase keys cause a silent schema mismatch.
# Inside rest_client.py (SDK source): this runs on every POST body:
if convert_body_to_camel_case:
body = humps.camelize(body)Fix
The post() method accepts **kwargs that are passed through. There is an undocumented boolean parameter convert_body_to_camel_case that disables this behavior:
response = ai_core_client.rest_client.post(
path=f”/inference/deployments/{DEPLOYMENT_ID}/predict”,
body=payload,
headers={“AI-Resource-Group”: “default”},
convert_body_to_camel_case=False # disables humps.camelize()
)Without this flag, the payload is corrupted before it ever leaves the client.
Bug 3: SDK Silently Decamelizes the Response Keys
Symptom
Now we have data as the API call succeeds and returns predictions. However, when you try to extract values from the response, such as item.get(“Order ID”) or item.get(“Sales Group”), you get None for every field.
Root cause
rest_client.py, line 117, runs humps.decamelize() on the raw response JSON. This converts the response keys from whatever the API returned to snake_case: Order ID becomes order_id, Sales Group becomes sales_group, salesGroup becomes sales_group. The transformation is lossy and silent; the SDK does not warn you that keys have been renamed.
# Inside rest_client.py (SDK source): this runs on every response:
response_body = humps.decamelize(response_body)Fix
When extracting predictions from the response, map your original column names to their decamelized equivalents before doing the lookup:
def to_resp_key(col: str) -> str:
“””Converts a column name to the key the SDK will have decamelized it to.”””
return col.lower().replace(” “, “_”)
# Example extraction:
predicted_sales_group = item.get(to_resp_key(“Sales Group”)) # → “sales_group”
predicted_order_id = item.get(to_resp_key(“Order ID”)) # → “order_id”
This is a workaround, not a fix to the SDK behavior itself. If a future SDK version changes the decamelize step, this mapping will need revisiting. Ideally, the SDK would expose convert_response_to_snake_case=False as a symmetric flag to the request-side parameter.
Final Working Request Structure
Putting all three fixes together, a minimal working prediction call looks like this:
import pandas as pd
from ai_api_client_sdk.ai_api_v2_client import AIAPIV2Client
# — Config —
AI_API_URL = “<your-api-url/v2>”
AUTH_URL = “<your-auth-url>/oauth/token”
CLIENT_ID = “<your-client-id>”
CLIENT_SECRET = “<your-client-secret>”
RESOURCE_GROUP = “default”
DEPLOYMENT_ID = “<your-deployment-id>”
INDEX_COL = “Order ID”
TARGET_COLS = [“Sales Group”, “Shipping Point”, “Payment Terms”, “Incoterms”]
PLACEHOLDER = “[PREDICT]”
# — Client —
ai_core_client = AIAPIV2Client(
base_url=AI_API_URL,
auth_url=AUTH_URL,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
resource_group=RESOURCE_GROUP,
)
# — Payload (Bug 1 fix: correct schema) —
df = pd.read_csv(“dataset.csv”)
payload = {
“prediction_config”: {
“target_columns”: [
{“name”: col, “task_type”: “classification”, “prediction_placeholder”: PLACEHOLDER}
for col in TARGET_COLS
]
},
“index_column”: INDEX_COL,
“rows”: df.to_dict(orient=“records”),
}
# — Request (Bug 2 fix: disable camelization) —
response = ai_core_client.rest_client.post(
path=f”/inference/deployments/{DEPLOYMENT_ID}/predict”,
body=payload,
headers={“AI-Resource-Group”: RESOURCE_GROUP},
convert_body_to_camel_case=False,
)
# — Response parsing (Bug 3 fix: account for decamelized keys) —
def to_resp_key(col: str) -> str:
return col.lower().replace(” “, “_”)
results = response.data.get(“predictions”, [])
for item in results:
order_id = item.get(to_resp_key(INDEX_COL))
preds = {col: item.get(to_resp_key(col)) for col in TARGET_COLS}
print(order_id, preds)
Summary
# Bug Root cause Fix
| 1 | Wrong payload schema | RPT-1.6 uses prediction_config + rows, not task_type + data | Use the correct nested schema |
| 2 | Request body camelized | rest_client.py runs humps.camelize() by default | Pass convert_body_to_camel_case=False |
| 3 | Response keys decamelized | rest_client.py runs humps.decamelize() on response | Map column names with col.lower().replace(” “, “_”) |
None of these are documented in the official SAP AI Core developer docs or the SDK README at the time of writing. Hopefully this saves a few hours for the next person.
Tested on SAP AI Core (EU Central 1, AWS), RPT-1.6, ai-api-client-sdk latest, Python 3.11.