https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/Python-blog-posts.xml SAP Community - Python 2026-02-07T00:11:33.406251+00:00 python-feedgen Python blog posts in SAP Community https://community.sap.com/t5/technology-blog-posts-by-sap/building-agents-for-a-simple-microservice-architecture-with-fastapi-part-2/ba-p/14176702 🚀Building Agents for a Simple Microservice Architecture with FastAPI (Part 2) 2025-08-10T09:01:21.368000+02:00 Yogananda https://community.sap.com/t5/user/viewprofilepage/user-id/75 <P>&nbsp;</P><P><STRONG>Previous Blog :&nbsp;&nbsp;</STRONG><SPAN class=""><A class="" href="https://community.sap.com/t5/technology-blog-posts-by-sap/building-collaborative-microservices-in-python-with-fastapi-echo-amp/ba-p/14170025" target="_blank">Building Collaborative Microservices in Python with FastAPI: Echo &amp; Reverse Agents (Beginner -Part1)</A></SPAN></P><P><EM>Microservices are a powerful way to design scalable and maintainable applications. </EM></P><P><EM>In this blog, we will explore a minimal yet effective microservice setup using&nbsp;<STRONG>FastAPI</STRONG>, perfect for learning and experimentation. This will help to you build better Microservices and deploy in SAP BTP - Kyma</EM></P><H3 id="toc-hId-1866088139">Sample Use Case</H3><P>A client sends a city name to the Weather Agent. The agent fetches enrichment data from the Data Enricher, generates fake weather data, and returns a combined report. This mimics real-world API composition and data aggregation.</P><H3 id="toc-hId-1669574634">Overview</H3><P>It consists of two core services:</P><UL><LI>Fake Weather Agent&nbsp;(<FONT color="#FF6600">weather_agent.py</FONT>)</LI><LI>Data Enricher&nbsp;(<FONT color="#FF6600">data_enricher.py</FONT>)</LI></UL><P>A shell script (<FONT color="#FF6600">run.sh</FONT>) is included to launch both services on separate ports, simulating a real-world microservice environment.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_0-1754809227004.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/298973i1E5B429726C6F429/image-size/large?v=v2&amp;px=999" role="button" title="Yogananda_0-1754809227004.png" alt="Yogananda_0-1754809227004.png" /></span></P><H3 id="toc-hId-1473061129"><span class="lia-unicode-emoji" title=":sun_behind_rain_cloud:">🌦</span>️ 1. Fake Weather Agent (<FONT color="#FF6600">weather_agent.py</FONT>)</H3><P><FONT color="#3366FF">Purpose</FONT>:&nbsp; &nbsp;Generates a fake weather report for a given city.</P><P><FONT color="#3366FF">API Endpoint:&nbsp;&nbsp;</FONT><FONT color="#FF6600">POST /weather</FONT>&nbsp;— Accepts a JSON payload with a city name.</P><P><FONT color="#3366FF">How It Works:</FONT></P><OL><LI>Receives a city name from the client.</LI><LI>Optionally calls the&nbsp;Data Enricher&nbsp;service to fetch additional info (e.g., population, country).</LI><LI>Generates random weather data:<UL><LI>Temperature</LI><LI>Condition (e.g., sunny, rainy)</LI><LI>Humidity</LI><LI>Wind speed</LI></UL></LI><LI>Returns a combined weather report, enriched with city metadata if available.</LI></OL><P><FONT color="#3366FF">Tech Stack:</FONT></P><UL><LI>FastAPI for API development</LI><LI>Pydantic for data validation</LI><LI>httpx for asynchronous HTTP calls</LI></UL><pre class="lia-code-sample language-python"><code>from fastapi import FastAPI, HTTPException from pydantic import BaseModel import httpx import os import random PORT = int(os.getenv("PORT", 8002)) TARGET = os.getenv("TARGET_URL", "http://localhost:8003") # downstream agent app = FastAPI(title="Fake-Weather-Agent") class Location(BaseModel): city: str class WeatherReport(BaseModel): source: str city: str temperature: float # °C condition: str humidity: int # % wind_kmh: float CONDITIONS = ["Sunny", "Cloudy", "Rain", "Snow", "Thunderstorm"] @app.post("/weather", response_model=WeatherReport) async def get_weather(loc: Location): """Generate a fake weather report for the given city.""" # Optionally call another agent (e.g. a “data-enrichment” service) async with httpx.AsyncClient() as client: try: r = await client.post( f"{TARGET}/enrich", json={"city": loc.city} ) r.raise_for_status() extra = r.json() except Exception: extra = {} return WeatherReport( source="Fake-Weather-Agent", city=loc.city, temperature=round(random.uniform(-10, 40), 1), condition=random.choice(CONDITIONS), humidity=random.randint(20, 95), wind_kmh=round(random.uniform(0, 40), 1), **extra )</code></pre><H3 id="toc-hId-1276547624"><span class="lia-unicode-emoji" title=":cityscape:">🏙</span>️ 2. Data Enricher (<FONT color="#FF6600">data_enricher.py</FONT>)</H3><P><FONT color="#3366FF">Purpose</FONT>:&nbsp;Provides additional metadata about a city.</P><P><STRONG>API Endpoint:&nbsp;</STRONG><FONT color="#FF6600">POST /enrich</FONT>&nbsp;— Accepts a JSON payload with a city name.</P><P><FONT color="#3366FF">How It Works:</FONT></P><OL><LI>Looks up the city in a fake in-memory database.</LI><LI>Returns population and country if found.</LI><LI>If not found, returns default placeholder values.</LI></OL><P><FONT color="#3366FF">Tech Stack:</FONT></P><UL><LI>FastAPI</LI><LI>Pydantic</LI></UL><pre class="lia-code-sample language-python"><code>from fastapi import FastAPI from pydantic import BaseModel app = FastAPI(title="Data-Enricher") class EnrichRequest(BaseModel): city: str class EnrichResponse(BaseModel): population: int country: str FAKE_DB = { "london": {"population": 9_000_000, "country": "UK"}, "paris": {"population": 2_100_000, "country": "France"}, "tokyo": {"population": 14_000_000, "country": "Japan"}, } @app.post("/enrich", response_model=EnrichResponse) def enrich(req: EnrichRequest): city = req.city.lower() if city not in FAKE_DB: return EnrichResponse(population=0, country="Unknown") return FAKE_DB[city]</code></pre><H3 id="toc-hId-1080034119"><span class="lia-unicode-emoji" title=":desktop_computer:">🖥</span>️ 3. Running the Services (<FONT color="#FF6600">run.sh</FONT>)</H3><P><FONT color="#3366FF">Purpose:&nbsp;</FONT>Starts both services using&nbsp;uvicorn, FastAPI’s ASGI server.<BR />A shell script (<A title="" href="vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html" target="_blank" rel="noopener nofollow noreferrer">run.sh</A>) is provided to run both services on different ports.</P><P><FONT color="#3366FF">How It Works</FONT>:</P><UL><LI>Launches&nbsp;Fake Weather Agent&nbsp;on port&nbsp;<FONT color="#FF6600">8002</FONT></LI><LI>Launches&nbsp;Data Enricher&nbsp;on port&nbsp;<FONT color="#FF6600">8003</FONT></LI><LI>Each service runs in its own terminal window</LI></UL><pre class="lia-code-sample language-python"><code># Terminal 1 uvicorn fake_weather:app --port 8002 --reload # Terminal 2 uvicorn data_enricher:app --port 8003 --reload</code></pre><H2 id="toc-hId-754437895">Key Points :&nbsp;</H2><UL><LI>Microservice Communication:<BR />The Weather Agent calls the Data Enricher via HTTP to demonstrate service-to-service communication.</LI><LI>Extensibility:<BR />Easy to add more enrichment services or expand the fake database.</LI><LI>FastAPI Features:<BR />Shows how to use Pydantic models, async endpoints, and response models.</LI><LI>Local Development:&nbsp;&nbsp;<BR />Simple to run both services locally for testing and learning.</LI></UL> 2025-08-10T09:01:21.368000+02:00 https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-members/easy-way-to-move-zeroes-in-sap-btp-abap-steampunk-js-amp-python/ba-p/14176847 Easy way to move zeroes in SAP BTP ABAP(Steampunk), JS & Python 2025-08-10T15:27:33.552000+02:00 kallolathome https://community.sap.com/t5/user/viewprofilepage/user-id/14879 <H2 id="toc-hId-962976781" id="toc-hId-1737006510">Introduction</H2><P><SPAN>This is part of the&nbsp;</SPAN><A href="https://blogs.sap.com/2022/12/20/easy-way-to-write-algorithms-in-abap-series-01/" target="_blank" rel="noopener noreferrer"><STRONG>Easy way to write algorithms in ABAP: Series 01</STRONG></A><SPAN>. For more algorithms, please check the main blog-post.</SPAN></P><H2 id="toc-hId-766463276" id="toc-hId-1540493005">Problem</H2><P>Given an integer array<SPAN>&nbsp;</SPAN>nums, move all<SPAN>&nbsp;</SPAN>0's to the end of it while maintaining the relative order of the non-zero elements.</P><P><STRONG>Note</STRONG><SPAN>&nbsp;</SPAN>that you must do this in-place without making a copy of the array.</P><P><STRONG>Example 1:</STRONG></P><PRE><STRONG>Input:</STRONG> nums = [0,1,0,3,12] <STRONG>Output:</STRONG> [1,3,12,0,0]</PRE><P><STRONG>Example 2:</STRONG></P><PRE><STRONG>Input:</STRONG> nums = [0] <STRONG>Output:</STRONG> [0]</PRE><P><STRONG>Constraints:</STRONG></P><UL><LI>1 &lt;= nums.length &lt;= 104</LI><LI>-231 &lt;= nums[i] &lt;= 231 - 1</LI></UL><P><STRONG>Follow up:</STRONG><SPAN>&nbsp;Could you minimize the total number of operations done?</SPAN></P><H2 id="toc-hId-569949771" id="toc-hId-1343979500">Solution</H2><P><SPAN>Time Complexity: <STRONG>O(n)</STRONG><BR />Space Complexity: <STRONG>O(1)</STRONG></SPAN></P><H3 id="toc-hId-502518985" id="toc-hId-1276548714">ABAP</H3><pre class="lia-code-sample language-abap"><code>CLASS zmove_zeroes DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. " Define a table type for integers TYPES ty_nums TYPE STANDARD TABLE OF i WITH EMPTY KEY. " Method to move zeroes in-place METHODS moveZeroes CHANGING lt_nums TYPE ty_nums. ENDCLASS. CLASS zmove_zeroes IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Initialize the number array with some zeroes and non-zeroes DATA(lt_nums) = VALUE ty_nums( ( 0 ) ( 1 ) ( 0 ) ( 3 ) ( 12 ) ). " Output the array before moving zeroes out-&gt;write( |Array before moving zeroes: | ). LOOP AT lt_nums INTO DATA(lv_num). out-&gt;write( lv_num ). ENDLOOP. " Call the method to move zeroes to the end moveZeroes( CHANGING lt_nums = lt_nums ). " Output the array after moving zeroes out-&gt;write( |Array after moving zeroes: | ). LOOP AT lt_nums INTO lv_num. out-&gt;write( lv_num ). ENDLOOP. ENDMETHOD. METHOD moveZeroes. DATA(lv_count) = 0. " Counter for non-zero elements " First pass: Move all non-zero elements to the front LOOP AT lt_nums ASSIGNING FIELD-SYMBOL(&lt;lf_num&gt;). IF &lt;lf_num&gt; &lt;&gt; 0. " Place the non-zero element at the next available position lt_nums[ lv_count + 1 ] = &lt;lf_num&gt;. lv_count += 1. ENDIF. ENDLOOP. " Second pass: Fill the rest of the array with zeroes WHILE lv_count &lt; lines( lt_nums ). lt_nums[ lv_count + 1 ] = 0. lv_count += 1. ENDWHILE. ENDMETHOD. ENDCLASS.</code></pre><H3 id="toc-hId-306005480" id="toc-hId-1080035209">JavaScript</H3><pre class="lia-code-sample language-javascript"><code>function moveZeroes(nums) { let left = 0; for (let right = 0; right &lt; nums.length; right++) { if (nums[right] !== 0) { // Swap nums[left] and nums[right] let temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; left++; } } return nums; }</code></pre><H3 id="toc-hId-883521704">&nbsp;</H3><H3 id="toc-hId-502518985" id="toc-hId-687008199">Python</H3><pre class="lia-code-sample language-python"><code>def moveZeroes(nums): left = 0 for right in range(len(nums)): if nums[right] != 0: nums[left], nums[right] = nums[right], nums[left] left += 1 return nums</code></pre><P>&nbsp;</P><P><SPAN>N.B: For ABAP, I am using SAP BTP ABAP Environment 2309 Release.</SPAN><BR /><BR /><SPAN>Happy Coding!&nbsp;</SPAN><SPAN class="lia-unicode-emoji"><span class="lia-unicode-emoji" title=":slightly_smiling_face:">🙂</span></SPAN></P> 2025-08-10T15:27:33.552000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/sap-datasphere-automation-creating-database-user-in-sap-datasphere-using/ba-p/14176606 SAP Datasphere Automation : Creating Database user in SAP Datasphere using Datasphere CLI & Python 2025-08-12T08:06:51.294000+02:00 shubham521 https://community.sap.com/t5/user/viewprofilepage/user-id/845865 <H3 id="toc-hId-1866087182">Introduction</H3><P>One way to access datasphere hana database is via database users. Each database user is linked to one space (except database analysis user). We can create database user vai GUI however, its a long process and prone to errors. In this blog, i will share my work on how i automated the process using SAP Datasphere CLI and Python without the need to login into datasphere GUI.</P><H3 id="toc-hId-1669573677">Old Process:</H3><P>If you want to create a database user via GUI, you need to perform the below steps.</P><OL><LI>Login into the datasphere tenant. Make sure you have the required application roles to perform the task.</LI><LI>Go to Space management and select the space. Scroll down and click create in database user.&nbsp;</LI><LI>Provide the username and deploy the space. Save the password and share it with the user.&nbsp;</LI></OL><H3 id="toc-hId-1473060172">Automated Process</H3><P>To overcome the long process, i have divided the python script to perform all these task in sequential manner.&nbsp;</P><OL><LI><STRONG>Datasphere CLI Setup</STRONG> : Set the CLI and login into datasphere</LI><LI><STRONG>Reading the metadata</STRONG>: Read the space metadata using SAP Datasphere CLI <STRONG>space read</STRONG> command</LI><LI><STRONG>Modifying JSON</STRONG> : Enhance the output JSON with the new user details and deploy it using the <STRONG>space create</STRONG> command</LI><LI><STRONG>Password Reset</STRONG> : Reset the database user password and save it in a file for sharing.</LI></OL><H3 id="toc-hId-1276546667">Set up SAP Datasphere CLI (First time only)</H3><P>To start working with Datasphere CLI, we need to perform few one time configuration like setting host, login in and setting cache. If you want to login via a different tenant, you need to change the CLIENT_ID and CLIENT_SECRETS as per the tenant.</P><pre class="lia-code-sample language-python"><code>datasphere config set host {"host_url"} datasphere login --client-id {"client_id"} --client-secret {"client_secret"} datasphere config set cache --client-id {"client_id"} --client-secret {"client_secret"}</code></pre><P>&nbsp;Once you are done with the initial setup of CLI, we can start working on the main code which will create database users.</P><H3 id="toc-hId-1080033162">Reading space metadata</H3><P>Since we do not have a direct command to create DB user, we extract the space metadata JSON using CLI. This command output the space metadata JSON into console. I have stored this into a variable that i will use later in modifying the JSON</P><pre class="lia-code-sample language-python"><code>space_Json = datasphere space read -space "{space"} --definations</code></pre><H3 id="toc-hId-883519657">Modifying the JSON and deploying it in datasphere&nbsp;</H3><P>To create a new databased user in space, we need to add the new user details in the dbuser object of the JSON. We can store the space metadata JSON into our local machine and modify it or we can modify it on the go using tempfiles. I have used the later approach as it eliminates the need of JSON management&nbsp;</P><pre class="lia-code-sample language-python"><code>#Concatenating the space and username new_db_user = f"{space}#{username}" #Appending the new_db_user details into the space metadata JSON space_JSON[space]["spaceDefinition"]["dbusers"][new_db_user] = { "ingestion":{ "auditing":{ "dppRead":{ "retentionPeriod":21 "isAuditPolicyActive":True }, } }, "consumption":{ "consumptionWithGrant":false, "spaceSchemaAccess":True, "scriptServerAccess":false, "localSchemaAccess":false, "hdiGrantorForCupsAccess":false }</code></pre><P>Once the JSON is modified, write the new JSON into a temporary file. I have stored the file path into a variable called tmp_file_path. I will push this file path to datasphere tenant.</P><pre class="lia-code-sample language-python"><code>#Pushing the modified JSON to datasphere tenant datasphere space create --file-path "{tmp_file_path}" -- force-defination-deployment </code></pre><P>If you want to delete a database user, remove the entry from the JSON and run the same command and add&nbsp;<SPAN><STRONG>--enforce-database-user-deletion</STRONG> in the end. If this command is not added, then the deletion will not work.</SPAN></P><H3 id="toc-hId-687006152">Password Reset</H3><P>Once the database user is created, we need to reset the password to store it in a local file for sharing. Below is the command to perform that action</P><pre class="lia-code-sample language-python"><code>datasphere dbusers password reset --space{"space"} --databaseuser {"new_db_user"}</code></pre><P>&nbsp;(<STRONG>Tip: Add a 30 seconds wait time between creating the database user and resetting the password commands because the space deployment takes some time and we cannot reset the password before that</STRONG>).</P><P>I have also added an automatic email creation in my workflow and inserting database user details into a local table for future analysis.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2025-08-09 at 20.56.29.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/298901iDFDFD74E03AF67EA/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot 2025-08-09 at 20.56.29.png" alt="Screenshot 2025-08-09 at 20.56.29.png" /></span></P><H3 id="toc-hId-490492647">Conclusion:</H3><P>With this automation, we can create database users in datasphere tenant without even login into datasphere. This workflow provides a more robust way to handle creation and maintenance database users.&nbsp;</P><P>I would love to know your thoughts on this workflow in the comments below. Lets explore more opportunities of automation using datasphere CLI.&nbsp;</P> 2025-08-12T08:06:51.294000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/predictive-stock-transfer-amp-automatic-purchase-re-order-plant-to-plant/ba-p/14170063 Predictive Stock Transfer & Automatic Purchase Re-Order: Plant-to-Plant A2A Orchestration 2025-08-16T14:04:54.954000+02:00 Yogananda https://community.sap.com/t5/user/viewprofilepage/user-id/75 <P><STRONG>Use-case: Predictive Stock Transfer &amp; Automatic Purchase Re-Order (Plant-to-Plant A2A scenario driven by real-time APIs)</STRONG><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_0-1755345825205.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/301623i7ED9C4CC9FB704BB/image-size/large?v=v2&amp;px=999" role="button" title="Yogananda_0-1755345825205.png" alt="Yogananda_0-1755345825205.png" /></span></P><P><SPAN>A fast-moving material in Plant A is kept in stock by an end-to-end, automated flow that </SPAN></P><OL><LI><SPAN>checks forecast demand, </SPAN></LI><LI><SPAN>looks for internal surplus in nearby plants, and </SPAN></LI><LI><SPAN>automatically creates stock transfers or purchase requisitions as needed. </SPAN></LI></OL><P><STRONG>Prerequisities and needed</STRONG></P><UL><LI><SPAN>SAP S/4HANA Cloud (Inventory &amp; MRP) – On-hand stock, stock in transit, MRP items</SPAN></LI><LI><SPAN>SAP Integrated Business Planning (IBP) – Demand forecast for FG-100 at Plant A</SPAN></LI><LI>SAP Extended Warehouse Management (EWM) – Real-time on-hand stock incl. quarantine</LI><LI><SPAN>SAP Ariba – Supplier catalog and pricing for external options</SPAN></LI><LI><SPAN>External supplier catalog (Ariba-like) – External pricing and lead times</SPAN></LI><LI><SPAN>SAP Integration Suite / SAP Event Mesh – Orchestrates the end-to-end flow and publishes events</SPAN></LI></UL><P><STRONG>Sequence – how the APIs interact end-to-end</STRONG></P><P><STRONG>Step 0 – Trigger</STRONG><BR />A nightly iFlow in SAP Integration Suite (or SAP Event Mesh) starts the orchestration.</P><P><STRONG>Step 1 – Demand forecast</STRONG><BR />GET /api/ibp/v1/demandplanning/forecast?material=FG-100&amp;plant=A&amp;weeks=4<BR />→ Returns 1,200 pcs forecasted demand.</P><P><STRONG>Step 2 – Current &amp; projected stock in Plant A</STRONG><BR />GET /sap/opu/odata/sap/API_MATERIAL_STOCK_SRV/MaterialStock?material=FG-100&amp;plant=A<BR />→ 180 pcs unrestricted, 40 pcs blocked.<BR />GET /sap/opu/odata/sap/API_MRP_COCKPIT_SRV/MrpItems?material=FG-100&amp;plant=A<BR /><SPAN>Result: confirmed receipts 600 pcs</SPAN><BR />→ Confirmed receipts 600 pcs, so net shortage = 1,200 – 180 – 600 = 420 pcs.</P><P><STRONG>Step 3 – Locate surplus stock in the network</STRONG><BR />Parallel calls (one per plant):<BR />GET /sap/opu/odata/sap/API_MATERIAL_STOCK_SRV/MaterialStock?material=FG-100&amp;plant=B<BR />→ 300 pcs unrestricted.<BR />GET /sap/opu/odata/sap/API_MATERIAL_STOCK_SRV/MaterialStock?material=FG-100&amp;plant=C<BR />→ 250 pcs unrestricted.</P><P><STRONG>Step 4 – Check ATP (available-to-transfer) incl. transit time</STRONG><BR />POST /sap/opu/odata/sap/API_ATP_CHECK_SRV/CheckAvailability<BR />Body:</P><pre class="lia-code-sample language-json"><code>{ "material": "FG-100", "plant": "B", "demandQty": 300, "requiredDate": "2024-06-25" }</code></pre><P>→ Confirms 300 pcs can be delivered by 2024-06-23.<BR />Same for Plant C → 120 pcs available by 2024-06-24.</P><P><STRONG>Step 5 – Decide cheapest internal option</STRONG><BR />Cost service (custom REST on S/4):<BR />POST /internal/transferCost</P><pre class="lia-code-sample language-json"><code>{ "fromPlants": ["B","C"], "toPlant": "A", "qty": [300,120] }</code></pre><P><BR />→ Plant B has the lowest freight cost (€0.05/pc vs €0.07/pc).</P><P><STRONG>Step 6 – Create stock transport order (STO)</STRONG><BR />POST /sap/opu/odata/sap/API_STOCK_TRANSPORT_ORDER_SRV/A_StockTransportOrder<BR />Body:</P><pre class="lia-code-sample language-json"><code>{ "SupplyingPlant": "B", "ReceivingPlant": "A", "Material": "FG-100", "OrderQuantity": 300, "DeliveryDate": "2024-06-23" }</code></pre><P>→ STO 4500012345 created.</P><P><STRONG>Step 7 – Remaining uncovered quantity</STRONG><BR />Shortage after internal transfer = 420 – 300 = 120 pcs.<BR />Call Ariba to get best external price:<BR />GET /v2/suppliers/catalog?material=FG-100&amp;qty=120&amp;currency=EUR<BR />→ Supplier S-987 offers €2.30/pc, lead time 7 days.</P><P><STRONG>Step 8 – Create purchase requisition in S/4</STRONG><BR />POST /sap/opu/odata/sap/API_PURCHASEREQ_PROCESS_SRV/A_PurchaseRequisitionHeader<BR />Body:</P><pre class="lia-code-sample language-json"><code>{ "Material": "FG-100", "Plant": "A", "Quantity": 120, "DeliveryDate": "2024-06-27", "SupplierHint": "S-987" }</code></pre><P>→ PR 1000123456 created.</P><P><STRONG>Step 9 – Notify planners</STRONG><BR />Publish event to SAP Event Mesh topic /business/plantA/stockReplenished<BR />Payload:</P><pre class="lia-code-sample language-json"><code>{ "material": "FG-100", "sto": "4500012345", "pr": "1000123456", "status": "covered" }</code></pre><P><STRONG>Outcome</STRONG><BR />Plant A will receive 300 pcs from Plant B via an automatically created STO and 120 pcs via a purchase requisition with the cheapest external supplier—no manual intervention, no stock-out, and minimal freight cost.</P><P>complete code</P><pre class="lia-code-sample language-python"><code>#!/usr/bin/env python3 """ End-to-end A2A flow: 1. Read demand forecast (IBP) 2. Check stock &amp; MRP in Plant A 3. Search surplus stock in Plants B/C 4. Run ATP check 5. Create STO (cheapest internal) 6. Create PR for remaining qty (Ariba best price) """ import os, json, math from datetime import datetime, timedelta from dotenv import load_dotenv from requests import Session from requests_oauthlib import OAuth2Session from oauthlib.oauth2 import BackendApplicationClient load_dotenv() # ---------- CONFIG ---------- MATERIAL = "FG-100" PLANT_A = "A" PLANTS_SURPLUS = ["B", "C"] DEMAND_WEEKS = 4 TRANSPORT_DAYS = 2 # ---------------------------- s = Session() # ---------- 0. OAUTH TOKEN for S/4 ---------- def s4_token(): url = f"{os.getenv('S4_HOST')}/oauth/token" r = s.post(url, auth=(os.getenv('S4_USER'), os.getenv('S4_PASSWORD')), data={'grant_type':'client_credentials'}) r.raise_for_status() return r.json()['access_token'] s.headers.update({'Authorization': f"Bearer {s4_token()}"}) # ---------- 1. IBP – demand forecast ---------- def ibp_forecast(): url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_DEMAND_PLANNING_SRV/DemandForecast") params = { "$filter": f"Material eq '{MATERIAL}' and Plant eq '{PLANT_A}'", "$select": "DemandQuantity", "$format": "json" } r = s.get(url, params=params) r.raise_for_status() total = sum(item['DemandQuantity'] for item in r.json()['d']['results']) return total demand_qty = ibp_forecast() print("Demand forecast:", demand_qty) # ---------- 2. Stock &amp; MRP ---------- def plant_stock(plant): url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_MATERIAL_STOCK_SRV/MaterialStock") params = { "$filter": f"Material eq '{MATERIAL}' and Plant eq '{plant}' and " f"StorageLocation ne ''", "$format": "json" } r = s.get(url, params=params) r.raise_for_status() return sum(item['UnrestrictedStockQuantity'] for item in r.json()['d']['results']) def plant_receipts(plant): url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_MRP_COCKPIT_SRV/MrpItems") params = { "$filter": f"Material eq '{MATERIAL}' and Plant eq '{plant}' and " f"MrpElementCategory eq 'AR'", "$format": "json" } r = s.get(url, params=params) r.raise_for_status() return sum(item['Quantity'] for item in r.json()['d']['results']) stock_A = plant_stock(PLANT_A) receipts_A = plant_receipts(PLANT_A) shortage = max(0, demand_qty - stock_A - receipts_A) print("Shortage:", shortage) if shortage == 0: print("No action needed.") exit() # ---------- 3. Surplus stock ---------- surplus = {} for p in PLANTS_SURPLUS: surplus[p] = plant_stock(p) print("Surplus:", surplus) # ---------- 4. ATP check ---------- def atp_ok(plant, qty, req_date): url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_ATP_CHECK_SRV/CheckAvailability") body = { "Material": MATERIAL, "Plant": plant, "DemandQuantity": str(qty), "RequiredDate": req_date.isoformat() } r = s.post(url, json=body) r.raise_for_status() return r.json()['d']['ConfirmedQuantity'] == str(qty) best_internal = None needed = shortage for plant, qty in surplus.items(): take = min(qty, needed) req = datetime.utcnow().date() + timedelta(days=TRANSPORT_DAYS) if atp_ok(plant, take, req): best_internal = (plant, take) break if best_internal: plant, qty = best_internal print(f"Best internal: {qty} from {plant}") # ---------- 5. Create STO ---------- url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_STOCK_TRANSPORT_ORDER_SRV/A_StockTransportOrder") body = { "SupplyingPlant": plant, "ReceivingPlant": PLANT_A, "Material": MATERIAL, "OrderQuantity": str(qty), "DeliveryDate": req.isoformat() } r = s.post(url, json=body) r.raise_for_status() sto = r.json()['d']['StockTransportOrder'] print("STO created:", sto) shortage -= qty # ---------- 6. Ariba – best external price ---------- if shortage &gt; 0: client = BackendApplicationClient(client_id=os.getenv('ARIBA_CLIENT_ID')) oauth = OAuth2Session(client=client) token = oauth.fetch_token( token_url=os.getenv('ARIBA_TOKEN_URL'), client_id=os.getenv('ARIBA_CLIENT_ID'), client_secret=os.getenv('ARIBA_CLIENT_SECRET') ) url = "https://api.ariba.com/v2/suppliers/catalog" params = { "material": MATERIAL, "qty": shortage, "currency": "EUR" } r = oauth.get(url, params=params) r.raise_for_status() best = min(r.json()['offers'], key=lambda x: float(x['price'])) print("Best supplier:", best['supplier'], best['price']) # ---------- 7. Create PR ---------- url = (f"{os.getenv('S4_HOST')}/sap/opu/odata/sap/" f"API_PURCHASEREQ_PROCESS_SRV/A_PurchaseRequisitionHeader") body = { "Material": MATERIAL, "Plant": PLANT_A, "Quantity": str(shortage), "DeliveryDate": (datetime.utcnow().date() + timedelta(days=int(best['leadtime']))).isoformat(), "SupplierHint": best['supplier'] } r = s.post(url, json=body) r.raise_for_status() pr = r.json()['d']['PurchaseRequisition'] print("PR created:", pr) print("Flow finished.")</code></pre><P>&nbsp;</P> 2025-08-16T14:04:54.954000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/sap-datasphere-amp-python-one-click-to-export-data-of-multiple-views-in/ba-p/14180664 SAP Datasphere & Python : One click to export data of multiple views in Excel/CSV 2025-08-19T08:22:39.531000+02:00 vikasparmar88 https://community.sap.com/t5/user/viewprofilepage/user-id/1528256 <P><STRONG><FONT size="5">Introduction</FONT></STRONG></P><P>Currently, SAP Datasphere only allows data export through Analytical Models. That means for every fact view, one has to create a separate Analytical Model just to download the data. It’s not ideal, especially when you have many views. Exporting them one by one becomes slow and repetitive.</P><P>To solve this,&nbsp; Python script was developed that connects to SAP Datasphere, runs a query on each view from list, and saves the results as Excel files in a local drive path which is predefined. Now&nbsp; just run the script, and it exports everything in one go—no manual effort needed.</P><P><FONT size="5"><STRONG>Requirements&nbsp;</STRONG></FONT></P><P>Before running the script, install the following Python packages:</P><P>These packages enable secure connectivity to SAP Datasphere and support efficient data handling.</P><PRE>pip install hdbcli pip install sqlalchemy pip install sqlalchemy-hana</PRE><P><FONT size="5"><STRONG>Creating a Database User in SAP Datasphere</STRONG></FONT></P><P>To enable Python connectivity, create a database user:</P><OL><LI>Navigate to<SPAN>&nbsp;</SPAN><STRONG>Space Management</STRONG><SPAN>&nbsp;</SPAN>in SAP Datasphere</LI><LI>Select the relevant space</LI><LI>Click on<SPAN>&nbsp;</SPAN><STRONG>Database Access</STRONG></LI><LI>Create a new user with read access</LI><LI>Copy the host, port, username, and password</LI></OL><P>Helpful links :&nbsp;<A title="Create DB User in Datasphere Space" href="https://developers.sap.com/tutorials/data-warehouse-cloud-intro8-create-databaseuser..html" target="_blank" rel="noopener noreferrer">Create DB User in Datasphere Space</A>&nbsp;&nbsp;</P><P><STRONG><FONT size="5">Python&nbsp;Script</FONT></STRONG></P><PRE># <span class="lia-unicode-emoji" title=":package:">📦</span> Install required packages (run these in your terminal or notebook) # pip install hdbcli # SAP HANA database client # pip install sqlalchemy # SQL toolkit and ORM for Python # pip install sqlalchemy-hana # SAP HANA dialect for SQLAlchemy # <span class="lia-unicode-emoji" title=":books:">📚</span> Import necessary libraries import pandas as pd # For data manipulation and Excel export from hdbcli import dbapi # SAP HANA DBAPI for direct connection import warnings # To suppress unnecessary warnings import os # For file path and directory handling # <span class="lia-unicode-emoji" title=":prohibited:">🚫</span> Suppress warnings for cleaner output warnings.filterwarnings('ignore') # <span class="lia-unicode-emoji" title=":locked_with_key:">🔐</span> Define SAP Datasphere connection parameters # <span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">👉</span> Replace the placeholders below with your actual connection details db_user = '&lt;your_database_user&gt;' # User with access to target schema db_password = '&lt;your_secure_password&gt;' # Password (handle securely) db_host = '&lt;your_datasphere_host_url&gt;' # Host URL (e.g., xyz.hanacloud.ondemand.com) db_port = 443 # Default HTTPS port for SAP HANA Cloud db_schema = '&lt;your_schema_name&gt;' # Target schema containing views # <span class="lia-unicode-emoji" title=":file_folder:">📁</span> Ensure output folder exists for Excel exports output_folder = r'C:\Datasphere\Excel export' # Update path as needed os.makedirs(output_folder, exist_ok=True) # <span class="lia-unicode-emoji" title=":clipboard:">📋</span> Define list of views to extract data from # <span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">👉</span> Add or modify view names based on your schema view_list = ['VIEW_1', 'VIEW_2'] # Example views try: # <span class="lia-unicode-emoji" title=":globe_with_meridians:">🌐</span> Establish secure connection to SAP Datasphere connection = dbapi.connect( address=db_host, port=db_port, user=db_user, password=db_password, encrypt=True, sslValidateCertificate=True ) print("<span class="lia-unicode-emoji" title=":white_heavy_check_mark:">✅</span> Connected to SAP Datasphere") cursor = connection.cursor() # <span class="lia-unicode-emoji" title=":repeat_button:">🔁</span> Loop through each view and export its data for view_name in view_list: try: # <span class="lia-unicode-emoji" title=":bar_chart:">📊</span> Construct and execute SQL query sql_query = f'SELECT * FROM "{db_schema}"."{view_name}"' print(f"<span class="lia-unicode-emoji" title=":bar_chart:">📊</span> Executing query: {sql_query}") cursor.execute(sql_query) # <span class="lia-unicode-emoji" title=":inbox_tray:">📥</span> Fetch results and convert to DataFrame rows = cursor.fetchall() columns = [desc[0] for desc in cursor.description] df = pd.DataFrame(rows, columns=columns) # <span class="lia-unicode-emoji" title=":outbox_tray:">📤</span> Export DataFrame to Excel output_path = os.path.join(output_folder, f'{view_name}.xlsx') df.to_excel(output_path, index=False) print(f"<span class="lia-unicode-emoji" title=":white_heavy_check_mark:">✅</span> Data from '{view_name}' saved to: {output_path}") except dbapi.Error as view_err: print(f"<span class="lia-unicode-emoji" title=":cross_mark:">❌</span> Error querying '{view_name}': {view_err}") except dbapi.Error as db_err: print(f"<span class="lia-unicode-emoji" title=":cross_mark:">❌</span> Database error: {db_err}") except Exception as ex: print(f"<span class="lia-unicode-emoji" title=":warning:">⚠️</span> Unexpected error: {ex}") finally: # <span class="lia-unicode-emoji" title=":locked:">🔒</span> Ensure connection is closed gracefully if 'connection' in locals(): connection.close() print("<span class="lia-unicode-emoji" title=":locked:">🔒</span> Connection closed")</PRE><P><STRONG><FONT size="5">Script Capabilities</FONT></STRONG></P><UL><LI>Establishes secure connection to SAP Datasphere</LI><LI>Executes queries on each listed view</LI><LI>Saves data from each view into a separate Excel file</LI><LI>Stores all files in a defined folder</LI></UL><P>Only connection details and view names need to be updated. The script handles the rest.</P><P>&nbsp;<FONT size="5"><STRONG>One-Click Execution with a .bat File</STRONG></FONT></P><P>To simplify execution, create a&nbsp;<SPAN>&nbsp;</SPAN><STRONG>RunExport.bat</STRONG><SPAN>&nbsp;</SPAN>file to run the Python script with a double-click.</P><P>Double-clicking the file will automatically export all views to Excel without opening a terminal</P><PRE> off REM Activate Python and run the Export script REM Change to the script directory cd /d "C:\Datasphere\Excel export" REM Run the Python script python Export.py REM Pause to keep the window open (optional) pause</PRE><P><FONT size="5"><STRONG>Example</STRONG></FONT></P><P><STRONG><FONT size="4">Before Execution</FONT></STRONG>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="11.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300844iD619132276E2474C/image-size/large?v=v2&amp;px=999" role="button" title="11.jpeg" alt="11.jpeg" /></span></P><P><STRONG><SPAN>Double Click on "RunExcel.bat" file</SPAN></STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300845iCD8108A14F47A5B1/image-size/large?v=v2&amp;px=999" role="button" title="2.jpeg" alt="2.jpeg" /></span></P><P><STRONG><SPAN>Post Execution</SPAN></STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.jpg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300847iD739F0EA7A4E9615/image-size/large?v=v2&amp;px=999" role="button" title="3.jpg" alt="3.jpg" /></span></P><P><FONT size="5"><STRONG>Conclusion</STRONG></FONT><BR />This automation simplifies data exports from SAP Datasphere, especially when working with multiple views.</P><UL><LI>It reduces manual effort</LI><LI>improves consistency and saves time.</LI><LI>ideal for recurring tasks or scheduled jobs.</LI></UL><P>For setup support or customization, feel free to connect.</P><P>Thanks</P><P>Vikas Parmar</P><P><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Datasphere/pd-p/73555000100800002141" class="lia-product-mention" data-product="16-1">SAP Datasphere</a>&nbsp;<a href="https://community.sap.com/t5/c-khhcw49343/SAP+Business+Data+Cloud/pd-p/73554900100700003531" class="lia-product-mention" data-product="1249-1">SAP Business Data Cloud</a>&nbsp;<a href="https://community.sap.com/t5/c-khhcw49343/Python/pd-p/f220d74d-56e2-487e-8e6c-a8cb3def2378" class="lia-product-mention" data-product="126-1">Python</a>&nbsp;&nbsp;</P> 2025-08-19T08:22:39.531000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-in-sap-business-data-cloud-a-typical-machine-learning/ba-p/14206612 SAP Databricks in SAP Business Data Cloud – a Typical Machine Learning Workflow 2025-09-04T04:16:17.227000+02:00 js2 https://community.sap.com/t5/user/viewprofilepage/user-id/41060 <P>With SAP Databricks you have access to an amazing set of capabilities to work with your BDC Data Products and other data.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_0-1756949550337.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308823i50F7383D319ECA1F/image-size/large?v=v2&amp;px=999" role="button" title="js2_0-1756949550337.png" alt="js2_0-1756949550337.png" /></span></P><P>&nbsp;</P><P>This blog post is part of a series exploring SAP Databricks in SAP Business Data Cloud:</P><P><STRONG><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-building-an-intelligent-enterprise-with-ai-unleashed-part-1/ba-p/14166813" target="_self"><SPAN>Part 1 – SQL analytics with SAP Data Products<BR /></SPAN></A><SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-building-an-intelligent-enterprise-with-ai-unleashed-part-2/ba-p/14167025" target="_self">Part 2 – Build and deploy Mosaic AI and Agent Tools</A><BR /><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-how-to-use-automl-to-forecast-sales-data-part-3/ba-p/14174354" target="_self">Part 3 – How to use AutoML to forecast sales data</A><BR /></SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-building-an-intelligent-enterprise-with-ai-unleashed-part-3/ba-p/14174201" target="_self"><SPAN>Part 4 – Connect SAP Data Products with non-SAP data from AWS S3<BR /></SPAN></A><SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-building-an-intelligent-enterprise-with-ai-unleashed-part-4/ba-p/14178056" target="_self">Part 5 – End-to-end integration: SAP Databricks, SAP Datasphere, and SAP Analytics Cloud</A><BR /></SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-create-inferences-for-application-integration-with-sap-build/ba-p/14186662" target="_self">Part 6 – Create inferences for application integration with SAP Build&nbsp;</A></STRONG></P><P><STRONG>Part 7 -&nbsp;SAP Databricks in SAP Business Data Cloud – a Typical Machine Learning Workflow</STRONG></P><P>&nbsp;</P><P>In this blog post we’ll look at the typical workflow you would undertake when trying to train a machine learning model.</P><UL><LI>Visualise and understand your data</LI><LI>Optimise for hyperparameters to tune your model</LI><LI>Explore hyperparameter sweep results with MLflow</LI><LI>Register the best performing model in MLflow</LI><LI>Apply the registered model with batch inference and Databricks Model Serving</LI></UL><P>We’ll use a classic machine learning dataset to predict whether a wine is of high quality or not (<STRONG><EM>a data classification problem</EM></STRONG>). Of course you have access to a range of SAP Data Products, but by using this dataset you don’t even need a connected S/4HANA system to follow along. The dataset also comes built-in with SAP Databricks.</P><P>&nbsp;</P><H2 id="toc-hId-1759168994">Wine Quality Classification</H2><P>We will train a binary classification model to predict the quality of Portuguese "Vinho Verde" wine based on the wine's physicochemical properties.</P><P>The dataset is from the UCI Machine Learning Repository, presented in Modelling wine preferences by data mining from physicochemical properties [Cortez et al., 2009]. And the good news is that this dataset comes preloaded with your Databricks system.</P><P>&nbsp;</P><P>Let’s create a new notebook in our SAP Databricks system and in a new cell we will install some module dependencies.</P><pre class="lia-code-sample language-python"><code>%pip install --upgrade -Uqqq mlflow&gt;=3.0 xgboost hyperopt %restart_python</code></pre><P>Installs:</P><UL><LI>The latest <STRONG><A href="https://mlflow.org/" target="_blank" rel="nofollow noopener noreferrer">mlflow</A></STRONG> for experiment tracking and general MLOps</LI><LI><STRONG><A href="https://xgboost.readthedocs.io/en/stable/" target="_blank" rel="nofollow noopener noreferrer">xgboost</A></STRONG> being a fantastic and very popular machine learning model architecture (it dominates many Kaggle competitions)</LI><LI><STRONG><A href="https://hyperopt.github.io/hyperopt/" target="_blank" rel="nofollow noopener noreferrer">hyperopt</A></STRONG> is a python library used for hyperparameter optimisation. It intelligently searches for the optimal hyperparameters to use when training a machine learning model.</LI><LI>The `<STRONG>%restart_python</STRONG>` magic command it necessary in Databricks notebooks because they use long running Python processes and this ensures that the system path and any newly installed python packages are being used.</LI></UL><P>&nbsp;</P><P>Next, we create a cell to connect MLFlow to the Databricks Unity Catalog (it would otherwise use an sqlite data store) and create a few constants that will be used later:</P><pre class="lia-code-sample language-python"><code>import mlflow mlflow.set_registry_uri("databricks-uc") CATALOG_NAME = "workspace" SCHEMA_NAME = "default" MODEL_NAME = "wine_quality_classifier"</code></pre><P>&nbsp;</P><H2 id="toc-hId-1562655489">Read and Understand the DATA</H2><P>Read the white wine quality and red wine quality CSV datasets and merge them into a single DataFrame. <EM>Note theses datasets come with your Databricks system</EM>.</P><pre class="lia-code-sample language-python"><code>import pandas as pd white_wine = pd.read_csv("/databricks-datasets/wine-quality/winequality-white.csv", sep=";") red_wine = pd.read_csv("/databricks-datasets/wine-quality/winequality-red.csv", sep=";")</code></pre><P>&nbsp;</P><P>Merge the two DataFrames into a single dataset, with a new binary feature "is_red" that indicates whether the wine is red or white.</P><pre class="lia-code-sample language-python"><code>red_wine['is_red'] = 1 white_wine['is_red'] = 0 data = pd.concat([red_wine, white_wine], axis=0) # cast to float as a best practice (avoids dtype issues with NaN's later) data["is_red"] = data["is_red"].astype("float32") # Remove spaces from column names data.rename(columns=lambda x: x.replace(' ', '_'), inplace=True)</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_1-1756949711124.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308824i39D3D0E1BF31443D/image-size/large?v=v2&amp;px=999" role="button" title="js2_1-1756949711124.png" alt="js2_1-1756949711124.png" /></span></P><P>Now we have one dataset with a mix of white and red wines.</P><P>&nbsp;</P><H3 id="toc-hId-1495224703">Visualize data</H3><P>Before training a model, explore the dataset using popular charting libraries: Seaborn and Matplotlib.</P><P>Plot a histogram of the dependent variable, quality.</P><pre class="lia-code-sample language-python"><code>import seaborn as sns sns.displot(data.quality, kde=False)</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_2-1756949850129.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308825i87E79215C173B70F/image-size/large?v=v2&amp;px=999" role="button" title="js2_2-1756949850129.png" alt="js2_2-1756949850129.png" /></span></P><P>Looks like quality scores are normally distributed between 3 and 9. Define a wine as high quality if it has quality &gt;= 7.</P><pre class="lia-code-sample language-python"><code>high_quality = (data.quality &gt;= 7) data.quality = high_quality # cast to float as a best practice (avoids dtype issues with NaN's later) data["quality"] = data["quality"].astype("float32")</code></pre><P>&nbsp;</P><P>Box plots are useful for identifying correlations between features and a binary label. Create box plots for each feature to compare high-quality and low-quality wines. Significant differences in the box plots indicate good predictors of quality.</P><pre class="lia-code-sample language-python"><code>import matplotlib.pyplot as plt dims = (3, 4) f, axes = plt.subplots(dims[0], dims[1], figsize=(25, 15)) axis_i, axis_j = 0, 0 for col in data.columns: if col == 'is_red' or col == 'quality': continue # Box plots cannot be used on indicator variables sns.boxplot(x=high_quality, y=data[col], ax=axes[axis_i, axis_j]) axis_j += 1 if axis_j == dims[1]: axis_i += 1 axis_j = 0</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_3-1756949850144.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308826iF8D13D55625B9536/image-size/large?v=v2&amp;px=999" role="button" title="js2_3-1756949850144.png" alt="js2_3-1756949850144.png" /></span></P><P>In the above box plots, a few variables stand out as good univariate predictors of quality.</P><UL><LI>In the alcohol box plot, the median alcohol content of high-quality wines is greater than even the 75th quantile of low-quality wines. High alcohol content is correlated with quality.</LI><LI>In the density box plot, low quality wines have a greater density than high quality wines. Density is inversely correlated with quality.</LI></UL><H2 id="toc-hId-1169628479">&nbsp;</H2><H2 id="toc-hId-973114974">Preprocess data</H2><P>Before training a model, check for missing values and split the data into training and validation sets.</P><pre class="lia-code-sample language-python"><code>data.isna().any()</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_4-1756950017285.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308827i2A0200F5FB07390B/image-size/medium?v=v2&amp;px=400" role="button" title="js2_4-1756950017285.png" alt="js2_4-1756950017285.png" /></span></P><P>There are no missing values.</P><P>&nbsp;</P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>Note: Often you will need to take advantage of <STRONG>feature engineering</STRONG> at this step. This is where you can build new features as combinations of your existing data… for example multiplying two existing feature columns together to create a new column may enable the model to find better patterns in the data.</EM></P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>For time series data it can be very helpful to generate a new feature column called “Qtr” for example to show which qtr of the year that data point is in based on a date. You will need to experiment with this…</EM></P><P>&nbsp;</P><H2 id="toc-hId-776601469">Prepare the dataset to train a baseline model</H2><P>Split the input data into 3 sets:</P><UL><LI>Train (60% of the dataset used to train the model)</LI><LI>Validation (20% of the dataset used to tune the hyperparameters)</LI><LI>Test (20% of the dataset used to report the true performance of the model on an unseen dataset)</LI></UL><pre class="lia-code-sample language-python"><code>from sklearn.model_selection import train_test_split X = data.drop(["quality"], axis=1) y = data.quality # Split out the training data X_train, X_rem, y_train, y_rem = train_test_split(X, y, train_size=0.6, random_state=123) # Split the remaining data equally into validation and test X_val, X_test, y_val, y_test = train_test_split(X_rem, y_rem, test_size=0.5, random_state=123)</code></pre><P>&nbsp;</P><H2 id="toc-hId-580087964">Train a baseline model</H2><P>This task seems well suited to a <STRONG>random forest classifier</STRONG>, since the output is binary and there may be interactions between multiple variables.</P><P>Build a simple classifier using scikit-learn and use MLflow to keep track of the model's accuracy and save the model for later use.</P><P>&nbsp;</P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>Note: When this cell is executed an MLFlow Experiment will be created by default (automatically) using the full path of this Notebook. In production experiments its best practice to set the Experiment name with&nbsp;mlflow.set_experiment()&nbsp;because you may work on the problem over multiple Notebooks and/or users.</EM></P><pre class="lia-code-sample language-python"><code>import mlflow.pyfunc import mlflow.sklearn import numpy as np import sklearn from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score from mlflow.models.signature import infer_signature from mlflow.utils.environment import _mlflow_conda_env import cloudpickle import time # The predict method of sklearn's RandomForestClassifier returns a binary classification (0 or 1). # The following code creates a wrapper function, SklearnModelWrapper, that uses # the predict_proba method to return the probability that the observation belongs to each class. class SklearnModelWrapper(mlflow.pyfunc.PythonModel): def __init__(self, model): self.model = model def predict(self, context, model_input): return self.model.predict_proba(model_input)[:,1] # mlflow.start_run creates a new MLflow run to track the performance of this model. # Within the context, you call mlflow.log_param to keep track of the parameters used, and # mlflow.log_metric to record metrics like accuracy. with mlflow.start_run(run_name='rf_baseline_n10'): n_estimators = 10 model = RandomForestClassifier(n_estimators=n_estimators, random_state=np.random.RandomState(123)) model.fit(X_train, y_train) # predict_proba returns [prob_negative, prob_positive], so slice the output with [:, 1] predictions_test = model.predict_proba(X_test)[:,1] auc_score = roc_auc_score(y_test, predictions_test) mlflow.log_param('n_estimators', n_estimators) # Use the area under the ROC curve as a metric. mlflow.log_metric('auc', auc_score) wrappedModel = SklearnModelWrapper(model) # MLflow contains utilities to create a conda environment used to serve models. # The necessary dependencies are added to a conda.yaml file which is logged along with the model. conda_env = _mlflow_conda_env( additional_conda_deps=None, additional_pip_deps=["cloudpickle=={}".format(cloudpickle.__version__), "scikit-learn=={}".format(sklearn.__version__)], additional_conda_channels=None, ) # Here we log the model and register it to Unity Catalog in one go. # MLflow automatically generates model signatures when you provide # an `input_example` during model logging. This works for all model # flavors and is the recommended approach for most use cases. # By registering this model to Unity Catalog, you can easily reference # the model from anywhere within Databricks. # sample_input = X_train.head(5) model_version = mlflow.pyfunc.log_model( name="rf_baseline", python_model=wrappedModel, conda_env=conda_env, input_example=sample_input, registered_model_name=MODEL_NAME, )</code></pre><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>Note how the trained model is registered when logging it to MLFlow. This can be done separately as we will see later. If you are running many experiments there is no need to register every model but only the best model.</EM></P><P>Review the learned feature importances output by the model. As illustrated by the previous boxplots, alcohol and density are important in predicting quality.</P><pre class="lia-code-sample language-python"><code>feature_importances = pd.DataFrame(model.feature_importances_, index=X_train.columns.tolist(), columns=['importance']) feature_importances.sort_values('importance', ascending=False)</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_5-1756950458175.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308828i525ABF330AAFB999/image-size/medium?v=v2&amp;px=400" role="button" title="js2_5-1756950458175.png" alt="js2_5-1756950458175.png" /></span></P><P>&nbsp;</P><P>You logged the Area Under the ROC Curve (AUC) to MLflow. Click the Experiment icon in the right sidebar to display the Experiment Runs sidebar. The model achieved an AUC of 0.854. A random classifier would have an AUC of 0.5, and higher AUC values are better.</P><P>The ROC AUC is a good evaluation metric for binary classification problems like we have here (is good quality / is not good quality).</P><P>&nbsp;</P><P>Next, assign this model the "Best" tag, and load it into this notebook from Unity Catalog.</P><pre class="lia-code-sample language-python"><code>from mlflow.tracking import MlflowClient client = MlflowClient() client.set_registered_model_alias(MODEL_NAME, "Best", model_version.registered_model_version)</code></pre><P>In Unity Catalog, the model version now has the tag "Best". You can now refer to the model using the path&nbsp;<FONT face="terminal,monaco">models:/{model_name}@Best</FONT>.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_6-1756950552130.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308830iB8AEA9EA4051F02D/image-size/medium?v=v2&amp;px=400" role="button" title="js2_6-1756950552130.png" alt="js2_6-1756950552130.png" /></span></P><P>&nbsp;</P><H2 id="toc-hId-383574459">Experiment with a new model</H2><P>The random forest model performed well even <EM>without</EM> hyperparameter tuning.</P><P>Let's now try and do better and use the xgboost library to train a more accurate model. Run a hyperparameter sweep to train multiple models in parallel, using Hyperopt and Trials. As before, MLflow tracks the performance of each parameter configuration.</P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>Note: We must use Trials and not SparkTrials in SAP Databricks, because SparkTrials tries to access the underlying JVM which is not supported on serverless compute.</EM></P><P>We use the validation dataset here for hyperparameter search.</P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><FONT color="#FF0000"><EM>Note this training cell takes over 20mins to complete!</EM></FONT></P><pre class="lia-code-sample language-python"><code>from hyperopt import fmin, tpe, hp, Trials, STATUS_OK from hyperopt.pyll import scope from math import exp import mlflow.xgboost import numpy as np import xgboost as xgb search_space = { 'max_depth': scope.int(hp.quniform('max_depth', 4, 100, 1)), 'learning_rate': hp.loguniform('learning_rate', -3, 0), 'reg_alpha': hp.loguniform('reg_alpha', -5, -1), 'reg_lambda': hp.loguniform('reg_lambda', -6, -1), 'min_child_weight': hp.loguniform('min_child_weight', -1, 3), 'objective': 'binary:logistic', 'seed': 123, # Set a seed for deterministic training } def train_model(params): # With MLflow autologging, hyperparameters and the trained model are automatically logged to MLflow. mlflow.xgboost.autolog() with mlflow.start_run(nested=True): train = xgb.DMatrix(data=X_train, label=y_train) validation = xgb.DMatrix(data=X_val, label=y_val) # Pass in the validation set so xgb can track an evaluation metric. XGBoost terminates training when the evaluation metric # is no longer improving. booster = xgb.train(params=params, dtrain=train, num_boost_round=1000, evals=[(validation, "validation")], early_stopping_rounds=50) validation_predictions = booster.predict(validation) auc_score = roc_auc_score(y_val, validation_predictions) mlflow.log_metric('auc', auc_score) # Don't register the model in one-step here - let the hyperparameter search find the best one first. #signature = infer_signature(X_train, booster.predict(train)) #mlflow.xgboost.log_model(booster, name="xgboost", signature=signature) sample_input = X_train.head(5) mlflow.xgboost.log_model(booster, name="xgboost", input_example=sample_input) # Set the loss to -1*auc_score so fmin maximizes the auc_score return {'status': STATUS_OK, 'loss': -1*auc_score, 'booster': booster.attributes()} # Use Trials instead of SparkTrials trials = Trials() # Run fmin within an MLflow run context so that each hyperparameter configuration is logged as a child run of a parent # run called "xgboost_models" . with mlflow.start_run(run_name='xgboost_models'): best_params = fmin( fn=train_model, space=search_space, algo=tpe.suggest, max_evals=96, trials=trials, )</code></pre><P>&nbsp;</P><H2 id="toc-hId-187060954">Use MLflow to view the results</H2><P>Open up the <EM>Experiments</EM> sidebar to see the MLflow runs. Click on Date next to the down arrow to display a menu, and select 'auc' to display the runs sorted by the auc metric. The highest auc value is ~0.90.&nbsp;<STRONG>Remember that this is against the validation data</STRONG>.</P><P class="lia-indent-padding-left-60px" style="padding-left : 60px;"><EM>Hyperparameter tuning (runs) score against the validation dataset!</EM></P><P>MLflow tracks the parameters and performance metrics of each run. Click the External Link icon at the top of the Experiment Runs sidebar to navigate to the MLflow Runs Table.</P><P>&nbsp;</P><H2 id="toc-hId--9452551">Update the best version of the&nbsp;<FONT face="terminal,monaco">wine_quality_classifier</FONT>&nbsp;model</H2><P>Earlier, you saved the baseline model to Unity Catalog with the name&nbsp;<FONT face="terminal,monaco">wine_quality_classifier</FONT>. Now you can update&nbsp;<FONT face="terminal,monaco">wine_quality_classifier</FONT>&nbsp;to a more accurate model from the hyperparameter sweep. Because you used MLflow to log the model produced by each hyperparameter configuration, you can use MLflow to identify the best performing run and save the model from that run to Unity Catalog.</P><pre class="lia-code-sample language-python"><code>best_run = mlflow.search_runs(order_by=['metrics.auc DESC']).iloc[0] print(f'AUC of Best Run: {best_run["metrics.auc"]}')</code></pre><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_7-1756950758865.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308831i3D6D3AA368E57F2C/image-size/medium?v=v2&amp;px=400" role="button" title="js2_7-1756950758865.png" alt="js2_7-1756950758865.png" /></span></P><pre class="lia-code-sample language-python"><code>new_model_version = mlflow.register_model(f"runs:/{best_run.run_id}/model", MODEL_NAME) # Registering the model takes a few seconds, so add a small delay import time time.sleep(15)</code></pre><P>&nbsp;</P><P>&nbsp;</P><P>Click&nbsp;<STRONG>Models</STRONG>&nbsp;in the left sidebar to see that the&nbsp;<FONT face="terminal,monaco">wine_quality_classifier</FONT>&nbsp;model now has a new versions. Assign the "Best" alias to the new version.</P><pre class="lia-code-sample language-python"><code>from mlflow.tracking import MlflowClient client = MlflowClient() client.set_registered_model_alias(MODEL_NAME, "Best", new_model_version.version)</code></pre><P>&nbsp;</P><P>Clients that call&nbsp;<FONT face="terminal,monaco">load_model()</FONT>&nbsp;using the "Best" alias now get the new model.</P><P><STRONG>&gt;&gt; </STRONG><STRONG>Let's get the AUC score against the Test data</STRONG><STRONG>:</STRONG></P><pre class="lia-code-sample language-python"><code>model = mlflow.pyfunc.load_model(f"models:/{MODEL_NAME}@Best") from sklearn.metrics import roc_auc_score print(f'AUC: {roc_auc_score(y_test, model.predict(X_test))}')</code></pre><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_8-1756950758865.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308832i435B28BDC047F3F0/image-size/medium?v=v2&amp;px=400" role="button" title="js2_8-1756950758865.png" alt="js2_8-1756950758865.png" /></span></P><P>The new version achieved a better score (AUC = 0.90) on the test set.</P><P>&nbsp;</P><H2 id="toc-hId-141288301">Batch inference</H2><P>There are many scenarios where you might want to evaluate a model on a corpus of new data. For example, you may have a fresh batch of data or may need to compare the performance of two models on the same corpus of data.</P><P>Evaluate the model on data stored in a Delta table, using Spark to run the computation in parallel.</P><pre class="lia-code-sample language-python"><code># To simulate a new corpus of data, save the existing X_train data to a Delta table. # In the real world, this would be a new batch of data. spark_df = spark.createDataFrame(X_train) table_name = f"{CATALOG_NAME}.{SCHEMA_NAME}.wine_data" (spark_df .write .format("delta") .mode("overwrite") .option("overwriteSchema",True) .saveAsTable(table_name) )</code></pre><P>&nbsp;</P><P>Load the model into a Spark UDF, so it can be applied to the Delta table.</P><pre class="lia-code-sample language-python"><code>apply_model_udf = mlflow.pyfunc.spark_udf(spark, f"models:/{MODEL_NAME}@Best")</code></pre><pre class="lia-code-sample language-python"><code># Read the "new data" from the Unity Catalog table new_data = spark.read.table(f"{CATALOG_NAME}.{SCHEMA_NAME}.wine_data")</code></pre><pre class="lia-code-sample language-python"><code>from pyspark.sql.functions import struct # Apply the model to the new data udf_inputs = struct(*(X_train.columns.tolist())) new_data = new_data.withColumn( "prediction", apply_model_udf(udf_inputs) ) display(new_data)</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_9-1756951022597.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308833iDD624907B34C7B23/image-size/large?v=v2&amp;px=999" role="button" title="js2_9-1756951022597.png" alt="js2_9-1756951022597.png" /></span></P><P>Each row now has an associated prediction. Note that the&nbsp;<STRONG>xgboost</STRONG>&nbsp;function is using the objective "binary:logistic" so the predictions shown here are probabilities.</P><P>We also add a is_good_quality column:</P><pre class="lia-code-sample language-python"><code>from pyspark.sql.functions import col, when new_data = new_data.withColumn("prediction", col("prediction")[0]) new_data = new_data.withColumn( "is_good_quality", when(col("prediction") &gt; 0.5, True).otherwise(False) ) display(new_data)</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_10-1756951022605.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308834i46308B60261C3565/image-size/large?v=v2&amp;px=999" role="button" title="js2_10-1756951022605.png" alt="js2_10-1756951022605.png" /></span></P><P>Overwrite the table with the new columns:</P><pre class="lia-code-sample language-python"><code>(new_data .write .format("delta") .mode("overwrite") .option("overwriteSchema", True) .saveAsTable(table_name) ) # Enable Change Data Feed for the table # Seems that we can only add this option via SQL!! spark.sql(f"ALTER TABLE {table_name} SET TBLPROPERTIES (delta.enableChangeDataFeed = true)")</code></pre><P>&nbsp;</P><H2 id="toc-hId--55225204">Serve the model</H2><P>To productionize the model for low latency predictions, use Mosaic AI Model Serving to deploy the model to an endpoint. The following cell shows how to use the MLflow Deployments SDK to create a model serving endpoint (which can also be done view the Serving menu on the left).</P><P>&nbsp;</P><P>First of all, let’s just show the current model’s name and best version</P><pre class="lia-code-sample language-python"><code># Get the model vesion we tagged as <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1725027">@Best</a> from mlflow.tracking import MlflowClient best_ver = MlflowClient().get_model_version_by_alias(MODEL_NAME, "Best").version print(MODEL_NAME, best_ver)</code></pre><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_11-1756951367897.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308835i39C1C658C1D6029A/image-size/medium?v=v2&amp;px=400" role="button" title="js2_11-1756951367897.png" alt="js2_11-1756951367897.png" /></span></P><P>Check if any versions of this model are already being served and delete them</P><pre class="lia-code-sample language-python"><code>from mlflow.deployments import get_deploy_client client = get_deploy_client("databricks") endpoints = client.list_endpoints() deployed = False deployed_versions = [] for ep in endpoints: ep_detail = client.get_endpoint(ep["name"]) for entity in ep_detail.get("config", {}).get("served_models", []): if entity.get("model_name") == MODEL_NAME or entity.get("model_name").endswith(MODEL_NAME): deployed = True deployed_versions.append(str(entity.get("model_version"))) # Delete the serving endpoint if the model is already deployed client.delete_endpoint(ep["name"]) if deployed_versions: deployed_versions_str = ", ".join(deployed_versions) else: deployed_versions_str = "" display(spark.createDataFrame([{"model_name": MODEL_NAME, "deployed": deployed, "deployed_versions": deployed_versions_str, "action": "deleting..."}]))</code></pre><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_12-1756951367899.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308836iCF48545499B7C401/image-size/large?v=v2&amp;px=999" role="button" title="js2_12-1756951367899.png" alt="js2_12-1756951367899.png" /></span></P><P>&nbsp;</P><P><EM><FONT color="#FF0000">Creating the endpoint can take 5+ minutes...</FONT></EM></P><pre class="lia-code-sample language-python"><code># the "name" property can't include special chars so we drop the catalog and schema from the model_name from mlflow.deployments import get_deploy_client client = get_deploy_client("databricks") endpoint = client.create_endpoint( name="wine-model-endpoint", config={ "served_entities": [ { "name": MODEL_NAME, "entity_name": f"{CATALOG_NAME}.{SCHEMA_NAME}.{MODEL_NAME}", "entity_version": best_ver, "workload_size": "Small", "scale_to_zero_enabled": True } ], } )</code></pre><P>&nbsp;</P><H2 id="toc-hId--251738709">Test the Model Serving Endpoint</H2><P>Navigate to User Settings -&gt; Developer and create an Access Token for calling the serving endpoint.</P><P>Ensure the model is being served as this can take 5-10 mins.</P><P>In the below cells the notebook will:</P><OL><LI>Ask for your access token</LI><LI>Setup a payload (the required inputs for your model)</LI><LI>Call the model serving endpoint!</LI></OL><P>&nbsp;</P><pre class="lia-code-sample language-python"><code>from getpass import getpass token = getpass("🔑 Paste your Databricks token: ")</code></pre><P>&nbsp;</P><P>Setup the api call request payload with sample wine quality data:</P><pre class="lia-code-sample language-python"><code>payload = { "dataframe_split": { "columns": [ "fixed_acidity", "volatile_acidity", "citric_acid", "residual_sugar", "chlorides", "free_sulfur_dioxide", "total_sulfur_dioxide", "density", "pH", "sulphates", "alcohol", "is_red" ], "data": [ [7.3, 0.19, 0.27, 1.6, 0.027, 35, 136, 0.99248, 3.38, 0.54, 11, 0], [7.8, 0.88, 0.00, 2.6, 0.098, 25, 67, 0.9968, 3.20, 0.68, 9.8, 1] ] }</code></pre><P>&nbsp;</P><P>Use the python requests package to make an api call to the SAP Databricks Model Serving Endpoint.</P><P>&nbsp;</P><P class="lia-indent-padding-left-30px" style="padding-left : 30px;"><FONT color="#FF0000"><EM>Make sure to update the endpoint uri below to match your current SAP Databricks system!</EM></FONT></P><pre class="lia-code-sample language-python"><code>import os, json, requests url = "https://&lt;uri&gt;.cloud.databricks.com/serving-endpoints/wine-model-endpoint/invocations" resp = requests.post( url, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" }, data=json.dumps(payload), timeout=60 ) if resp.status_code == 404: print("The endpoint is still deploying.") else: print(resp.json()) for i, score in enumerate(resp.json()["predictions"], start=1): is_good = score &gt;= 0.5 # quality flag print(f"Row {i}: {score:.3f} ➜ Good quality? {is_good}")</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="js2_13-1756951569054.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/308837i0BC6D9EA17EBEA20/image-size/large?v=v2&amp;px=999" role="button" title="js2_13-1756951569054.png" alt="js2_13-1756951569054.png" /></span></P><P>&nbsp;</P><P>You can use this API endpoint to perform inference from your own applications – as is done with blog post : “<STRONG><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-databricks-create-inferences-for-application-integration-with-sap-build/ba-p/14186662" target="_blank">Part 6 – Create inferences for application integration with SAP Build&nbsp;</A></STRONG>” in the series.</P><P>&nbsp;</P><H2 id="toc-hId--448252214">Conclusion</H2><P>We’ve seen in this notebook, if you have followed along, the typical pattern of training a machine learning model.</P><UL><LI>It always starts with understanding the data available. Visualising the data with histograms and box plots as shown here can be a great help. Use tools like ChatGPT to assist in the best ways to flesh out information about your data</LI><LI>It can often be helpful to create a quick baseline model just to see that we can do better than random luck with the training data</LI><LI>Use a hyperparameter optimisation tool to help search for the ideal parameters to tune the best model. Be very careful with the split of training, validation and test data and ensure that there can never be any overlap. Research how to do this if using time-series data</LI><LI>Use MLFlow to log training experiments and their generated models. Assign tags to highlight specific or “best” models.</LI><LI>Look at Batch Inference or Model Serving.<UL><LI>The former (batch) being ideal if you want to batch score a table of data and potentially share it back to BDC to be used in analytics models. Make use of scheduled notebooks to keep the data up to date and to train the model on new data</LI><LI>Use Model Serving to expose an endpoint for real-time applications to make predictions.</LI></UL></LI></UL><P>&nbsp;</P> 2025-09-04T04:16:17.227000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/hello-python-my-first-script-in-sap-bas-connecting-to-hana-cloud/ba-p/14228993 Hello Python: My First Script in SAP BAS Connecting to HANA Cloud 2025-09-26T13:05:26.454000+02:00 Sharathmg https://community.sap.com/t5/user/viewprofilepage/user-id/174516 <P>Credit:&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/183">@Vitaliy-R</a>&nbsp;Your startup blogs kindled my interest to explore working with Python in SAP ecosystem.&nbsp;<A href="https://community.sap.com/t5/technology-blog-posts-by-sap/using-python-in-sap-business-application-studio-my-notes/ba-p/14155516" target="_self">Python in BAS</A>&nbsp;and&nbsp;<A href="https://community.sap.com/t5/technology-blog-posts-by-sap/using-jupyter-in-sap-business-application-studio-my-notes/ba-p/14167294" target="_self">Jupyter in BAS</A>&nbsp;</P><P>When I first started exploring SAP Business Application Studio (BAS), I was curious about how Python could fit into the SAP landscape. I’ve mostly associated BAS with HANA artefacts(SQLScript, hdbcalculationview, hdbreptask etc.) and CAP artefacts, so writing a Python script inside BAS felt like venturing into new territory. My goal was simple: write a basic script and connect it to SAP HANA Cloud. What I discovered along the way is that Python not only works smoothly in BAS but also makes it easy to interact with HANA Cloud, opening up opportunities for data exploration, automation, and integration in a way that feels both modern and approachable.</P><P>Before jumping into the Python script, I had to get my environment ready in SAP Business Application Studio (BAS). Here’s what I set up:</P><P>A BAS dev space with a full-stack cloud application space since it supports multiple runtimes, including Python. I had a space with HANA Native Application type. Since the Python tools extension&nbsp;is not added by default, I edited the space to select the Python tools in the additional extension options.&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="HANA Dev Space Python extension" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320334iFEC4E0932EFEAC15/image-size/large?v=v2&amp;px=999" role="button" title="HANA_DevSpace_Setting.png" alt="HANA Dev Space Python extension" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">HANA Dev Space Python extension</span></span></P><P>&nbsp;Note: For initial steps to check the Python version, Jupyter notebook and set ups refer to the blogs listed at the start.&nbsp;</P><P>Use Case: I attempted to achieve the following:&nbsp;</P><UL><LI>Establish a connection to HANA Cloud</LI><LI>Execute an SQL query on a table/view&nbsp;</LI><LI>Display the results</LI></UL><P>In the BAS, I created a project from Template: SAP HANA Database Project</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Project Template.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320351iEAE035C8FCA7C5B5/image-size/large?v=v2&amp;px=999" role="button" title="Project Template.png" alt="Project Template.png" /></span></P><P>&nbsp;</P><P>Next step: Create a notebook file.&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="notebook file.png" style="width: 339px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320356i8F1BB8DEF9D0E888/image-size/medium?v=v2&amp;px=400" role="button" title="notebook file.png" alt="notebook file.png" /></span></P><P>My guide to connect to HANA Cloud:&nbsp;<A href="https://help.sap.com/docs/SAP_HANA_CLIENT/f1b440ded6144a54ada97ff95dac7adf/d12c86af7cb442d1b9f8520e2aba7758.html" target="_self" rel="noopener noreferrer">Connect to HANA Cloud</A>&nbsp;</P><P>When I first tried importing hdbcli into my Jupyter Notebook within BAS, I ran into the same ModuleNotFoundError. Even though I had already installed hdbcli In the terminal, the notebook kernel wasn’t recognizing it. On some search and prompting with GPT( <span class="lia-unicode-emoji" title=":beaming_face_with_smiling_eyes:">😁</span>), I understood that it's a common issue because Jupyter can run in a different Python environment than the terminal. The fix was simple: I ran</P><PRE>import sys !{sys.executable} -m pip install hdbcli</PRE><P>directly in a notebook cell. This ensures that the HANA client is installed in the same environment as the notebook kernel. After this step, I could successfully import dbapi and connect to HANA Cloud without any errors. It was a small but important lesson about Python environments in BAS, especially when using Jupyter.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="hdbcli Module Not found.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320378i62AF858DA44FE00C/image-size/large?v=v2&amp;px=999" role="button" title="hdbcli Module Not found.png" alt="hdbcli Module Not found.png" /></span>With the hdbcli package installed and working in my Jupyter Notebook, I was ready to write my first Python script to connect to SAP HANA Cloud.</P><P>In the next cell, I imported hdbcli in this notebook.&nbsp;</P><pre class="lia-code-sample language-python"><code>import hdbcli print(hdbcli.__file__)</code></pre><P>&nbsp;<span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="import hdbcli.png" style="width: 854px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320388iF426637D8D8CCB0F/image-size/large?v=v2&amp;px=999" role="button" title="import hdbcli.png" alt="import hdbcli.png" /></span></P><P>&nbsp;The next step was to&nbsp;gain access to the dbapi interface, which allows you to establish connections, execute SQL queries, and fetch results from your HANA Cloud instance. This simple import is the gateway to working with HANA directly from Python.</P><pre class="lia-code-sample language-python"><code>from hdbcli import dbapi</code></pre><P>The next step is to establish a connection to your HANA Cloud instance. This requires specifying the host, port, username, and password.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="hana cloud connection.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320408i84F10DA5613166DC/image-size/large?v=v2&amp;px=999" role="button" title="hana cloud connection.png" alt="hana cloud connection.png" /></span></P><P>&nbsp;After connecting, you can create a cursor object to execute SQL statements. An SQL statement, preferably a Select Query to test the retrieval of data from HANA Cloud. In my case, I used a Select with count on the number of records in a view. Once the variables were ready, execute the connection cursor object.</P><P>Note: in the SQL variable, use single quotes and a semicolon at the end of the query. (beginner tip&nbsp;<span class="lia-unicode-emoji" title=":slightly_smiling_face:">🙂</span> )</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Execution Cursor.png" style="width: 799px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320427iB0929785AAAB7257/image-size/large?v=v2&amp;px=999" role="button" title="Execution Cursor.png" alt="Execution Cursor.png" /></span></P><P>Now is the time to test the data retrieval from the script and compare it with the Database Explorer.</P><P>Drum roll....<span class="lia-unicode-emoji" title=":drum:">🥁</span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Data in DB explorer.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320447i3D6BB255F8FDBF13/image-size/medium?v=v2&amp;px=400" role="button" title="Data in DB explorer.png" alt="Data in DB explorer.png" /></span></P><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-right" image-alt="Data in Script.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/320448iDA977EF3358B8FF8/image-size/medium?v=v2&amp;px=400" role="button" title="Data in Script.png" alt="Data in Script.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>Hurray&nbsp;<span class="lia-unicode-emoji" title=":party_popper:">🎉</span></P><P>Completing my first Python script in SAP Business Application Studio and connecting it to HANA Cloud was an exciting milestone. From the initial curiosity to the small hurdles like installing hdbcli in the notebook and finally seeing my script return results, every step felt like a mini victory.</P><P>That simple output from HANA Cloud made all the effort worthwhile and gave me a real sense of accomplishment.</P><P>This experience has sparked my curiosity to explore more complex queries, data analysis, and automation using Python in SAP.</P><P>I hope my journey inspires others to take that first step and discover how fun and powerful working with Python and HANA Cloud can be.</P><P>Chao.&nbsp;</P> 2025-09-26T13:05:26.454000+02:00 https://community.sap.com/t5/financial-management-blog-posts-by-sap/sap-cpq-2511-scripting-for-custom-quote-actions-amp-quote-item-custom/ba-p/14253497 SAP CPQ 2511 - Scripting for Custom Quote Actions & Quote Item Custom Fields Access Control 2025-10-26T14:38:16.450000+01:00 Yogananda https://community.sap.com/t5/user/viewprofilepage/user-id/75 <P>This blog introduces the scripting enhancements in SAP CPQ 2511, tested and verified in my pre-release version. The scripting examples provided here are optimized for implementation and serve as a valuable resource for CPQ developers, functional consultants, and integration specialists seeking for practical guidance.<BR /><BR />Special thanks to Nikola &amp; Pavithran for upgrading to 2511 for pre-release testing</P><P>Follow for more updates from&nbsp; : <A href="https://profile.sap.com/u/Yogananda" target="_self" rel="noopener noreferrer">Yogananda Muthaiah</A></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2025-10-26_14-05-29.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/332560i4533DD2CD067FDDD/image-size/large?v=v2&amp;px=999" role="button" title="2025-10-26_14-05-29.png" alt="2025-10-26_14-05-29.png" /></span></P><H3 id="toc-hId-1892778276">Dynamic Access Control for Quote Item Custom Fields</H3><P>Access level permissions on Quote Item Custom Fields can now be dynamically set through scripting in <SPAN class="">Quote</SPAN> 2.0. This feature allows users to control the editability, read-only status, and visibility of fields, enhancing customization and security in managing quotes.</P><P class=""><FONT color="#FF00FF">Here's an example script:</FONT></P><pre class="lia-code-sample language-python"><code>from Scripting.Quote import QuoteFieldAccessLevel for item in context.PagedItems: QuoteFieldAccessContext.SetAccessLevel(item, 'TargetPrice', QuoteFieldAccessLevel.Hidden) QuoteFieldAccessContext.SetAccessLevel(item, 'TargetPrice', QuoteFieldAccessLevel.Readonly) QuoteFieldAccessContext.SetAccessLevel(item, 'TargetPrice', QuoteFieldAccessLevel.Editable) eachItem = context.Quote.GetItemByItemNumber(1) QuoteFieldAccessContext.SetAccessLevel(eachItem, 'TargetPrice', QuoteFieldAccessLevel.ReadOnly) QuoteFieldAccessContext.SetColumnAccessLevel('TargetPrice', QuoteFieldAccessLevel.ReadOnly) QuoteFieldAccessContext.SetAccessLevelForProductType(39, 'TargetPrice', QuoteFieldAccessLevel.ReadOnly) QuoteFieldAccessContext.SetAccessLevelForSection('ProductType', 'TargetPrice', QuoteFieldAccessLevel.Editable) QuoteFieldAccessContext.SetAccessLevelForCartTotal</code></pre><DIV><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2025-10-26_14-10-35.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/332558i583D3AF65C315A6D/image-size/large?v=v2&amp;px=999" role="button" title="2025-10-26_14-10-35.png" alt="2025-10-26_14-10-35.png" /></span></DIV><DIV>&nbsp;<SPAN>&nbsp;</SPAN></DIV><H3 id="toc-hId-1696264771">Executing Custom Quote Actions via Scripting</H3><P class="">As of the 2511 release, you can execute custom quote actions in <SPAN class="">Quote</SPAN> 2.0 within the Workflow context through scripting.</P><P class="">The <SPAN class="">SAP CPQ</SPAN> system checks all the permissions and conditions defined in the Workflow for these actions to ensure that only available custom quote actions are executed. For each available action, the system performs pre-actions and post-actions and sends notifications. The list of available custom quote actions is also returned in scripting.</P><P class=""><FONT color="#FF00FF">Here's an example script:</FONT></P><DIV class="">&nbsp;</DIV><pre class="lia-code-sample language-python"><code>customQuoteactionAvailable = WorkflowExecutor.IsActionAvailableForQuote(3102, context.Quote.Id) WorkflowExecutor.ExecuteActionOnQuote(3102, context.Quote.Id)</code></pre><P>&nbsp;</P><H3 id="toc-hId-1499751266">Enhanced ExtenalItemID Editing via API and Scripting</H3><P class="">As of 2511 release, extenalItemID can be edited, updated and accessed through API and scripting. This enhancement enables more flexible and efficient data management and integration scenarios.</P><P class=""><SPAN>to enable ExternalItemId to be changed from the Scripting</SPAN></P><P class=""><FONT color="#FF00FF">Here's an example script:</FONT></P><pre class="lia-code-sample language-python"><code>Items = context.Quote.GetAllItems() Items[0].ExternalItemId = 'Testing from Yoga to update ExternalItemId ' Trace.Write(Items[0].ExternalItemId)</code></pre><H3 id="toc-hId-1303237761">Improved <FONT color="#FF6600">RestClient </FONT>Methods for Decompressed Responses</H3><P><SPAN class="">RestClients</SPAN> previously returned unreadable strings for compressed responses. To address this issue and support the decompression of the response body for compressed responses using Gzip or Deflate compression methods, a new <SPAN class="">decompressResponse</SPAN> parameter was added. <FONT color="#FF6600">By default, it is set to False,</FONT> maintaining current functionality.</P><P><FONT color="#0000FF">Setting it to True</FONT> automatically decompresses responses into readable JSON, allowing users to handle compressed responses without changing existing calls unless needed.</P><P><FONT color="#FF00FF">Here's an example script:</FONT></P><pre class="lia-code-sample language-python"><code>authorization_token = RestClient.GetBasicAuthenticationHeader("YMUTHAIAH", "XXXXXXXXXXXXXXX") headers = { "Authorization": authorization_token} token_url = "https://XXXXXXXXX.de1.demo.crm.cloud.sap/sap/c4c/api/v1/iam-service/token" token = RestClient.Get(token_url,headers ) aa = token.value.access_token url = "https://XXXXXXXXX.de1.demo.crm.cloud.sap/sap/c4c/api/v1/document-service/documents/" headers1 = { "Authorization": "Bearer " + str(aa), 'Accept': 'application/json; charset=UTF-8'} json_body = { 'isSelected':'false', 'isDisplayDocument':'true', 'fileName': 'CPQ-Document.xlsx', 'category': 'DOCUMENT', 'type': '10001' } newdata=JsonHelper.Deserialize(JsonHelper.Serialize(json_body)) response = RestClient.Post(url, newdata, headers1, True)</code></pre><P>&nbsp;</P><H3 id="toc-hId-1106724256">Script Fix for <SPAN class="">Quote</SPAN> 2.0 Alternatives</H3><P class="">The IronPython script issue in <SPAN class="">Quote</SPAN> 2.0, where newly added alternative items were incorrectly flagged and excluded from calculations, has been resolved.</P><P class="">This fix ensures accurate total and product type calculations, making it essential for users who rely on scripting to manage quote alternatives efficiently.</P><P class=""><FONT color="#FF00FF">Here's an example script:</FONT></P><pre class="lia-code-sample language-python"><code>quote = QuoteHelper.Get("00021088") item = quote.GetItemByItemNumber(1) altProduct = ProductHelper.CreateProduct('00021088',item.QuoteItem) alternativeItem = quote.AddItem(altProduct,1).AsMainItem alternativeItem.ChangeItemTypeToAlternative(item.Id) </code></pre><P>&nbsp;</P> 2025-10-26T14:38:16.450000+01:00 https://community.sap.com/t5/technology-blog-posts-by-members/from-sap-datasphere-to-a-local-llm-llama-3-1-hands-on-tutorial/ba-p/14253357 From SAP Datasphere to a Local LLM (Llama 3.1) — Hands-On Tutorial 2025-10-29T12:05:30.405000+01:00 SethiR https://community.sap.com/t5/user/viewprofilepage/user-id/1792324 <P><FONT size="6"><BR /></FONT><STRONG><FONT size="5">Introduction&nbsp;</FONT></STRONG></P><P>This post documents a small, reproducible pattern for bringing SAP Datasphere data to a local large language model (LLM) for lightweight analysis. The goal is simple: keep modeling and governance in Datasphere, pull a view into a Jupyter notebook with pandas, and let a local LLM produce machine-readable JSON that you can filter, join, or visualize. The prototype runs on a CPU-only laptop so anyone can follow along without special hardware.</P><P><STRONG>What this is</STRONG>:<BR />-&nbsp;A step-by-step walkthrough that uses hdbcli to query a Datasphere view and Transformers to run Meta Llama 3.1 locally.<BR />-&nbsp;A row-by-row prompt pattern that returns compact JSON (total, average, flags) you can plug back into your DataFrame. A row-by-row prompt pattern that returns compact JSON (total, average, flags) you can plug back into your DataFrame.<BR />-&nbsp;A row-by-row prompt pattern that returns compact JSON (total, average, flags) you can plug back into your DataFrame.<BR /><BR /><STRONG>What this is not:</STRONG><BR />-&nbsp;A benchmarking or performance guide. CPU runs are slow but convenient for learning.<BR />-&nbsp;A recommendation to hard-code credentials. The POC mirrors the original notebook for clarity; use environment variables or a vault in real projects.A recommendation to hard-code credentials. The POC mirrors the original notebook for clarity; use environment variables or a vault in real projects.<BR />-&nbsp;A pattern for sending sensitive data to external endpoints. Use test or masked data if you move past local inference. A pattern for sending sensitive data to external endpoints. Use test or masked data if you move past local inference.<BR /><BR /><STRONG>What you will build:</STRONG></P><P>A compact flow: SAP Datasphere View -&gt; Python/Jupyter (hdbcli + pandas) -&gt; row-level prompt -&gt; Local LLM (Transformers) -&gt; JSON back to DataFrame. The same prompts can be pointed to managed inference later for production.<BR /><BR /></P><H2 id="toc-hId-1763694472">Prerequisites</H2><P>1.&nbsp;SAP Datasphere space with permission to create a Database User and a SQL View<BR />2.&nbsp;Python 3.10+ with Jupyter<BR />3.&nbsp;Libraries: pandas, hdbcli, transformers, torch, accelerate, ipython<BR />4.&nbsp;Hugging Face account + access token (accept access for the Llama 3.1 model)<BR /><BR />Security note: The POC code below uses inline credentials to mirror the original run. In real work, put secrets in environment variables or a vault and keep TLS validation enabled.<BR /><BR /></P><P><STRONG><FONT size="5">Part A — SAP Datasphere</FONT></STRONG></P><P>A1. Enable database access for the space</P><P>1.&nbsp;Open SAP Datasphere -&gt; Spaces -&gt; select your space.<BR />2.&nbsp;Go to Database Access and confirm SQL access is enabled for the space&nbsp;</P><P>A2. Create a Database User</P><P>1.&nbsp;Database Access -&gt; Database Users -&gt; Create.Database Access -&gt; Database Users -&gt; Create.<BR />2.&nbsp;Grant only read privileges/necessary privileges to the schema/view you will query.<BR />3.&nbsp;Copy the SQL Endpoint (host) and port 443 for Python connectivity. Make sure you copy password and host details and store it in a safe place.&nbsp;<BR /><BR />A3. Create a demo view with a few rows</P><P>Create a SQL View (or graphical view). Here we have taken&nbsp; "ACN_DWC"."DemoView_SETHIR_PY" with columns:<BR />Stud_ID, Stud_Fname, Stud_Lname, Stud_DOB, Maths, Physics, Chemistry, Total, Stud_Addr, Stud_Faname for the demo.<BR />Make sure the view is exposed for Consumption.<BR />Optional seed SQL you can adapt:<BR /><BR /></P><pre class="lia-code-sample language-sql"><code>SELECT * FROM ( SELECT 101 AS Stud_ID, 'Arjun' AS Stud_Fname, 'Singh' AS Stud_Lname, DATE'2012-04-06' AS Stud_DOB, 80 AS Maths, 75 AS Physics, 80 AS Chemistry, 235 AS Total, 'Chandigarh' AS Stud_Addr, 'Sukhdev' AS Stud_Faname UNION ALL SELECT 102, 'Harpreet', 'Kaur', DATE'2010-09-08', 85, 78, 85, 248, 'Ludhiana', 'Gurinder' UNION ALL SELECT 103, 'Gursimran','Gill', DATE'2011-07-09', 95, 85, 90, 270, 'Amritsar', 'Balwinder' UNION ALL SELECT 104, 'Manpreet', 'Sidhu', DATE'2014-01-01', 90, 85, 95, 270, 'Patiala', 'Harjit' UNION ALL SELECT 105, 'Jasleen', 'Dhillon',DATE'2012-03-02', 89, 90, 75, 254, 'Jalandhar', 'Paramjit' ) AS t; SELECT * FROM ( SELECT 101 AS Stud_ID, 'Arjun' AS Stud_Fname, 'Singh' AS Stud_Lname, DATE'2012-04-06' AS Stud_DOB, 80 AS Maths, 75 AS Physics, 80 AS Chemistry, 235 AS Total, 'Chandigarh' AS Stud_Addr, 'Sukhdev' AS Stud_Faname UNION ALL SELECT 102, 'Harpreet', 'Kaur', DATE'2010-09-08', 85, 78, 85, 248, 'Ludhiana', 'Gurinder' UNION ALL SELECT 103, 'Gursimran','Gill', DATE'2011-07-09', 95, 85, 90, 270, 'Amritsar', 'Balwinder' UNION ALL SELECT 104, 'Manpreet', 'Sidhu', DATE'2014-01-01', 90, 85, 95, 270, 'Patiala', 'Harjit' UNION ALL SELECT 105, 'Jasleen', 'Dhillon',DATE'2012-03-02', 89, 90, 75, 254, 'Jalandhar', 'Paramjit' ) AS t;</code></pre><P>&nbsp;SAP Datasphere DB user :&nbsp;<BR /><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="SAP Datasphere DB user screen" style="width: 521px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/332993i05D97A6906259C90/image-dimensions/521x754?v=v2" width="521" height="754" role="button" title="Screenshot 2025-10-27 170111.png" alt="SAP Datasphere DB user screen" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">SAP Datasphere DB user screen</span></span></P><P>Demo View :</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="SethiR_0-1761564892069.png" style="width: 709px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/332994i6C5415EEA2B13F92/image-dimensions/709x443?v=v2" width="709" height="443" role="button" title="SethiR_0-1761564892069.png" alt="SethiR_0-1761564892069.png" /></span></P><P><STRONG><FONT size="5">Part B — Local environment (quick path)</FONT></STRONG></P><P><FONT size="3">1. Create a project folder and venvCreate a project folder and venv<BR /></FONT></P><pre class="lia-code-sample language-bash"><code>mkdir datasphere-local-llm &amp;&amp; cd datasphere-local-llm python -m venv .venv # Windows: .venv\Scripts\activate # macOS/Linux: source .venv/bin/activate</code></pre><P>2.&nbsp;Install requirements&nbsp;</P><pre class="lia-code-sample language-bash"><code>pip install hdbcli pip install sqlalchemy pip install sqlalchemy-hana pip install pandas pip install hdbcli pip install transformers torch accelerate pip install ipython</code></pre><P>&nbsp;3.&nbsp;Launch Jupyter</P><pre class="lia-code-sample language-bash"><code>jupyter notebook</code></pre><P><STRONG><FONT size="5">Part C — Original POC notebook</FONT></STRONG></P><P><FONT size="3">1. Open a new notebook and write the code.<BR /></FONT></P><pre class="lia-code-sample language-python"><code># --- Imports and setup (original) import pandas as pd from hdbcli import dbapi import warnings from transformers import pipeline from IPython.display import display warnings.filterwarnings('ignore') # --- Inline credentials (POC-style; replace with environment variables for real use) db_user = 'ACN_DWC#SETHIR_DB' db_password = 'secret' db_host = 'secret' db_port = 443 db_schema = 'ACN_DWC' connection = dbapi.connect( address = db_host, port = db_port, user = db_user, password = db_password, encrypt = True, sslValidCertificate = False # POC-only convenience; prefer True in real use ) print("Connected to SAP Datasphere - confirmation")</code></pre><P>2. If you see the output - "Connected to SAP Datasphere - confirmation" -- this implies you are on right track.</P><pre class="lia-code-sample language-python"><code># --- Query the view view_name = 'DemoView_SETHIR_PY' sql_query = f'SELECT * FROM "{db_schema}"."{view_name}"' print(f"Executing query: {sql_query}") cursor = connection.cursor() cursor.execute(sql_query) rows = cursor.fetchall() columns = [desc[0] for desc in cursor.description] df = pd.DataFrame(rows, columns=columns) display(df.head())</code></pre><P>3. At this point, you should see the outcome of the view that you have. The last statement is used for displaying the data in the form of pandas dataFrame.<BR /><BR />4. Now we need to prepare our data , so that the LLM can process it. This can be done by converting our data in the form of string format.</P><pre class="lia-code-sample language-python"><code># ============================================================================== #Prepare Data and Interact with OpenLLM # ============================================================================== # Convert DataFrame to a string data_as_string = df.to_string(index=False) print("\n--- Data Prepared for LLM ---") print("The DataFrame has been converted to the following string format:") print(data_as_string[:300] + "\n...") # snippet preview</code></pre><P>5. Prepare data pipeline :</P><pre class="lia-code-sample language-python"><code># --- LLM imports import os import torch import json from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, pipeline # --- LLM Configuration MODEL_ID = "meta-llama/Meta-Llama-3.1-8B-Instruct" def load_llama_pipeline(): """ Loads the quantized Llama 3 model, tokenizer, and the text-generation pipeline. """ print("\n--- Loading Llama 3 Model ---") hf_token = "secret" # replace with your HF token; accept model access first quantization_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16 ) tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=hf_token) model = AutoModelForCausalLM.from_pretrained( MODEL_ID, token=hf_token, quantization_config=quantization_config, torch_dtype=torch.bfloat16, device_map="auto", ) print("Model loaded. Creating text generation pipeline...") return pipeline( "text-generation", model=model, tokenizer=tokenizer, )</code></pre><P>6. Build the prompt for the LLM model :</P><pre class="lia-code-sample language-python"><code># --- Prompt builder def build_student_analysis_prompt(tokenizer, student_data: pd.Series) -&gt; str: """ Builds a structured prompt for Llama 3 to analyze student marks. """ input_text = ( f"Student {student_data['Stud_Fname']} {student_data['Stud_Lname']} " f"(ID: {student_data['Stud_ID']}) scored {student_data['Maths']} in Maths, " f"{student_data['Physics']} in Physics, and {student_data['Chemistry']} in Chemistry." ) messages = [ { "role": "system", "content": ( "You analyze one student's marks and return ONLY a valid JSON object. " "Output must start with '{' and end with '}'. No commentary, no markdown, no backticks.\n\n" "Schema (keys and types MUST match exactly):\n" "{\n" ' "total_marks": &lt;int&gt;,\n' ' "average_percentage": &lt;float&gt;,\n' ' "is_top_performer": &lt;boolean&gt;\n' "}\n\n" "Rules:\n" "1) total_marks = Maths + Physics + Chemistry (each out of 100).\n" "2) average_percentage = total_marks / 3.\n" "3) Round average_percentage to TWO decimals.\n" "4) is_top_performer = true if average_percentage &gt; 80.0; else false.\n" "5) Use lowercase true/false for booleans.\n" "6) Do not include extra keys. Do not include trailing commas.\n" "7) Return JSON only." ) }, { "role": "user", "content": input_text } ] return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)</code></pre><P>7. Do the analysis :&nbsp;</P><pre class="lia-code-sample language-python"><code># --- Row-by-row analysis def analyze_student_data(df, text_pipeline): """ Analyzes the student DataFrame row by row using the LLM pipeline. """ print("\n--- Starting Student Performance Analysis with Llama 3 ---") results = [] for index, row in df.iterrows(): print(f"\nAnalyzing student ID: {row['Stud_ID']}...") prompt = build_student_analysis_prompt(text_pipeline.tokenizer, row) raw_output = text_pipeline( prompt, max_new_tokens=128, do_sample=False, # deterministic temperature=None, top_p=None, )[0]['generated_text'] json_response_str = raw_output[len(prompt):].strip() try: analysis_result = json.loads(json_response_str) results.append(analysis_result) print("Analysis successful.") except json.JSONDecodeError: print(f" &gt; Failed to decode JSON from model output.") print(f" &gt; Raw model output: {json_response_str}") results.append({"error": "Invalid JSON output", "raw_output": json_response_str}) print("\n--- Analysis Complete ---") return pd.DataFrame(results) # --- Main if __name__ == "__main__": if df is not None and not df.empty: try: llm_pipeline = load_llama_pipeline() analysis_df = analyze_student_data(df, llm_pipeline) if analysis_df is not None: final_df = pd.concat([df.reset_index(drop=True), analysis_df.reset_index(drop=True)], axis=1) print("\n--- Full Data with LLM Analysis ---") display(final_df) print("\n--- Top Performing Students (Average &gt; 80%) ---") top_performers = final_df[final_df['is_top_performer'] == True] if not top_performers.empty: display(top_performers[['Stud_ID', 'Stud_Fname', 'Stud_Lname', 'total_marks', 'average_percentage']]) else: print("No students found with an average greater than 80%.") except Exception as e: print(f"\nAn unexpected error occurred during the analysis process: {e}") else: print("\nDataFrame is empty. Cannot proceed with analysis.")</code></pre><P>8.&nbsp;Example output (simulated for the final cell)<BR /><BR /></P><pre class="lia-code-sample language-markup"><code>Full Data with LLM Analysis (excerpt) Stud_ID Stud_Fname Stud_Lname Maths Physics Chemistry total_marks average_percentage is_top_performer 101 Arjun Singh 80 75 80 235 78.33 false 102 Harpreet Kaur 85 78 85 248 82.67 true 103 Gursimran Gill 95 85 90 270 90.00 true 104 Manpreet Sidhu 90 85 95 270 90.00 true 105 Jasleen Dhillon 89 90 75 254 84.67 true Top Performing Students (Average &gt; 80%) Stud_ID Stud_Fname Stud_Lname total_marks average_percentage 102 Harpreet Kaur 248 82.67 103 Gursimran Gill 270 90.00 104 Manpreet Sidhu 270 90.00 105 Jasleen Dhillon 254 84.67</code></pre><P>&nbsp;</P><P><STRONG><FONT size="5">Part D —&nbsp;Production path (same pattern, managed inference)</FONT></STRONG></P><P><FONT size="3">When performance or scale matters, swap the local pipeline for a hosted endpoint and keep the Datasphere extraction and prompts identical. When performance or scale matters, swap the local pipeline for a hosted endpoint and keep the Datasphere extraction and prompts identical.</FONT></P><P><FONT size="3">1.&nbsp;Databricks Model Serving / Foundation Model Endpoints (REST).<BR />2.&nbsp;Hugging Face Inference Endpoints (private endpoints; REST with your HF token)<BR />3.&nbsp;SAP AI Core (containerized model hosting; REST)SAP AI Core (containerized model hosting; REST)<BR /><BR />Minimal REST skeleton --<BR /></FONT></P><pre class="lia-code-sample language-python"><code>import requests, os, json endpoint = os.getenv("INFERENCE_URL") token = os.getenv("INFERENCE_TOKEN") r = requests.post(endpoint, headers={"Authorization": f"Bearer {token}"}, json={"inputs": "your_prompt_here", "parameters": {"max_new_tokens": 128, "temperature": 0.0}}) print(r.json())</code></pre><P><FONT size="3"><BR /><BR /><FONT size="5">Conclusion ---<BR /></FONT></FONT></P><P>This walkthrough demonstrated how to extract data from SAP Datasphere, process it in pandas, and apply a local LLM for row-level analysis that returns clean, machine-readable JSON. The pattern is intentionally small and portable: you can keep iterating locally to refine prompts and outputs, then swap the model call for a managed endpoint (Databricks, Hugging Face, or SAP AI Core) when performance, cost control, or governance call for it. From here, natural next steps include batching larger datasets, persisting results back to a database table, wiring the outputs to SAP Analytics Cloud dashboards, and adding guardrails around data privacy and prompt consistency.</P><P>I would be excited to hear how you adapt this to your solutions. Feel free to reach out to me or comment.</P><P>&nbsp;</P><P>&nbsp;</P> 2025-10-29T12:05:30.405000+01:00 https://community.sap.com/t5/technology-blog-posts-by-members/sap-datasphere-export-data-of-analyticalmodel-via-odata-url-amp-oauth/ba-p/14256993 SAP Datasphere : Export Data of AnalyticalModel via Odata URL & Oauth Client of type Technical User 2025-11-05T07:58:30.289000+01:00 vikasparmar88 https://community.sap.com/t5/user/viewprofilepage/user-id/1528256 <P>In this blog, I have explained how to export data from an analytical model into CSV file securely using an OData URL and a technical user OAuth client.</P><P>Step - 1) Create an Oauth Client with Purpose as Technical User and select required roles. get client ID and secret and save it.&nbsp;<A href="https://help.sap.com/docs/SAP_DATASPHERE/9f804b8efa8043539289f42f372c4862/88b13468fc3c4ebd972bcb8faa6cafbf.html" target="_self" rel="noopener noreferrer">How to Guide</A>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Oauth.png" style="width: 347px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334244iFF68CE80547515B7/image-dimensions/347x368?v=v2" width="347" height="368" role="button" title="Oauth.png" alt="Oauth.png" /></span></P><P>Step - 2) Create Odata_Tech_User_OauthClient.py file with below code&nbsp;</P><P>&nbsp;</P><pre class="lia-code-sample language-python"><code># --- IMPORTS --- import requests # For making HTTP requests to token and OData endpoints import pandas as pd # For handling tabular data from the OData response import os # For file path resolution and environment variable access from dotenv import load_dotenv # For loading credentials from a .env file # --- LOAD ENV VARIABLES --- load_dotenv() # Load environment variables from .env file into the runtime # --- CONFIG: Read credentials and endpoints from environment --- odata_url = os.getenv("ODATA_URL") # OData service endpoint token_url = os.getenv("TOKEN_URL") # OAuth token endpoint client_id = os.getenv("CLIENT_ID") # OAuth client ID client_secret = os.getenv("CLIENT_SECRET") # OAuth client secret # --- GET TOKEN: Request access token using client credentials --- token_payload = { "grant_type": "client_credentials", # OAuth flow type "client_id": client_id, # Injected from .env "client_secret": client_secret # Injected from .env } token_resp = requests.post(token_url, data=token_payload) # POST request to token endpoint token_resp.raise_for_status() # Raise error if token request fails access_token = token_resp.json()["access_token"] # Extract access token from response # --- CALL ODATA SERVICE: Fetch data using bearer token --- headers = {"Authorization": f"Bearer {access_token}"} # Auth header with token response = requests.get(odata_url, headers=headers) # GET request to OData endpoint response.raise_for_status() # Raise error if data fetch fails # --- PARSE RESULTS: Convert JSON payload to DataFrame --- data = response.json()["value"] # Extract 'value' list from OData response df = pd.DataFrame(data) # Convert list of records to pandas DataFrame # --- DISPLAY OR EXPORT: Show preview and optionally save to CSV --- print() print("🔍 Displaying top rows for quick inspection:") print() print(df.head()) # Display top rows for quick inspection print() # --- Extract view name from OData URL --- view_name = odata_url.rstrip("/").split("/")[-1] # Gets 'AM_EXPORT' from the URL # --- Construct filename --- filename = f"{view_name}.csv" # --- Display and save --- df.to_csv(filename, index=False) print(f"📁 Exported data Saved to: {os.path.abspath(filename)}")</code></pre><P>&nbsp;</P><P>Step - 3) Create .env file with all values of variables.</P><P>ODATA_URL : Copy the Odata link from analytical model</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="odata.png" style="width: 630px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334255i61C52D8A5777AD69/image-dimensions/630x162?v=v2" width="630" height="162" role="button" title="odata.png" alt="odata.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="odata.png" style="width: 517px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334257iF036F0A98768B92D/image-dimensions/517x297?v=v2" width="517" height="297" role="button" title="odata.png" alt="odata.png" /></span></P><P>&nbsp;</P><P>TOKEN_URL : get it from Datasphere -&gt; System -&gt; App Integration page</P><P>CLIENT ID &amp; Secret : Get it from Step-1</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Variables.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334246iBF8AA617FEDF18B0/image-size/large?v=v2&amp;px=999" role="button" title="Variables.png" alt="Variables.png" /></span></P><P>Step-&nbsp; 4) Create .bat file with blow code&nbsp;</P><P>&nbsp;</P><pre class="lia-code-sample language-python"><code> off chcp 65001 &gt;nul REM ─────────────────────────────────────────────── REM 🚀 ODATA TECH USER OAUTH CLIENT EXECUTION SCRIPT REM ─────────────────────────────────────────────── echo. echo ============================================== echo 🔄 Starting OData export process... echo ============================================== REM --- Navigate to script directory --- cd /d "%~dp0" REM --- Run the Python script --- echo 🐍 Running Python script: Odata_Tech_User_OauthClient.py python Odata_Tech_User_OauthClient.py REM --- Completion message --- echo. echo ✅ Script execution completed. echo ============================================== REM --- Keep window open --- pause</code></pre><P>&nbsp;</P><P>Keep all files in same directory</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Folder.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334250i25167CB60103AFB0/image-size/large?v=v2&amp;px=999" role="button" title="Folder.png" alt="Folder.png" /></span></P><P><!-- StartFragment --></P><P>Once everything is set up, just double-click the .bat&nbsp;file to run the process. It will execute the Python script and, once finished, generate a .csv file name exactly same name as the analytical model name. As part of the execution, the first five rows of data will also be displayed on screen for quick preview</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="output.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334261i1BE9F41A80C0F984/image-size/large?v=v2&amp;px=999" role="button" title="output.png" alt="output.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Folder.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/334248iA75EC24BCB3F6067/image-size/large?v=v2&amp;px=999" role="button" title="Folder.png" alt="Folder.png" /></span></P><P>Thanks</P><P>Vikas Parmar</P><P>&nbsp;</P> 2025-11-05T07:58:30.289000+01:00 https://community.sap.com/t5/sap-learning-blog-posts/want-to-explore-developing-ai-workflows-with-the-python-machine-learning/ba-p/14263178 Want to explore developing AI workflows with the Python Machine Learning Client for SAP HANA? 2025-11-07T16:40:45.744000+01:00 Margit_Wagner https://community.sap.com/t5/user/viewprofilepage/user-id/491 <P data-unlink="true"><FONT size="3"><SPAN>I&nbsp;recommend to access our&nbsp;</SPAN></FONT><A title="Developing AI workflows with the Python Machine Learning Client for SAP HANA" href="https://learning.sap.com/learning-journeys/developing-ai-workflows-with-the-python-machine-learning-client-for-sap-hana" target="_blank" rel="noopener noreferrer">Developing AI workflows with the Python Machine Learning Client for SAP HANA</A>&nbsp; learning journey.<BR /><BR /><STRONG>Leaning Objectives</STRONG><BR />By the end of this learning journey, learners will be able to:</P><UL><LI>Know how to install and configure the Python Machine Learning Client (hana-ml) and connect securely to SAP HANA Cloud.</LI><LI>Apply HANA DataFrames to access, prepare, and explore SAP HANA data for machine learning tasks.</LI><LI>Think critically to build, train, and deploy machine learning models using PAL functions for regression, classification, and time-series forecasting.</LI><LI>Impact business outcomes by using real-world datasets (e.g., housing prices, employee churn) to complete end-to-end ML workflows.</LI><LI>Evaluate models using appropriate metrics to assess performance, robustness, and business relevance.</LI></UL><P data-unlink="true"><STRONG>Goals</STRONG></P><UL><LI><SPAN>Get started with SAP: Building the basics</SPAN></LI><LI>Develop your expertise: Skill deepening learning</LI><LI>Excel in your expertise: Advanced specialization learning</LI></UL><P><STRONG>Prerequisites</STRONG></P><UL><LI><DIV class=""><DIV class=""><DIV><P>Basic python expertise</P></DIV></DIV></DIV></LI></UL><P><STRONG>Please post you question related&nbsp;to the digital learning Journey in the&nbsp;</STRONG><A href="https://groups.community.sap.com/t5/sap-learning-q-a/qa-p/learningqanda-board" target="_blank" rel="noopener noreferrer"><STRONG>Q&amp;A area</STRONG></A><STRONG>.&nbsp;</STRONG></P><DIV class=""><DIV class=""><DIV class=""><P>Our SAP Learning Experts will get back to you as soon as possible!&nbsp;<BR />We are here to support you.</P><DIV class=""><DIV class=""><DIV class=""><BR />I appreciate your feedback and we will make sure to continue sharing interesting topics.<BR /><BR />Kind regards<BR />Margit</DIV></DIV></DIV></DIV></DIV></DIV> 2025-11-07T16:40:45.744000+01:00 https://community.sap.com/t5/technology-blog-posts-by-sap/how-to-reduce-prompt-token-costs-using-toon-save-money/ba-p/14267265 How to Reduce Prompt Token Costs Using Toon = Save Money 2025-11-12T17:34:51.069000+01:00 Yogananda https://community.sap.com/t5/user/viewprofilepage/user-id/75 <P>As you already might know prompt tokens are the backbone of communication with large language models (LLMs). However, as usage scales, token costs can quickly become a significant expense. If you’re using <STRONG>Toon</STRONG>—a tool designed for optimizing prompt workflows—you can dramatically cut down on these costs without sacrificing performance.</P><H3 id="toc-hId-1893818944"><STRONG>Why Token Costs Matter</STRONG></H3><P>Every interaction with an LLM consumes tokens. These tokens represent:</P><UL><LI><STRONG>Input tokens</STRONG>: The text you send to the model.</LI><LI><STRONG>Output tokens</STRONG>: The text generated by the model.</LI></UL><P>The more tokens you use, the higher your bill. For businesses running thousands of prompts daily, even small inefficiencies can lead to big costs.</P><P><FONT color="#FF00FF"><STRONG>Example:</STRONG></FONT></P><UL><LI>Original prompt: <EM>“Please summarize the following text in a clear and concise manner, highlighting the key points and provide a RACI Matrix for S/4 HANA”</EM></LI><LI>Compressed prompt: <EM>“Summarize key points.”</EM></LI></UL><H3 id="toc-hId-1697305439"><FONT color="#800080"><STRONG>What is TOON?</STRONG></FONT></H3><P>TOON is a <STRONG>compact, human-readable serialization format</STRONG> designed for passing structured data to LLMs with <STRONG>significantly reduced token usage</STRONG>. It acts as a <STRONG>lossless, drop-in representation of JSON</STRONG>, optimized for token efficiency.</P><H3 id="toc-hId-1500791934"><FONT color="#800080"><STRONG>Why Use TOON?</STRONG></FONT></H3><UL><LI><STRONG>Token-efficient</STRONG>: Saves <STRONG>30–60% tokens</STRONG> compared to formatted JSON for large uniform arrays.</LI><LI><STRONG>LLM-friendly</STRONG>: Explicit lengths and fields improve parsing and validation.</LI><LI><STRONG>Minimal syntax</STRONG>: Removes redundant punctuation (braces, quotes).</LI><LI><STRONG>Tabular arrays</STRONG>: Declare keys once, stream data as rows.</LI><LI><STRONG>Optional key folding</STRONG>: Collapses nested chains into dotted paths for fewer tokens.</LI></UL><H3 id="toc-hId-1304278429"><STRONG>Benchmarks</STRONG></H3><UL><LI>TOON uses <STRONG>39.6% fewer tokens</STRONG> than JSON while improving retrieval accuracy (73.9% vs 69.7%).</LI><LI>For uniform tabular data, TOON is slightly larger than CSV (+6%) but far smaller than JSON (-58%).</LI></UL><H3 id="toc-hId-1107764924"><STRONG>When NOT to Use TOON</STRONG></H3><UL><LI>Deeply nested or non-uniform structures → JSON may be better.</LI><LI>Pure tabular data → CSV is smaller.</LI><LI>Latency-critical apps → Benchmark first; compact JSON might be faster.</LI></UL><P><STRONG>How to Use TOON</STRONG></P><pre class="lia-code-sample language-abap"><code>NPM Lib npm install -format/toon Python https://github.com/xaviviro/python-toon</code></pre><P>&nbsp;<A href="https://github.com/toon-format/toon" target="_blank" rel="noopener nofollow noreferrer">https://github.com/toon-format/toon</A>&nbsp;</P><P><A href="https://github.com/toon-format/spec" target="_blank" rel="noopener nofollow noreferrer">https://github.com/toon-format/spec</A>&nbsp;</P><H3 id="toc-hId-911251419"><STRONG>Why TOON Matters while your working for different SAP Product LoBs</STRONG></H3><OL><LI><P><STRONG>Token Cost Reduction</STRONG></P><UL><LI>SAP S/4 HANA systems handle large datasets (e.g., thousands of line items in a purchase order).</LI><LI>JSON representation of these datasets is expensive in token terms.</LI><LI>TOON compresses this data by <STRONG>30–60%</STRONG>, reducing LLM API costs significantly.</LI></UL></LI><LI><P><STRONG>Performance Gains</STRONG></P><UL><LI>Smaller prompts mean <STRONG>lower latency</STRONG> and <STRONG>faster response times</STRONG>.</LI><LI>This is critical for real-time SAP applications like <STRONG>Joule for procurement</STRONG> or <STRONG>HR assistants</STRONG>.</LI></UL></LI><LI><P><STRONG>Improved Accuracy</STRONG></P><UL><LI>TOON’s explicit structure (e.g., tabular arrays with declared keys) helps LLMs parse data better.</LI><LI>This reduces hallucinations in SAP workflows like <STRONG>financial reconciliation</STRONG> or <STRONG>compliance checks</STRONG>.</LI></UL></LI></OL><P><STRONG>High Level Flow</STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_0-1762963441825.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/339677iCF14D8DA461534BD/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="Yogananda_0-1762963441825.png" alt="Yogananda_0-1762963441825.png" /></span></P><H2 id="toc-hId-585655195"><STRONG>Integration Approach</STRONG></H2><UL><LI><STRONG>Middleware Layer</STRONG>: Convert SAP OData/JSON responses to TOON before sending to LLM.</LI><LI><STRONG>SAP BTP Extension</STRONG>: Implement TOON conversion in <STRONG>CAP,&nbsp;Cloud Foundry apps</STRONG> or <STRONG>Kyma runtime or SAP Databricks, AI Core Models you have selected</STRONG>.</LI><LI><STRONG>Prompt Wrapping</STRONG>: Always wrap TOON in fenced code blocks for LLM clarity</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="G5qMVMlacAAosFZ.png" style="width: 800px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/340395iDB351B41F1A46C83/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="G5qMVMlacAAosFZ.png" alt="G5qMVMlacAAosFZ.png" /></span></P><H2 id="toc-hId-389141690"><STRONG>Best Practices</STRONG></H2><UL><LI>Use <STRONG>tab-delimited rows</STRONG> for extra token savings.</LI><LI>Benchmark TOON vs JSON for your specific SAP dataset.</LI><LI>Cache static context to avoid repeated token costs.</LI></UL><H2 id="toc-hId-192628185"><STRONG>Conclusion</STRONG></H2><P>TOON offers a simple yet powerful way to reduce LLM costs in your SAP landscape environments. By compressing structured data without losing meaning, SAP teams can achieve:</P><UL><LI><STRONG>Up to 60% cost savings</STRONG></LI><LI><STRONG>Faster response times</STRONG></LI><LI><STRONG>Improved AI accuracy</STRONG></LI></UL><P>As SAP continues its AI journey, adopting TOON can make intelligent automation more scalable and cost-effective.</P> 2025-11-12T17:34:51.069000+01:00 https://community.sap.com/t5/technology-blog-posts-by-members/sap-rpt-1-context-model-vs-training-classical-models-the-models-battle/ba-p/14268507 SAP RPT-1 Context Model vs. Training Classical Models: The Models Battle (Python Hands-on) 2025-11-20T07:50:27.670000+01:00 nicolasestevan https://community.sap.com/t5/user/viewprofilepage/user-id/1198632 <H2 id="toc-hId-1764768715"><span class="lia-unicode-emoji" title=":collision:">💥</span>The Models Battle</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_5-1763206328497.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341535i2A2C9A98D24BF43B/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="nicolasestevan_5-1763206328497.png" alt="nicolasestevan_5-1763206328497.png" /></span></P><P>Predictive modeling is becoming a built-in capability across SAP, improving how teams handle forecasting, pricing, and planning. <STRONG>Many SAP professionals, however, aren’t machine-learning specialists</STRONG>, and traditional models often demand extensive setup, tuning, and repeated training, which slows down new ideas.</P><P><STRONG>SAP RPT-1</STRONG> offers a simpler path. It’s a pretrained model from SAP, also available in an OSS version, that lets developers and consultants produce predictions with far less technical effort, no deep ML background required.</P><P>I've explored SAP RPT-1 hands-on, comparing it with traditional regressors using Python and a real public vehicles price dataset.&nbsp;</P><BLOCKQUOTE><P><STRONG>Goal:</STRONG> To see (as a non Data Scientist) how <STRONG>SAP RPT-1</STRONG> behaves in practice, what advantages and limits it shows, and when it could make sense in a predictive scenario.</P></BLOCKQUOTE><P>Usually for real-world scenario, the right approach would be consume the SAP RPT-1 though the available and simplified API, but for studies proposal and fair comparision over othe traditional ML models, the <STRONG>OSS</STRONG> fits perfectly for it:</P><HR /><H2 id="toc-hId-1568255210"><span class="lia-unicode-emoji" title=":thinking_face:">🤔</span>&nbsp;SAP RPT-1 vs Traditional Machine Learning - Core Differences</H2><P>Before diving into the code, let’s quickly revisit how<STRONG> traditional ML</STRONG> models work:</P><UL><LI>Training-based models like Random Forest, LightGBM, and Linear Regression learn patterns directly from data.&nbsp;</LI><LI>They require hundreds or thousands of examples to tune their internal parameters.</LI><LI>Their performance depends heavily on data quantity and quality.</LI><LI>The more relevant examples they see, the smarter they get.</LI></UL><P>On the other hand, <STRONG>SAP RPT-1 f</STRONG>ollows a different philosophy. It’s part of the RPT (Representational Predictive Transformer) family, pretrained on a wide variety of business and contextual data. This means:</P><UL><LI>You don’t "train" it in the traditional sense. Instead, it uses context embeddings to predict outcomes.</LI><LI>It can be used immediately, even with smaller datasets.</LI><LI>The OSS version allows developers to experiment directly in Python.</LI><LI>No special SAP backend required.</LI></UL><BLOCKQUOTE><P><STRONG>Outcome:</STRONG> Traditional ML models learn from high amount of data. SAP RPT-1 already knows how to deal with small context amount of data.</P></BLOCKQUOTE><HR /><H2 id="toc-hId-1371741705"><span class="lia-unicode-emoji" title=":desktop_computer:">🖥</span>&nbsp;The Experiment - Setup &amp; Dataset&nbsp;</H2><div class="lia-spoiler-container"><a class="lia-spoiler-link" href="#" rel="nofollow noopener noreferrer">Spoiler</a><noscript> (Highlight to read)</noscript><div class="lia-spoiler-border"><div class="lia-spoiler-content">Don't worry on "playing puzzles" copying + pasting code below. The full version is available for download at end!</div><noscript><div class="lia-spoiler-noscript-container"><div class="lia-spoiler-noscript-content">Don't worry on "playing puzzles" copying + pasting code below. The full version is available for download at end!</div></div></noscript></div></div><P>To make this comparison tangible, I built a simple yet realistic Python experiment to predict vehicle selling prices using a public dataset containing car attributes like make, model, year, transmission, and mileage.</P><P>Why vehicle pricing? Because it’s an intuitive example where both traditional machine learning and pretrained AI models can be applie, and it helps visualize how prediction quality evolves as the sample size grows.</P><P>This entire analysis runs on a local Python environment&nbsp;with the following stack:</P><pre class="lia-code-sample language-python"><code>import os import gc import warnings import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import datetime from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score from sklearn.ensemble import RandomForestRegressor from sklearn.preprocessing import LabelEncoder from sklearn.linear_model import LinearRegression from sap_rpt_oss import SAP_RPT_OSS_Regressor import lightgbm as lgb</code></pre><UL><LI><STRONG>pandas</STRONG> and <STRONG>numpy</STRONG> for data manipulation</LI><LI><STRONG>scikit-learn</STRONG> for classical ML regressors (R<STRONG>andom Forest, Linear Regression</STRONG>)</LI><LI><STRONG>LightGBM</STRONG> for gradient <STRONG>boosting</STRONG> comparison</LI><LI><STRONG>sap_rpt_oss</STRONG> — the open-source Python version of <STRONG>SAP’s RPT-1 model</STRONG></LI><LI><STRONG>matplotlib</STRONG> for all <STRONG>visualizations</STRONG></LI></UL><BLOCKQUOTE><P><STRONG>SAP RPT-1 OSS </STRONG>can be downloaded installed following official Hugging Face:&nbsp;<A title="https://huggingface.co/SAP/sap-rpt-1-oss?library=sap-rpt-1-oss" href="https://huggingface.co/SAP/sap-rpt-1-oss?library=sap-rpt-1-oss" target="_blank" rel="noopener nofollow noreferrer">https://huggingface.co/SAP/sap-rpt-1-oss?library=sap-rpt-1-oss</A>&nbsp;. Python can be installed with executable download on Windows, or via <STRONG>Home Brew</STRONG> for Mac and <STRONG>apt</STRONG> commands for Linux. Libraries dependencies can be downloaded with <STRONG>pip</STRONG> commands. Googling it may not be a road blocker.</P></BLOCKQUOTE><P>We use a sample&nbsp;vehicle sales dataset. The complete file is about to 88Mb but for such experiment a restricted sample of 20k as it's more than enough to prove our the concept, still it's faster and consuming less computing resources.</P><DIV class=""><DIV class=""><TABLE border="1" width="498px"><TBODY><TR><TD><STRONG>Feature</STRONG></TD><TD><STRONG>Description</STRONG></TD></TR><TR><TD width="248.57px" height="30px"><CODE>year</CODE></TD><TD width="248.43px" height="30px">Vehicle model year</TD></TR><TR><TD width="248.57px" height="30px"><CODE>make</CODE></TD><TD width="248.43px" height="30px">Brand (e.g., Toyota, Ford, BMW)</TD></TR><TR><TD width="248.57px" height="30px"><CODE>model</CODE></TD><TD width="248.43px" height="30px">Specific model name</TD></TR><TR><TD width="248.57px" height="30px"><CODE>body</CODE></TD><TD width="248.43px" height="30px">Type (SUV, Sedan, etc.)</TD></TR><TR><TD width="248.57px" height="30px"><CODE>transmission</CODE></TD><TD width="248.43px" height="30px">Gear type</TD></TR><TR><TD width="248.57px" height="30px"><CODE>odometer</CODE></TD><TD width="248.43px" height="30px">Vehicle mileage</TD></TR><TR><TD width="248.57px" height="30px"><CODE>color</CODE>, <CODE>interior</CODE></TD><TD width="248.43px" height="30px">Visual attributes</TD></TR><TR><TD width="248.57px" height="30px"><CODE>sellingprice</CODE></TD><TD width="248.43px" height="30px">The target variable to predict</TD></TR></TBODY></TABLE><P><STRONG><span class="lia-unicode-emoji" title=":bar_chart:">📊</span>&nbsp;Dataset Download:</STRONG>&nbsp;<A title="https://www.kaggle.com/datasets/syedanwarafridi/vehicle-sales-data?resource=download" href="https://www.kaggle.com/datasets/syedanwarafridi/vehicle-sales-data?resource=download" target="_blank" rel="noopener nofollow noreferrer">https://www.kaggle.com/datasets/syedanwarafridi/vehicle-sales-data?resource=download</A>&nbsp;</P><P>The dataset is loaded and preprocessed in a few simple steps:</P></DIV></DIV><pre class="lia-code-sample language-python"><code>df = pd.read_csv("car_prices.csv").sample(n=20000, random_state=42) # Fill missing values for categorical columns fill_defaults = { 'make': 'Other', 'model': 'Other', 'color': 'Other', 'interior': 'Unknown', 'body': 'Unknown', 'transmission': 'Unknown' } for col, val in fill_defaults.items(): df[col] = df[col].fillna(val) X = df[["year", "make", "model", "body", "transmission", "odometer", "color", "interior"]] y = df["sellingprice"]</code></pre><P>At this point, the stage is set:</P><UL><LI>The data is clean.</LI><LI>The environment is ready.</LI><LI>All models, traditionals and SAP RPT-1, are ready to be tested under identical conditions.</LI></UL><HR /><H2 id="toc-hId-1175228200"><span class="lia-unicode-emoji" title=":robot_face:">🤖</span>&nbsp;Training the Models - Three different ones</H2><P>With the dataset ready, the <STRONG>next step</STRONG> is to run each model under the same conditions: <STRONG>same features, same target, same train/test split and same random seed</STRONG>. This ensures the comparison is fair and repeatable.</P><P>We evaluate prediction performance using <STRONG>R² (coefficient of determination)</STRONG>, which indicates how much of the price variation the model can explain (1.0 = perfect prediction).</P><HR /><H3 id="toc-hId-1107797414">Training Model #1 - Random Forest</H3><P>Random Forest is often the first model used in tabular ML. It works by creating <STRONG>many decision trees</STRONG> and averaging their predictions. Before training, categorical variables need to be <STRONG>label-encoded</STRONG> into numbers, a common requirement for classical ML models:</P><pre class="lia-code-sample language-python"><code>def train_random_forest(X, y): X = X.copy() cat_cols = ["make", "model", "body", "transmission", "color", "interior"] le = LabelEncoder() for col in cat_cols: X[col] = le.fit_transform(X[col].astype(str).fillna("Unknown")) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=default_test_size, random_state=42 ) model = RandomForestRegressor( n_estimators=150, max_depth=20, random_state=42, n_jobs=-1 ) try: model.fit(X_train, y_train) preds = model.predict(X_test) r2 = r2_score(y_test, preds) except Exception as e: preds, r2 = np.zeros_like(y_test), 0 return [preds, r2, y_test]</code></pre><H3 id="toc-hId-911283909">Up to 50 rows:</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_3-1763206176248.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341502i82216AA724092E03/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_3-1763206176248.png" alt="nicolasestevan_3-1763206176248.png" /></span></P><H3 id="toc-hId-714770404">Up to 7067 rows:</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_8-1763206511155.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341538iF2A25E0C0EBE0612/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_8-1763206511155.png" alt="nicolasestevan_8-1763206511155.png" /></span></P><H3 id="toc-hId-518256899">Live view</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="RandomForest_20251115_092355.gif" style="width: 960px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341551i3A2C874AFAF47388/image-size/large?v=v2&amp;px=999" role="button" title="RandomForest_20251115_092355.gif" alt="RandomForest_20251115_092355.gif" /></span></P><P>&nbsp;</P><HR /><H3 id="toc-hId-321743394">Training Model #2 - LightGBM</H3><P>LightGBM is one of the most powerful models for tabular data. Unlike Random Forest (many independent trees), LightGBM builds trees <STRONG>sequentially</STRONG>, each correcting the errors of the previous one. It supports categorical features natively, which simplifies preprocessing.</P><pre class="lia-code-sample language-python"><code>def train_lightgbm(X, y): X = X.copy() cat_cols = ["make", "model", "body", "transmission", "color", "interior"] for col in cat_cols: X[col] = X[col].astype(str).fillna("Unknown").astype("category") X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=default_test_size, random_state=42 ) model = lgb.LGBMRegressor( n_estimators=500, learning_rate=0.05, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 ) try: model.fit(X_train, y_train, categorical_feature=cat_cols) preds = model.predict(X_test) r2 = r2_score(y_test, preds) except Exception: preds, r2 = np.zeros_like(y_test), 0 return [preds, r2, y_test]</code></pre><H3 id="toc-hId-125229889">Up to 50 rows:</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_2-1763205951324.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341474i1AAB214E2D01C2B2/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_2-1763205951324.png" alt="nicolasestevan_2-1763205951324.png" /></span></P><H3 id="toc-hId--146514985">Up to 7067 rows:</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_7-1763206474860.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341537i0ACD453B96C87ADF/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_7-1763206474860.png" alt="nicolasestevan_7-1763206474860.png" /></span></P><H3 id="toc-hId--343028490">Live view</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="LightGBM_20251115_092355.gif" style="width: 960px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341552i30BC4DE94C4988F6/image-size/large?v=v2&amp;px=999" role="button" title="LightGBM_20251115_092355.gif" alt="LightGBM_20251115_092355.gif" /></span></P><HR /><H3 id="toc-hId--539541995">Training Model #3 - Linear Regression</H3><P>Not fancy and even not complex, Linear Regression provides a baseline that shows:&nbsp;<SPAN>“If the relationship between attributes and price is roughly linear, how well can a simple model perform?”</SPAN></P><pre class="lia-code-sample language-python"><code>def train_linear_model(X, y): X = X.copy() cat_cols = ["make", "model", "body", "transmission", "color", "interior"] for col in cat_cols: X[col] = LabelEncoder().fit_transform(X[col].astype(str).fillna("Unknown")) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=default_test_size, random_state=42 ) model = LinearRegression() X_train = X_train.fillna(X_train.mean(numeric_only=True)) X_test = X_test.fillna(X_test.mean(numeric_only=True)) try: model.fit(X_train, y_train) preds = model.predict(X_test) r2 = r2_score(y_test, preds) except Exception: preds, r2 = np.zeros_like(y_test), 0 return [preds, r2, y_test]</code></pre><H3 id="toc-hId--736055500"><STRONG>Up to 50 rows:</STRONG></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_1-1763205857765.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341472i81AFB2D0BE770F90/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_1-1763205857765.png" alt="nicolasestevan_1-1763205857765.png" /></span></P><H3 id="toc-hId--932569005"><STRONG>Up to 7067 rows:</STRONG></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_6-1763206428099.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341536iC708165AEAE11D46/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_6-1763206428099.png" alt="nicolasestevan_6-1763206428099.png" /></span></P><H3 id="toc-hId--1129082510">Live view</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="LinearModel_20251115_092355.gif" style="width: 960px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341553i0849B4C842A417EE/image-size/large?v=v2&amp;px=999" role="button" title="LinearModel_20251115_092355.gif" alt="LinearModel_20251115_092355.gif" /></span></P><H2 id="toc-hId--1032193008"><span class="lia-unicode-emoji" title=":chequered_flag:">🏁</span>&nbsp;<SPAN>SAP RPT-1 OSS: Context Model</SPAN></H2><P>This is where things get interesting. SAP RPT-1 does <STRONG>not</STRONG> rely on learning patterns from the dataset. Instead, it uses a pretrained transformer architecture to infer relationships directly through <STRONG>context embeddings</STRONG>. Lean and simple, "for non-Data Science PhD":</P><pre class="lia-code-sample language-python"><code>def train_sap_rpt1(X, y): X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=default_test_size, random_state=42 ) model = SAP_RPT_OSS_Regressor(max_context_size=8192, bagging=8) model.fit(X_train, y_train) preds = model.predict(X_test) r2 = r2_score(y_test, preds) return [preds, r2, y_test]</code></pre><H3 id="toc-hId--1522109520"><STRONG>Up to 50 rows:</STRONG></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_0-1763205729558.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341471i4AC7007DCA5A0F76/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_0-1763205729558.png" alt="nicolasestevan_0-1763205729558.png" /></span></P><H3 id="toc-hId--1718623025"><STRONG>Up to 2055 rows:</STRONG></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="nicolasestevan_4-1763206228416.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341505i9ADE9D2D2B38C363/image-size/large?v=v2&amp;px=999" role="button" title="nicolasestevan_4-1763206228416.png" alt="nicolasestevan_4-1763206228416.png" /></span></P><H3 id="toc-hId--1915136530">Live view</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="SAP_RPT1_20251115_092355.gif" style="width: 960px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/341566i0BE0E0D666836951/image-size/large?v=v2&amp;px=999" role="button" title="SAP_RPT1_20251115_092355.gif" alt="SAP_RPT1_20251115_092355.gif" /></span></P><P>&nbsp;</P><HR /><H2 id="toc-hId--1650063337"><STRONG><span class="lia-unicode-emoji" title=":magnifying_glass_tilted_right:">🔎</span>&nbsp;Running Experiments at Multiple Sample Sizes</STRONG></H2><P>This section breaks down how the iterative experiment loop works, why the SAP RPT-1 OSS model has a max-context limit, and how performance changes as we scale up the dataset. By running the same models across several sample sizes, we can see where traditional ML shines, where RPT-1 stays competitive, and how both behave as the data grows.</P><pre class="lia-code-sample language-python"><code>sample_sizes = np.linspace(50, len(X), 200, dtype=int) results, max_r2_rpt1, max_sample_rpt1 = [], 0, 0 for n in sample_sizes: idx = np.random.choice(len(X), n, replace=False) X_sample, y_sample = X.iloc[idx], y.iloc[idx] # SAP RPT-1 OSS (limited sample size) if n &lt;= rpt1_limit: rpt_res = train_sap_rpt1(X_sample, y_sample) fn = plot_predictions(rpt_res[2], rpt_res[0], rpt_res[1], "SAP_RPT1", n) video_frames["SAP_RPT1"].append(fn) r2_rpt1 = rpt_res[1] max_r2_rpt1 = max(max_r2_rpt1, r2_rpt1) else: r2_rpt1 = max_r2_rpt1 if max_sample_rpt1 == 0: max_sample_rpt1 = n # Train and plot models rf_res = train_random_forest(X_sample, y_sample) fn = plot_predictions(rf_res[2], rf_res[0], rf_res[1], "RandomForest", n) video_frames["RandomForest"].append(fn) lgb_res = train_lightgbm(X_sample, y_sample) fn = plot_predictions(lgb_res[2], lgb_res[0], lgb_res[1], "LightGBM", n) video_frames["LightGBM"].append(fn) lin_res = train_linear_model(X_sample, y_sample) fn = plot_predictions(lin_res[2], lin_res[0], lin_res[1], "LinearModel", n) video_frames["LinearModel"].append(fn) results.append((n, rf_res[1], r2_rpt1, lgb_res[1], lin_res[1])) # Early stop if traditional model reaches SAP RPT-1 if rf_res[1] &gt;= max_r2_rpt1 or lgb_res[1] &gt;= max_r2_rpt1 or lin_res[1] &gt;= max_r2_rpt1: break gc.collect()</code></pre><P>This loop compares SAP RPT-1 OSS with traditional ML models as sample sizes increase. Each iteration randomly selects a subset of the data and trains all models on the same slice for a fair comparison. SAP RPT-1 can only run up to its max-context limit, so once the sample size exceeds that threshold, it stops retraining and simply carries forward its best R². The traditional models continue training at every step. The loop ends early when any traditional model matches or surpasses RPT-1’s best score, making the experiment efficient while showing how performance evolves as data grows.</P><HR /><H2 id="toc-hId--1846576842"><STRONG><span class="lia-unicode-emoji" title=":end_arrow:">🔚</span>&nbsp;Conclusion and Final Thoughts</STRONG></H2><P>&nbsp;SAP RPT-1 OSS stands out because it performs well with small datasets, requires minimal code, and can generate useful predictions with just an API call and a bit of context. This makes it ideal for jump-starting predictive use cases early on, delivering fast business value without a full ML pipeline. Traditional models, however, still shine when projects mature, data grows, and fine-tuned control becomes important. It’s not about choosing one over the other, but understanding where each approach brings the most value.</P><TABLE border="1" width="100%"><TBODY><TR><TD><STRONG>&nbsp;</STRONG><STRONG>Aspect&nbsp;</STRONG></TD><TD><STRONG>SAP RPT-1 OSS&nbsp;</STRONG></TD><TD><STRONG>Traditional ML (RF, LGBM, Linear)</STRONG></TD></TR><TR><TD width="19.011815252416756%" height="30px">Data Requirements</TD><TD width="38.66809881847476%" height="30px">Low (performs well with small samples)</TD><TD width="42.21267454350161%" height="30px">Medium/High (performance scales with data</TD></TR><TR><TD width="19.011815252416756%" height="30px">Setup Effort</TD><TD width="38.66809881847476%" height="30px">Minimal (API call + context)</TD><TD width="42.21267454350161%" height="30px">Higher (preprocessing, encoding, tuning)</TD></TR><TR><TD width="19.011815252416756%" height="30px">Training Process</TD><TD width="38.66809881847476%" height="30px">None (pretrained context model)</TD><TD width="42.21267454350161%" height="30px">Full training pipeline required</TD></TR><TR><TD width="19.011815252416756%" height="30px">Speed to Insights</TD><TD width="38.66809881847476%" height="30px">Very fast</TD><TD width="42.21267454350161%" height="30px">Moderate to slow</TD></TR><TR><TD width="19.011815252416756%" height="30px">Best Use Case</TD><TD width="38.66809881847476%" height="30px">Early-stage predictive cases, quick baselines</TD><TD width="42.21267454350161%" height="30px">Mature pipelines, high control and customization</TD></TR><TR><TD width="19.011815252416756%" height="30px">Flexibility</TD><TD width="38.66809881847476%" height="30px">Limited tuning / plug-and-play</TD><TD width="42.21267454350161%" height="30px">Highly customizable</TD></TR><TR><TD width="19.011815252416756%" height="30px">Business Value</TD><TD width="38.66809881847476%" height="30px">Immediate, fast, accessible</TD><TD width="42.21267454350161%" height="30px">Strong when optimized and scaled</TD></TR></TBODY></TABLE><P>This experiment highlights a simple truth: <STRONG>SAP RPT-1 isn’t here to replace traditional ML, it jump-starts it.&nbsp;</STRONG>With a pretrained, context-driven approach, RPT-1 delivers fast, reliable insights with very little data and almost no setup. Traditional models still excel in mature, data-rich scenarios, but RPT-1 shines as a rapid accelerator and early-value generator inside SAP landscapes.</P><HR /><H3 id="toc-hId-1958473942"><STRONG><span class="lia-unicode-emoji" title=":speech_balloon:">💬</span>Open for Exchange</STRONG></H3><P>If you're testing RPT-1, exploring predictive cases, or want the full code, feel free to reach out.<BR /><STRONG>Happy to connect, compare experiences, and push this topic forward together.</STRONG></P> 2025-11-20T07:50:27.670000+01:00 https://community.sap.com/t5/technology-blog-posts-by-sap/querying-rdf-graphs-with-sap-hana-cloud-knowledge-graph-engine/ba-p/14289126 Querying RDF Graphs with SAP HANA Cloud Knowledge Graph Engine 2025-12-12T03:01:55.876000+01:00 jing_wen https://community.sap.com/t5/user/viewprofilepage/user-id/1923466 <P>SAP HANA Cloud’s multi-model platform brings together vector, graph, text, spatial, and relational data natively. It enables developers and data teams to build smarter, more context-aware AI solutions — directly on operational data.</P><P>SAP HANA Cloud uniquely supports:</P><UL><LI><STRONG>Vector data</STRONG>&nbsp;for semantic and similarity search</LI><LI><STRONG>Graph data</STRONG>&nbsp;for explicit relationship modeling and knowledge graphs</LI><LI><STRONG>Text and spatial data</STRONG>&nbsp;for real-world context</LI><LI><STRONG>Relational data</STRONG>&nbsp;for structured operations and analytics</LI></UL><P>Rather than sending data across disparate services, you can store and process all of it in one place, accelerating time-to-value while reducing the risk of misalignment. This is multi-model done right, and it is the foundation for powerful AI workloads that scale.</P><P><SPAN>This blog demonstrates how to <STRONG>query RDF knowledge graphs in SAP HANA Cloud</STRONG>, both:</SPAN></P><UL><LI><SPAN>Directly from the <STRONG>HANA Cloud Central SQL Console</STRONG>, and</SPAN></LI><LI><SPAN>Programmatically using <STRONG>Python</STRONG></SPAN></LI></UL><P><SPAN>We will also show how RDF graphs can be combined seamlessly with <STRONG>vector similarity search, spatial filtering, and SQL analytics</STRONG> to create intelligent, real‑world use cases. The blog expands on this <A href="https://news.sap.com/2025/07/unifying-ai-workloads-sap-hana-cloud-one-database/" target="_self" rel="noopener noreferrer">post</A>.</SPAN></P><H2 id="toc-hId-1766641765"><SPAN>From supplier notes to AI‑ready supply‑chain intelligence</SPAN></H2><P class=""><SPAN>To ground the concepts, we start with a simple but realistic scenario: turning unstructured supplier feedback into AI‑ready supply‑chain intelligence.</SPAN></P><P class=""><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351371i531B6FE2D035385F/image-size/large?v=v2&amp;px=999" role="button" title="1.png" alt="1.png" /></span></SPAN></P><P class=""><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351376i034D72271FF3A2DD/image-size/large?v=v2&amp;px=999" role="button" title="1.1.png" alt="1.1.png" /></span></SPAN></P><P class=""><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351377i7E5A3692E6F81D96/image-size/large?v=v2&amp;px=999" role="button" title="1.2.png" alt="1.2.png" /></span></SPAN></P><H3 id="toc-hId-1699210979"><SPAN>Step 1: Create a supply‑chain schema and table</SPAN></H3><P class=""><SPAN>We first create a dedicated schema and table to store supplier identifiers, short operational reports, vector embeddings, and geographic locations.</SPAN></P><pre class="lia-code-sample language-sql"><code>CREATE SCHEMA KG_SUPPLYCHAIN; CREATE TABLE KG_SUPPLYCHAIN.SUPPLIER_REPORTS_LOOKUP ( supplier_uri NVARCHAR(200), report_text NVARCHAR(1000), report_embedding REAL_VECTOR(768), geo_location ST_POINT(4326) );</code></pre><H3 id="toc-hId-1502697474"><SPAN>Step 2: Insert natural‑language supplier reports</SPAN></H3><P class=""><SPAN>The reports represent real‑world observations such as customs delays or smooth clearance. At this stage, AI embeddings and spatial attributes are not yet populated.</SPAN></P><pre class="lia-code-sample language-sql"><code>INSERT INTO KG_SUPPLYCHAIN.SUPPLIER_REPORTS_LOOKUP VALUES ('http://example.org/supplier/AlphaGmbH', 'Consistently on time. No customs issue.', NULL, NULL), ('http://example.org/supplier/BetaGmbH', 'Severe customs delays reported last month.', NULL, NULL), ('http://example.org/supplier/GammaLogistics', 'Smooth operations, cleared customs quickly.', NULL, NULL);</code></pre><H3 id="toc-hId-1306183969"><SPAN>Step 3: Add geographic context</SPAN></H3><P class=""><SPAN>Each supplier is enriched with a geographic location using WGS84 coordinates. This enables proximity analysis and regional risk assessment.</SPAN></P><pre class="lia-code-sample language-sql"><code>UPDATE KG_SUPPLYCHAIN.SUPPLIER_REPORTS_LOOKUP SET geo_location = ST_GeomFromText('POINT(8.6821 50.1109)', 4326) WHERE supplier_uri = 'http://example.org/supplier/AlphaGmbH';</code></pre><H3 id="toc-hId-1109670464"><SPAN>Step 4: Generate vector embeddings inside the database</SPAN></H3><P class=""><SPAN>SAP HANA Cloud generates vector embeddings directly in‑database using SAP’s native embedding model. This enables <STRONG>semantic understanding</STRONG> of supplier reports.</SPAN></P><pre class="lia-code-sample language-sql"><code>UPDATE KG_SUPPLYCHAIN.SUPPLIER_REPORTS_LOOKUP SET report_embedding = VECTOR_EMBEDDING( report_text, 'QUERY', 'SAP_NEB.20240715' );</code></pre><P><SPAN>At this point, we have a single <STRONG>AI‑ready table</STRONG> that supports:</SPAN></P><UL><LI><SPAN>Semantic similarity search</SPAN></LI><LI><SPAN>Spatial analysis</SPAN></LI><LI><SPAN>Generative AI grounding</SPAN></LI></UL><P><SPAN>—all without exporting data outside SAP HANA Cloud.</SPAN></P><H2 id="toc-hId-784074240"><SPAN>Adding semantic supplier knowledge with SAP HANA Knowledge Graph</SPAN></H2><P class=""><SPAN>Relational tables capture operational facts well, but <STRONG>business meaning and rules</STRONG> are better expressed semantically. This is where the SAP HANA Cloud Knowledge Graph engine comes in.</SPAN></P><P class=""><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351372i3324C3D707173F47/image-size/large?v=v2&amp;px=999" role="button" title="2.png" alt="2.png" /></span></SPAN></P><H3 id="toc-hId-716643454"><SPAN>Step 5: Initialize the RDF graph</SPAN></H3><P class=""><SPAN>We start by removing any existing graph to ensure a clean load.</SPAN></P><pre class="lia-code-sample language-sql"><code>CALL SPARQL_EXECUTE('DROP GRAPH &lt;kg_supplychain&gt;', '', ?, ?);</code></pre><H3 id="toc-hId-520129949"><SPAN>Step 6: Insert semantic supplier knowledge</SPAN></H3><P class=""><SPAN>We then insert RDF triples describing suppliers and their business attributes. Each supplier uses the <STRONG>same URI</STRONG> as the relational table, creating a natural bridge between SQL and RDF.</SPAN></P><pre class="lia-code-sample language-sql"><code>CALL SPARQL_EXECUTE( ' INSERT DATA { GRAPH &lt;kg_supplychain&gt; { &lt;http://example.org/supplier/AlphaGmbH&gt; a &lt;Supplier&gt; ; &lt;hasCertification&gt; "ISO 9001" ; &lt;hasCarbonTaxRate&gt; "low" ; &lt;isFlaggedForDelays&gt; false . } }', '', ?, ?);</code></pre><P><SPAN>Within the Knowledge Graph, suppliers are enriched with structured, machine‑readable facts such as certifications, carbon‑tax exposure, and compliance flags.</SPAN></P><P><SPAN>The result is a <STRONG>dual‑layer architecture</STRONG>:</SPAN></P><UL><LI><STRONG><SPAN>Relational layer</SPAN></STRONG><SPAN>: text, embeddings, spatial data</SPAN></LI><LI><STRONG><SPAN>Semantic layer</SPAN></STRONG><SPAN>: business meaning, rules, and relationships</SPAN></LI></UL><P><SPAN>Both are connected by shared URIs and executed in the same database.</SPAN></P><H2 id="toc-hId-194533725"><SPAN>Querying across graph, vector, spatial, and SQL models</SPAN></H2><H3 id="toc-hId-127102939"><SPAN>Query 1: Filter Knowledge Graph data directly in SQL</SPAN></H3><P><SPAN>SAP HANA Cloud allows SPARQL queries to be consumed as relational tables using </SPAN><SPAN>SPARQL_TABLE</SPAN><SPAN>, enabling seamless integration with SQL analytics.</SPAN></P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351375i0A0BC8C8223AF7BB/image-size/large?v=v2&amp;px=999" role="button" title="4.png" alt="4.png" /></span></SPAN></P><pre class="lia-code-sample language-sql"><code>SELECT * FROM SPARQL_TABLE(' SELECT ?supplier ?certification ?carbontax ?flag FROM &lt;kg_supplychain&gt; WHERE { ?supplier a &lt;Supplier&gt; . ?supplier &lt;hasCertification&gt; ?certification . ?supplier &lt;hasCarbonTaxRate&gt; ?carbontax . ?supplier &lt;isFlaggedForDelays&gt; ?flag . FILTER( ?certification = "ISO 9001" &amp;&amp; ?carbontax = "low" &amp;&amp; STR(?flag) = "false" ) } ');</code></pre><P class=""><SPAN>This bridges <STRONG>semantic reasoning</STRONG> and <STRONG>relational analytics</STRONG> in a single query flow.</SPAN></P><H3 id="toc-hId--144641935"><SPAN>Query 2: Hybrid SPARQL + SQL with vector and spatial filtering</SPAN></H3><P><SPAN>This advanced query combines:</SPAN></P><OL><LI><STRONG><SPAN>Geospatial filtering</SPAN></STRONG><SPAN> (nearby suppliers)</SPAN></LI><LI><STRONG><SPAN>Vector similarity search</SPAN></STRONG><SPAN> on supplier reports</SPAN></LI><LI><STRONG><SPAN>Knowledge Graph constraints</SPAN></STRONG></LI></OL><P><STRONG><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/351374iB2F8DCBEBBB8F360/image-size/large?v=v2&amp;px=999" role="button" title="3.png" alt="3.png" /></span></SPAN></STRONG></P><pre class="lia-code-sample language-sql"><code>CALL SPARQL_EXECUTE( ' SELECT * FROM &lt;kg_supplychain&gt; WHERE { SQL_TABLE("SELECT \"uri_str\", \"REPORT_TEXT\", \"SCO\" FROM ( SELECT *, sco - FIRST_VALUE(sco) OVER(ORDER BY sco DESC) AS diff FROM ( SELECT \"SUPPLIER_URI\" AS \"uri_str\", \"REPORT_TEXT\", COSINE_SIMILARITY( \"REPORT_EMBEDDING\", VECTOR_EMBEDDING(''no customs delay'', ''QUERY'', ''SAP_NEB.20240715'') ) AS sco FROM KG_SUPPLYCHAIN.SUPPLIER_REPORTS_LOOKUP WHERE \"GEO_LOCATION\".ST_Distance( ST_GeomFromText(''POINT(8.6821 50.1109)'', 4326) ) &lt; 50000 ORDER BY sco DESC ) ) WHERE diff &gt; -0.2") . } ORDER BY DESC(?SCO) LIMIT 10 ', 'Accept: application/sparql-results+csv Content-Type: application/sparql-query', ?, ?);</code></pre><P class=""><SPAN>This single query performs <STRONG>AI‑driven supplier discovery</STRONG>, ranking results by semantic meaning while respecting spatial and business rules.</SPAN></P><H2 id="toc-hId--47752433"><SPAN>Querying and managing Knowledge Graphs with Python</SPAN></H2><P><SPAN>Beyond the SQL console, Knowledge Graphs can be managed programmatically using Python and the HANA DBAPI.</SPAN></P><P><SPAN>The following example shows how to:</SPAN></P><UL><LI><SPAN>Upload an RDF ontology (Turtle .ttl format)</SPAN></LI><LI><SPAN>Verify graph load success<BR /></SPAN></LI><LI><SPAN>Query RDF data programmatically</SPAN></LI></UL><pre class="lia-code-sample language-python"><code>from hdbcli import dbapi conn = dbapi.connect( address='XXX.hana.prod-ap10.hanacloud.ondemand.com', port=443, user='XXX', password='XXX' ) cursor = conn.cursor()</code></pre><H3 id="toc-hId--537668945"><SPAN>Load an ontology into SAP HANA Cloud Knowledge Graph</SPAN></H3><pre class="lia-code-sample language-python"><code># Populate a new RDF in HANA Cloud with the turtle file content. We put the ontology into a specific graph. ttl_filename = "/Users/materials_ontology.ttl" graph_name = "materials_ontology" # Load the ontology into HANA Cloud KG print("Loading ontology...") try: with open(ttl_filename, 'r') as ttlfp: request_hdrs = '' request_hdrs += 'rqx-load-protocol: true' + '\r\n' # required header for upload protocol request_hdrs += 'rqx-load-filename: ' + ttl_filename + '\r\n' # optional header request_hdrs += 'rqx-load-graphname: ' + graph_name + '\r\n' # optional header to specify name of the graph print(f"Loading file: {ttl_filename}") print(f"Graph name: {graph_name}") # Execute the load result = conn.cursor().callproc('SPARQL_EXECUTE', (ttlfp.read(), request_hdrs, '?', None)) print("Materials ontology loaded successfully!") print(f"Result: {result}") except Exception as e: print(f"Error loading materials ontology: {e}") print(f"Error type: {type(e)}")</code></pre><H3 id="toc-hId--734182450"><SPAN>Query the ontology&nbsp;</SPAN></H3><P><SPAN>Do note that you will have to adjust the query based on your ontology structure.</SPAN></P><pre class="lia-code-sample language-python"><code># Verify the materials ontology was loaded print("Verifying materials ontology load...") try: # Count triples in the materials graph query = f""" SELECT (COUNT(*) as ?Triples) WHERE {{ GRAPH &lt;{graph_name}&gt; {{ ?s ?p ?o }} }} """ resp = conn.cursor().callproc('SPARQL_EXECUTE', (query, 'Accept: application/sparql-results+csv', '?', None)) print("Materials ontology statistics:") print(resp[2]) # Query for some material instances query2 = f""" PREFIX mat: &lt;http://example.com/materials/&gt; PREFIX matprop: &lt;http://example.com/materials/property/&gt; SELECT ?material ?materialId ?level2 ?level3 FROM &lt;{graph_name}&gt; WHERE {{ ?material a mat:Material . ?material matprop:materialId ?materialId . OPTIONAL {{ ?material matprop:level2 ?level2 . }} OPTIONAL {{ ?material matprop:level3 ?level3 . }} }} LIMIT 5 """ resp2 = conn.cursor().callproc('SPARQL_EXECUTE', (query2, 'Accept: application/sparql-results+csv', '?', None)) print("\n Sample material instances:") print(resp2[2]) except Exception as e: print(f"Error verifying materials ontology: {e}")</code></pre><H2 id="toc-hId--637292948"><SPAN>Summary</SPAN></H2><P><SPAN>SAP HANA Cloud enables <STRONG>true multi‑model intelligence</STRONG> by allowing relational data, vector embeddings, spatial context, and RDF knowledge graphs to coexist and execute together.</SPAN></P><P><SPAN>By combining i</SPAN><SPAN>n‑database vector embeddings, s</SPAN><SPAN>emantic Knowledge Graphs, and&nbsp;</SPAN><SPAN>SQL, SPARQL, Python access, o</SPAN><SPAN>rganizations can build powerful AI applications that are <STRONG>context‑aware, explainable, and operationally grounded</STRONG>—all from a single platform.</SPAN></P><P><SPAN>SAP HANA Cloud is not just a database for AI. It is where <STRONG>data, meaning, and intelligence converge</STRONG>.</SPAN></P> 2025-12-12T03:01:55.876000+01:00 https://community.sap.com/t5/artificial-intelligence-blogs-posts/implementing-thread-safe-structured-logging-for-python-fastapi/ba-p/14292907 Implementing Thread-Safe Structured Logging for Python FastAPI 2025-12-18T05:41:59.266000+01:00 Kunal__Kumar https://community.sap.com/t5/user/viewprofilepage/user-id/1692145 <P>With the focous on bussiness AI scenarios and high <STRONG>throughput</STRONG> applications, fastapi has come to be the de facto for standard python developement due to it's native support for <STRONG>async I/0</STRONG> and pydantic based validations ensuring type safety. However, deploying fastapi applications to SAP BTP CF enviroment gives us issues with observalibity.</P><P>&nbsp;</P><P>the standard library provided by SAP, <STRONG>sap-cf-logging</STRONG> relies heavily on <STRONG>WSGI middleware</STRONG> and <STRONG>thread-local storage</STRONG> to manage requests contexts. in <STRONG>ASGI</STRONG> enviroment, a single thread can manage multiple concurrent requests via an event loop.&nbsp;</P><P>relying on thread-local storage in this enviroment can lead to <STRONG>Context Leakage</STRONG> where logs from one Request A appear with Coorelation id of Request B, or worse context is lost entirely due to await calls.</P><P>&nbsp;</P><P>to ensure this doesn't occur in our application and observalibity is not effected in Kibana without compromising on non-blocking operations, there needed to be a custom solution using python <STRONG>contextvars</STRONG></P><P>&nbsp;</P><P><STRONG>Architecture: Context-Local vs Thread-Local</STRONG></P><P>to maintain the integrity of correlation id across async boundaries, there was a need for moving away from global state and implement context aware logger. this has mainly three components:-</P><P>&nbsp;</P><OL><LI><STRONG>context variables</STRONG>: to hold the state safley across asyncio event loop</LI><LI><STRONG>custom json formatter</STRONG>: to serialize logs into strict JSON format required by SAP cloud logging</LI><LI><STRONG>ASGI Middleware</STRONG>: to handle lifecycle management of the correlation id</LI></OL><P>&nbsp;</P><P><STRONG>1. Async-safe Context</STRONG></P><P>first we need to define a <STRONG>ContextVar</STRONG>, unlike <STRONG>threading.local</STRONG> , contextvars are natively supported by <STRONG>asyncio</STRONG>. when a task awaits a coroutine then context is preserved for that specific chain of execution, ensuring concurrent reqeusts never corrupt each other's state.</P><P>&nbsp;</P><P><STRONG>loggger.py</STRONG></P><pre class="lia-code-sample language-python"><code>import logging import sys import json import uuid from contextvars import ContextVar from typing import Optional # ContextVar to store Correlation ID safely (Async safe!) # This works per-task, ensuring concurrent requests preserve their unique ID. correlation_id_ctx: ContextVar[Optional[str]] = ContextVar("correlation_id", default=None)</code></pre><P><STRONG>&nbsp;</STRONG></P><P><STRONG>2. JSON Formatter</STRONG></P><P>SAP BTP's log ingestion pipeline expects specifc fields (mgs, level, correlation_id, written_at) to index logs correctly in Kibana. we need to extend the standard python <STRONG>logging.Formatter</STRONG>&nbsp;to intercept every log record adn inject the current context dynamically.</P><P>&nbsp;</P><P><STRONG>logger.py</STRONG></P><pre class="lia-code-sample language-python"><code>class JSONFormatter(logging.Formatter): """ Custom formatter that outputs logs as a JSON string. Automatically injects the Correlation ID from the async context. """ def format(self, record): # Retrieve the current correlation ID (or None if outside a request) cid = correlation_id_ctx.get() # Build the structured log dictionary required by SAP Cloud Logging log_record = { "level": record.levelname, "msg": record.getMessage(), "logger": record.name, "correlation_id": cid, "written_at": self.formatTime(record, self.datefmt), } # Include exception info if present (essential for stack traces in Kibana) if record.exc_info: log_record["exc_info"] = self.formatException(record.exc_info) return json.dumps(log_record) def setup_logging(): logger = logging.getLogger() if logger.handlers: logger.handlers = [] # Stream to stdout is crucial for Cloud Foundry loggregator handler = logging.StreamHandler(sys.stdout) handler.setFormatter(JSONFormatter()) logger.addHandler(handler) logger.setLevel(logging.INFO)</code></pre><P><STRONG>&nbsp;</STRONG></P><P><STRONG>3. Middleware injection</STRONG></P><P>we need to implement a <STRONG>BaseHTTPMiddleware</STRONG> to intercept every incoming request. this middleware handles the extraction of the <STRONG>X-CorrelationID</STRONG> header or generates a new <STRONG>UUID</STRONG> V4 if one is missing.</P><P>&nbsp;</P><P><STRONG>logger.py</STRONG></P><pre class="lia-code-sample language-python"><code>from fastapi import Request from starlette.middleware.base import BaseHTTPMiddleware class CorrelationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 1. Extract ID from header OR generate a new one correlation_id = request.headers.get("X-CorrelationID") or str(uuid.uuid4()) # 2. Set the ID in the ContextVar and capture the Token token = correlation_id_ctx.set(correlation_id) try: # 3. Process the request response = await call_next(request) # 4. Propagate the ID back to the client (UI) via headers response.headers["X-CorrelationID"] = correlation_id return response finally: # 5. Clean up: Reset the context to prevent leakage in thread pooling correlation_id_ctx.reset(token)</code></pre><P>&nbsp;</P><P><STRONG>4. "Background-Task" Conundrum</STRONG></P><P>a specfic challenge encountered was handlign FASTAPI BackgroundTasks. since background tasks run after the HTTP reponse is returned, the middleware describes above has already finished execution and called <STRONG>reset(token).&nbsp;</STRONG>consequence being that background tasks start wtih an empty context, causing logs to show <STRONG>correlation_id: null</STRONG></P><P>to solve this, we need to implementa <STRONG>Context Injection Pattern.&nbsp;</STRONG>we need to explicitily capture the ID while the requrst is still active and pass it to a wrapper function that <STRONG>re-hydrates</STRONG> the context inside the background thread.</P><P>&nbsp;</P><P><STRONG>api.py</STRONG></P><pre class="lia-code-sample language-python"><code><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1523598">@Api</a>_router.post("/extract") def extract(data: ExtractRequest, background_tasks: BackgroundTasks): # Capture the ID from the current context before the response is sent current_cid = correlation_id_ctx.get() # Pass it explicitly to the background task wrapper background_tasks.add_task(process_contract_sync, data.contract_id, current_cid) return {"message": f"Extracting data for contract ID: {data.contract_id}"} def process_contract_sync(contract_id: str, correlation_id: str): # RE-INJECT the ID into the context for this isolated thread token = correlation_id_ctx.set(correlation_id) try: # Run the business logic with full logging context asyncio.run(process_contract(contract_id)) except Exception as e: logging.exception(f"Critical failure in background task for {contract_id}") finally: # Strict cleanup using the token correlation_id_ctx.reset(token)</code></pre><P>&nbsp;</P><P><STRONG>Conclusion</STRONG></P><P>by leaving syncchronous <STRONG>sap-cf-logging</STRONG> library in favour of a native <STRONG>contextvars</STRONG> approach, we acheived:</P><P>1. <STRONG>full traceability</STRONG>: every log entry, including those in disconnected background tasks is tagged with a consistent Correlation ID</P><P>2. <STRONG>Stability</STRONG>: eliminated "socket hang up" errors caused by WSGI middleware incompabilties</P><P>3. <STRONG>Observability</STRONG>: logs appear in Kibana as structured JSON, allowing for deep filtering on specific correlation id or error states.</P><P>&nbsp;</P><P><SPAN>this</SPAN> <SPAN>approach</SPAN> <SPAN>offers</SPAN><SPAN> a </SPAN><SPAN>blueprint</SPAN><SPAN> for any team </SPAN><SPAN>aiming</SPAN><SPAN> to </SPAN><SPAN>utilize</SPAN><SPAN>&nbsp;</SPAN><SPAN>FastAPI</SPAN> <SPAN>alongside</SPAN><SPAN> the </SPAN><SPAN>strict&nbsp;</SPAN><SPAN>enterprise </SPAN><SPAN>observability</SPAN> <SPAN>requirements</SPAN> <SPAN>of</SPAN><SPAN> SAP BTP.</SPAN></P> 2025-12-18T05:41:59.266000+01:00 https://community.sap.com/t5/technology-blog-posts-by-sap/new-machine-learning-nlp-and-ai-features-in-sap-hana-cloud-2025-q4/ba-p/14293152 New Machine Learning, NLP and AI features in SAP HANA Cloud 2025 Q4 2025-12-22T08:08:25.334000+01:00 ChristophMorgen https://community.sap.com/t5/user/viewprofilepage/user-id/14106 <P><SPAN>With the&nbsp;</SPAN><STRONG>SAP HANA Cloud 2025 Q4 release</STRONG><SPAN>, several&nbsp;</SPAN><STRONG>new embedded Machine Learning / AI functions </STRONG><SPAN>&nbsp;have been released with the with the Predictive Analysis Library (PAL), the Automated Predictive Library (APL) and the NLP Services in SAP HANA Cloud.</SPAN></P><P><SPAN>Key new capabilities to be highlighted include</SPAN></P><UL><LI><SPAN>A new unified time series procedure, serving developers to utilize the same interface across different times series algorithms</SPAN></LI><LI><SPAN>text embedding model enhancements, supporting output vector dimensionality reduction, while maintaining retrieval accuracy</SPAN></LI><LI><SPAN>a new cross encoder model with the NLP services, for accurately re-ranking search results</SPAN></LI><LI><SPAN>text column input, text embedding operators with AutoML classification and regression models</SPAN></LI><LI><SPAN>text tokenization enhancements supporting regular expression token filtering and a new text log parsing function for detecting and determining new log pattern </SPAN></LI><LI><SPAN>the HANA ML experiment monitor UI now supports visual model monitoring and drift analysis</SPAN></LI></UL><P><SPAN>An enhancement summary is available in the <STRONG>What’s new document</STRONG> for&nbsp;<A href="https://help.sap.com/whats-new/2495b34492334456a49084831c2bea4e?Category=Predictive+Analysis+Library&amp;Valid_as_Of=2025-12-01:2025-12-31&amp;locale=en-US" target="_blank" rel="noopener noreferrer">SAP HANA Cloud database 2025.40 (QRC 4/2025)</A>.</SPAN></P><H2 id="toc-hId-1767386629">&nbsp;</H2><H2 id="toc-hId-1570873124"><SPAN>Time series enhancements</SPAN></H2><P><STRONG><SPAN>Introducing Unified Time Series interfaces</SPAN></STRONG></P><P><SPAN><A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/unified-time-series?)" target="_blank" rel="noopener noreferrer">Unified time series</A> is a newly introduced interface for a simplified use of multiple time series algorithms (ARIMA, Exponential Smoothing, Bayesian Structural Time Series (BSTS) and Additive Model Analysis (aka prophet)). </SPAN></P><UL><LI><SPAN>it allows for application developers to flexibly consume different time series analysis algorithms using the same procedure interface and a harmonized the handling of time series data patterns, avoiding algorithm-specific data preparation.</SPAN></LI><LI><SPAN>It also supports massive, data-parallel time series modeling for maximum parallelism and performance when modeling thousands of time series in parallel</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_0-1766163060120.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354281i72B7D8752D9AD268/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_0-1766163060120.png" alt="ChristophMorgen_0-1766163060120.png" /></span></P><P>&nbsp;</P><P><SPAN>A more detailed introduction to the new function is given in the following blog post <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/simplifying-time-series-analytics-with-unified-time-series-interface/ba-p/14292218" target="_blank">https://community.sap.com/t5/technology-blog-posts-by-sap/simplifying-time-series-analytics-with-unified-time-series-interface/ba-p/14292218</A></SPAN></P><P><STRONG><SPAN>&nbsp;</SPAN></STRONG></P><P><STRONG><SPAN>AutoML time series now support Prediction Intervals</SPAN></STRONG></P><P><SPAN>AutoML time series predict function, in addition to all regular time series functions, now supports prediction intervals for probabilistic forecasting </SPAN></P><UL><LI><SPAN>the uncertainty associated with a forecast is quantified by providing a range (lower/upper bounds) into which a future observation likely falls with a specific confidence level.</SPAN></LI><LI><SPAN>For example a 95% prediction interval contains the true value 95% of the time</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_1-1766163060130.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354280i60475911734D2868/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_1-1766163060130.png" alt="ChristophMorgen_1-1766163060130.png" /></span></P><H2 id="toc-hId-1374359619">&nbsp;</H2><H2 id="toc-hId-1177846114"><SPAN>Text embedding model enhancements (NLP services)</SPAN></H2><P><STRONG><SPAN>Output vector dimension reduction</SPAN></STRONG></P><P><SPAN>The Text Embedding models in SAP HANA Cloud have been enhanced with an attached a linear layer</SPAN>​​<SPAN>, derived from PCA trainings, to allow for the output vector dimensionality reduction</SPAN></P><UL><LI><SPAN>The arget dimension cardinality can be flexibly set to 128, 256 384, 512, 768 dimensions using </SPAN>​​<SPAN>PCA_DIM_NUM parameter</SPAN></LI><LI><SPAN>A near-original retrieval task accuracy is sustained with 256 dimensions, at 1/3rd of the original vector size (768 dimensions)</SPAN></LI><LI><SPAN>While lower dimension values than 128 would lead to significant and critical-level accuracy loss for retrieval tasks, hence 256 dimensions</SPAN>​​<SPAN> is recommended for efficiency &amp; performance</SPAN></LI></UL><P><STRONG><SPAN>Now </SPAN></STRONG><SPAN>text embeddings of significantly <STRONG><EM>lower dimensionality can be utilized at a</EM></STRONG> <STRONG><EM>minimal information loss</EM></STRONG> for retrieval tasks as well as machine learning.</SPAN></P><P><SPAN>Moreover <STRONG>s<EM>maller vector sizes </EM></STRONG>unlock much <STRONG><EM>faster machine learning </EM></STRONG>processing times and may also serve <STRONG><EM>vector retrieval queries</EM></STRONG></SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_2-1766163135495.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354283iE2DA5A040F70C036/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_2-1766163135495.png" alt="ChristophMorgen_2-1766163135495.png" /></span></P><P><SPAN>A more detailed introduction to the output vector dimensionality reduction is given in the following blog post</SPAN>&nbsp;<SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/new-cross-encoder-and-text-embedding-support-dimensionality-reduction-in/ba-p/14293164" target="_blank">New Cross Encoder and Text Embedding support Dimensionality Reduction in HANA NLP Service 2025 Q4- SAP Community blog post.</A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-981332609"><SPAN>Text feature data support with AutoML models</SPAN></H2><P><STRONG><SPAN>Text column data and Text Embedding Operator in AutoML models</SPAN></STRONG></P><P><SPAN>With the introduction of the a new <STRONG>text embedding operator</STRONG> for AutoML models, <STRONG>text columns</STRONG> can be directly used as input feature data and benefit from <EM>automatic</EM>, and <EM>optimized text vectorization</EM> utilizing SAP HANA Clouds text embedding models.</SPAN></P><UL><LI><SPAN>Text columns can be processed as feature, specified with the new parameter TEXT_VARIABLE</SPAN></LI><LI><SPAN>In addition, the new TextEmbedding operator supports the new target dimension reduction with the parameter PCA_DIM_NUM&nbsp; </SPAN></LI><LI><SPAN>The enhancement are available with the SQL interface as well as hana-ml 2.27 in Python</SPAN></LI></UL><P><SPAN>With that, the semantic insights from text columns can get automatically unlocked when building AutoML classification / regression models.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_3-1766163135508.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354284i535154FFAB1373DB/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_3-1766163135508.png" alt="ChristophMorgen_3-1766163135508.png" /></span></P><P>&nbsp;</P><H2 id="toc-hId-784819104"><SPAN>Cross Encoder Model (NLP services)</SPAN></H2><P><STRONG><SPAN>Accurate re-ranking of search results</SPAN></STRONG></P><P><SPAN>The family NLP services in SAP HANA Cloud has now been enriched with a new <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/cross-encoder?" target="_blank" rel="noopener noreferrer"><STRONG>cross encoder model</STRONG></A> and respective PAL functions. The cross encoder model</SPAN></P><UL><LI><SPAN>Processes pairs/sets of text sentences (query, candidate results) together</SPAN></LI><LI><SPAN>Therefore allows for a more precise semantic similarity re-ranking of search results, based on the full contextual interaction analysis between the query and the input candidate set (e.g. a an initial result set retrieved from a vector engine similarity search)</SPAN></LI><LI><SPAN>Thus much higher accurate and relevant-ranked similarity search results can be achieved</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_5-1766163220596.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354289iCFA46CCE741FFD81/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_5-1766163220596.png" alt="ChristophMorgen_5-1766163220596.png" /></span></P><P>&nbsp;</P><P><SPAN>Moreover the use of cross encoder models allows to combine multiple result sets retrieved from for example classic text search and vector engine similarity search queries into a hybrid search result, which can be passed into the cross encoder for its overall and combined re-ranking.</SPAN></P><P><SPAN>Custom AI, Retrieval Augmented Generation Applications (RAG) can now fully be served by Text Embedding Models, Vector Engine with Similarity Search and the Cross Encoder model, managing context privacy from the SAP HANA Cloud database.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_6-1766163220604.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354288i5A22ACEECABE4AC3/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_6-1766163220604.png" alt="ChristophMorgen_6-1766163220604.png" /></span></P><P><SPAN>A more detailed introduction to the new cross encoder model is given in the following blog post</SPAN>&nbsp;<SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/new-cross-encoder-and-text-embedding-support-dimensionality-reduction-in/ba-p/14293164" target="_blank">New Cross Encoder and Text Embedding support Dimensionality Reduction in HANA NLP Service 2025 Q4- SAP Community blog post.</A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-588305599"><SPAN>Text tokenization enhancements and new automated log text analysis</SPAN></H2><P><STRONG><SPAN>Text tokenization support for regular expressions</SPAN></STRONG></P><P><SPAN>The text <EM>pre-processing</EM> function <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/text-tokenize?" target="_blank" rel="noopener noreferrer">Text Tokenize </A>function for <EM>splitting input text into smaller units called tokens, has now been enhanced and supports r</EM>egular expression for filtering token output patterns.</SPAN></P><UL><LI><SPAN>Custom filtering (removal/keeping) of text patterns can be applied</SPAN></LI><LI><SPAN>List of regular expressions, matching tokens will be kept / excluded</SPAN></LI><LI><SPAN>Typical filtering examples include extracting e-mail addresses, URLs, or other token patterns for domain-specific needs</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_7-1766163220609.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354287i7BC8633CA0DAB217/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_7-1766163220609.png" alt="ChristophMorgen_7-1766163220609.png" /></span></P><P>&nbsp;</P><P><STRONG><SPAN>Automatic pattern detection from log texts</SPAN></STRONG></P><P><SPAN>A new analysis function for log text documents <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/text-log-parse?" target="_blank" rel="noopener noreferrer">Text Log Parse</A> has been added to the Predictive Analysis Library, which allows</SPAN></P><UL><LI><SPAN>For an automatic extraction of new log patterns and derive templates for new log patterns</SPAN></LI><LI><SPAN>High-performant processing of log texts for log classification and automated log analysis, the ability to detect and alert for new log patterns</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_8-1766163220624.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354292iBFB4981D8D6C0303/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_8-1766163220624.png" alt="ChristophMorgen_8-1766163220624.png" /></span></P><P>&nbsp;</P><H2 id="toc-hId-391792094"><SPAN>Real-time prediction performance improvements</SPAN></H2><P><STRONG><SPAN>Using PAL stateful ML models for real-time prediction performance</SPAN></STRONG></P><P><SPAN>When a PAL ML model state is created, the model is parsed only once and kept as a runtime in-memory object (see PAL documentation on <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/state-enabled-real-time-scoring-functions" target="_blank" rel="noopener noreferrer">state-enabled-real-time-scoring-functions</A>)</SPAN></P><UL><LI><SPAN>The actual prediction-function references to the PAL ML model by its STATE_ID</SPAN></LI><LI><SPAN>The repeated overhead of PAL ML model parsing with every predict-function call can be avoided in scenarios </SPAN><UL><LI><SPAN>with rather complex and larger PAL ML models with significant model parsing time proportion</SPAN></LI><LI><SPAN>the prediction runtime shall be as minimal as possible and near real-time</SPAN></LI></UL></LI><LI><SPAN>The prediction runtime performance has now been improved from ~100ms to ~20ms in exemplary use cases (see example <A href="https://github.com/SAP-samples/hana-ml-samples/blob/main/PAL-SQL/usage-patterns/PAL%20ML%20model%20state%20for%20real-time%20predictions.sql" target="_blank" rel="nofollow noopener noreferrer">PAL_ML_model_for_real-time_predictions.sql</A>)</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_0-1767869320915.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359353iBBFA7DB11C804EF3/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="ChristophMorgen_0-1767869320915.png" alt="ChristophMorgen_0-1767869320915.png" /></span></P><P>&nbsp;</P><P><SPAN>&nbsp;</SPAN></P><H2 id="toc-hId-195278589"><SPAN>ML experiment tracking and task scheduling enhancements</SPAN></H2><P><STRONG><SPAN>Experiment tracking enhancements</SPAN></STRONG></P><P><SPAN><A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/pal-track?" target="_blank" rel="noopener noreferrer">Tracking of experiments</A>, now supports custom track entity tags</SPAN></P><UL><LI><SPAN>Standard track entities generated in PAL Training, Forecast, etc. can now be enriched with custom tags like business related information associated with related track entity</SPAN></LI><LI><SPAN>Tag is name-value pair for annotation or note, binding extra information to existing entities</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_9-1766163220625.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354290i96134FE7913A4153/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_9-1766163220625.png" alt="ChristophMorgen_9-1766163220625.png" /></span></P><P>&nbsp;</P><P>A more detailed introduction is provided in the following blog post <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/comprehensive-guide-to-mltrack-in-sap-hana-cloud-end-to-end-machine/ba-p/14134217" target="_blank">comprehensive-guide-to-mltrack-in-sap-hana-cloud-end-to-end-machine</A>&nbsp;</P><P>Moreover the Experiment Monitor in the Python Machine Learning client (hana-ml) supports for visually monitoring ML model performance degradation and drift.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_10-1766163220626.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354291i656B4787F3256EFB/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_10-1766163220626.png" alt="ChristophMorgen_10-1766163220626.png" /></span></P><P>&nbsp;</P><P><STRONG><SPAN>One-off scheduling of PAL tasks</SPAN></STRONG></P><P><SPAN>The <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/calling-pal-with-schedule?" target="_blank" rel="noopener noreferrer">PAL procedure scheduling interfaces</A></SPAN> has been enhance with a <SPAN>One-OFF schedule option, allowing for </SPAN></P><UL><LI><SPAN>ad-hoc, automatic one-off scheduled execution of PAL task procedures with dynamic setting of time-frequency information based on current UTC timestamp</SPAN></LI><LI><SPAN>it triggers a scheduled job to execute immediately, and the corresponding one-off schedule is removed right away and doesn’t require to be manually maintained.</SPAN></LI></UL><P>&nbsp;</P><H2 id="toc-hId--1234916"><SPAN>Python ML client (hana-ml) enhancements</SPAN></H2><P><EM>The full list of new methods and enhancements with hana_ml 2.27&nbsp; is summarized in the </EM><SPAN><A href="https://help.sap.com/doc/cd94b08fe2e041c2ba778374572ddba9/2025_4_QRC/en-US/change_log.html" target="_blank" rel="noopener noreferrer"><EM>changelog for hana-ml 2.27</EM></A> </SPAN><EM>as part of the documentation.&nbsp; You can find an examples notebook illustrating the highlighted feature enhancements <SPAN><A href="https://github.com/SAP-samples/hana-ml-samples/blob/main/Python-API/pal/notebooks/25QRC04_2.27.ipynb" target="_blank" rel="noopener nofollow noreferrer">here 25QRC04_2.27.ipynb</A>.</SPAN></EM></P><P><EM>The key enhancements in this release include the following:</EM><EM>&nbsp;</EM></P><P><STRONG><SPAN>Time series data outlier detection with threshold support</SPAN></STRONG></P><P><SPAN>The method for time series outlier detection in the Predictive Analysis Library has added support outlier threshold settings, in addition to the outlier voting capability using different outlier evaluation methods incl. &nbsp;Z1 score, Z2 score, IIQR score, MAD score, IsolationForest and DBSCAN </SPAN></P><UL><LI><SPAN>If the absolute value of outlier score is beyond the threshold, the respective data point will be considered as an outlier.<BR /><BR /></SPAN></LI></UL><P><STRONG><SPAN>Time series reports for massive, data-parallel model scenarios</SPAN></STRONG></P><P><SPAN>Massive AutoML Time Series modeling scenarios often utilize random-search with hyperband as the fastest optimization, potentially with larger number of time series data segment groups to be processed and forecasted in parallel, each time series segment group again with a significant number of forecast models to be explored.&nbsp; </SPAN></P><P><SPAN>Hence the display of forecasts which have been explored by AutoML within each time series segment group is collapsed by default and can be expanded for review. </SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_11-1766163388491.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354294iEE72CE6111257A87/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_11-1766163388491.png" alt="ChristophMorgen_11-1766163388491.png" /></span></P><P>&nbsp;</P><P><STRONG><SPAN>&nbsp;</SPAN></STRONG></P><P><STRONG><SPAN>Classification and regression function enhancements</SPAN></STRONG></P><P><STRONG><SPAN>Support Vector Machine (SVM)</SPAN></STRONG><SPAN> model training is computationally expensive, and computational costs are specifically sensitive to the number of training points, which makes SVM models often impractical for large datasets. </SPAN></P><P><SPAN>The SVM algorithm now supports <STRONG>Coreset Sampling</STRONG> </SPAN></P><UL><LI><SPAN>which allows to automatically sample small, representative subsets (the "coreset") from larger datasets, </SPAN></LI><LI><SPAN>enabling faster, more efficient training and processing while maintaining similar model accuracy as using the full data. </SPAN></LI></UL><P><SPAN>This enhancement significantly reduces SVM training time with minimal impact on accuracy. </SPAN></P><P><STRONG>&nbsp;</STRONG></P><P><SPAN>The<STRONG> model report </STRONG>for<STRONG> classification </STRONG>tasks now supports a<STRONG> percentage display </STRONG>in<STRONG> confusion matrix </STRONG>for easier visual interpretation of classification results.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_12-1766163388493.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354293i102668E672266574/image-size/large?v=v2&amp;px=999" role="button" title="ChristophMorgen_12-1766163388493.png" alt="ChristophMorgen_12-1766163388493.png" /></span></P><P>&nbsp;</P><P><STRONG><SPAN>High-dimensional feature data reduction using UMAP </SPAN></STRONG></P><P><SPAN><A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/uniform-manifold-approximation-and-projection?version=LATEST&amp;q=UMAP&amp;locale=en-US" target="_blank" rel="noopener noreferrer">UMAP (Uniform Manifold Approximation and Projection)</A> is a non-linear dimensionality reduction algorithm used to simplify complex, high-dimensional feature spaces, while preserving its essential structure. It is widely considered the modern gold standard for visualizing targeted dimension reduction of large-scale datasets, because it balances computational speed with the ability to maintain both local and global relationships.</SPAN></P><UL><LI><SPAN>It reduces thousands of variables (dimensions) into 2D or 3D scatter plots that humans can easily interpret.</SPAN></LI><LI><SPAN>Unlike comparable methods like t-SNE, UMAP is better at preserving global structure, meaning the relative positions between different clusters remain more meaningful.</SPAN></LI><LI><SPAN>It is significantly faster and more memory-efficient than t-SNE, capable of processing datasets with millions of points in a reasonable timeframe.</SPAN></LI><LI><SPAN>It can be used as a "transformer" preprocessing step in Machine Learning scenarios to reduce large feature spaces before applying clustering (e.g., k-means, HDBSCAN) or classification models, often improving their performance.</SPAN></LI></UL><P><STRONG><SPAN>&nbsp;</SPAN></STRONG></P><P><STRONG><SPAN>Calculating pairwise distances </SPAN></STRONG></P><P>Many algorithms, for example clustering algorithms utilize distance matrixes as a preprocessing step, often inbuild the functions. Often there is the wish to decouple though the distance matrix calculation from the follow-up task like the actual clustering. Moreover, if decoupled custom calculated matrixes can be fed into algorithms as input.</P><UL><LI><SPAN>Most PAL clustering functions support to feed-in a pre-calculated similarity matrix</SPAN></LI></UL><P>Now, a pairwise <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/distance-md?version=LATEST&amp;q=distance&amp;locale=en-US" target="_blank" rel="noopener noreferrer">distance calculation function</A> is provided</P><UL><LI><SPAN>It supports distance metrics like <EM>Manhattan, Euclidien, Minkowski, Chebyshey</EM> as well as <EM>Levenshtein</EM></SPAN></LI><LI><SPAN>The <STRONG>Levenshtein distance</STRONG> (or edit distance) is a distance metric specifically targeting distance between text-columns. It calculates the minimum number of single-character edits (insertions, deletions, or substitutions) needed to transform one word into another, acting as a measure of their similarity. A lower distance indicates a higher similarity.</SPAN></LI></UL><P><SPAN>Applicable use cases</SPAN></P><UL><LI><SPAN>It is useful in data cleaning, table column similarity analysis between columns of the same data type.</SPAN></LI><LI><SPAN>After calculating the column similarity across all data types, clustering like K-Means can be applied to group similar fields and propose mappings for fields within the same cluster</SPAN></LI></UL><P><SPAN>&nbsp;</SPAN></P><P><STRONG><EM>Again, an incredible set of enhancements in the SAP HANA Cloud database AI engine and NLP services!</EM></STRONG></P><P><STRONG>Enjoy to try out all the enhancements and let us know what you think here!</STRONG></P><P>&nbsp;</P> 2025-12-22T08:08:25.334000+01:00 https://community.sap.com/t5/technology-blog-posts-by-sap/leveraging-sap-ai-core-to-build-custom-ai-agents-with-crewai/ba-p/14279604 Leveraging SAP AI Core to Build Custom AI Agents with CrewAI 2025-12-24T06:41:18.594000+01:00 Manisha_19 https://community.sap.com/t5/user/viewprofilepage/user-id/1695623 <H1 id="toc-hId-1636640266">Introduction</H1><P>AI agents are becoming the go-to pattern for building modular, autonomous assistants that can think, act, and collaborate. <STRONG>CrewAI</STRONG>&nbsp;gives you a developer-friendly way to orchestrate these agents, while <STRONG>SAP AI Core</STRONG>&nbsp;provides enterprise-grade access to LLMs with proper governance, scalability, and security.</P><P>This quickstart shows you how to connect <STRONG>CrewAI</STRONG>&nbsp;to <STRONG>SAP AI Core</STRONG>, create a custom LLM interface, and build your first working agent — all in under 10 minutes.</P><BLOCKQUOTE><P>By the end, you’ll have a simple <STRONG>CrewAI</STRONG> agent powered by an LLM hosted in <EM>SAP AI Core</EM>.</P></BLOCKQUOTE><P>&nbsp;</P><H1 id="toc-hId-1440126761">How SAP Facilitates Building Custom Agents</H1><P>CrewAI handles the <EM>agent orchestration layer</EM>, while SAP AI Core hosts and manages your LLMs. &nbsp;</P><P>Connecting them allows you to use SAP’s AI infrastructure directly inside your CrewAI workflows — no external API juggling.</P><P>Here’s the high-level flow:</P><P>User Prompt → CrewAI Agent → Custom LLM Class → SAP AI Core Endpoint → Model Response</P><P><STRONG>Flow summary:</STRONG></P><OL><LI>User sends a prompt → CrewAI Agent receives it.</LI><LI>Agent forwards it to a custom LLM wrapper (`SAPAILLM`).</LI><LI>The wrapper authenticates using the token and sends the request to SAP AI Core.</LI><LI>SAP AI Core executes the model and returns a response to the agent.</LI><LI>CrewAI handles the output and delivers the final answer.</LI></OL><P>This setup ensures enterprise-level reliability with full control over which models your agents use.</P><P>&nbsp;</P><H1 id="toc-hId-1243613256">Prerequisites</H1><P>Before jumping in, make sure you have:</P><UL><LI>Access to <STRONG>SAP AI Core</STRONG>&nbsp;with deployed LLM models (via AI Launchpad)</LI><LI><STRONG>Python 3.10+</STRONG></LI><LI><STRONG>VS Code</STRONG>&nbsp;or your favorite IDE</LI><LI>Installed libraries:</LI></UL><pre class="lia-code-sample language-bash"><code>pip install ipykernel pip install crewai pip install crewai_tools pip install generative-ai-hub-sdk[all] pip install langchain_community</code></pre><UL><LI>Your SAP AI Core Service Key downloaded as a .json file</LI><LI>Your A-game to create some amazing agents</LI></UL><P>*If you want to learn how to deploy your LLM model on launchpad, you can go through this <A href="https://developers.sap.com/tutorials/ai-core-generative-ai.html" target="_self" rel="noopener noreferrer">course.</A></P><P>&nbsp;</P><H1 id="toc-hId-1047099751">Step-by-Step Guide</H1><P>Let us now dive into building our own custom agent which utilizes SAP's AI Core.</P><H2 id="toc-hId-979668965">Step 1: Get Your SAP AI Core Credentials</H2><P>In your SAP BTP Cockpit → AI Core Instance → Service Keys, create a new key and save it locally as credentials.json.</P><P>Example structure:</P><pre class="lia-code-sample language-json"><code>{ "clientid": "your-client-id", "clientsecret": "your-client-secret", "url": "https://&lt;your-region&gt;.authentication.sap.hana.ondemand.com", "serviceurls": { "AI_API_URL": "https://***.aws.ml.hana.ondemand.com" } }</code></pre><P>This file contains everything needed to authenticate and access your deployed LLM.</P><H2 id="toc-hId-783155460">Step 2: Get Your BTP LLM Access Token</H2><P>We’ll use this token to authenticate each request to SAP AI Core.</P><pre class="lia-code-sample language-python"><code>import json, requests # open credentials file with open("&lt;path-to-file&gt;/credentials.json", "r") as key_file: svcKey = json.load(key_file) authUrl = svcKey["url"] clientid = svcKey["clientid"] clientsecret = svcKey["clientsecret"] apiUrl = svcKey["serviceurls"]["AI_API_URL"] # request token params = {"grant_type": "client_credentials" } resp = requests.post(f"{authUrl}/oauth/token", auth=(clientid, clientsecret), params=params) BtpLlmAccessToken = resp.json()["access_token"] print("Token retrieved successfully!")</code></pre><BLOCKQUOTE><P>Tokens usually expire after 1 hour. Make sure to refresh them before making multiple requests.</P></BLOCKQUOTE><H2 id="toc-hId-586641955">Step 3: Create a Custom LLM Class Template for CrewAI</H2><P>CrewAI lets you define your own LLM wrappers, which means we can make one that talks directly to SAP AI Core.</P><P>In this class, we can define the procedure to use the deployed models from SAP AI Core.</P><P>You can now import this class into any CrewAI workflow to use SAP’s hosted LLMs.</P><pre class="lia-code-sample language-python"><code>from crewai import BaseLLM from typing import Any, Dict, List, Optional, Union class CustomLLM(BaseLLM): def __init__(self, model: str, api_key: str, endpoint: str, temperature: Optional[float] = None): super().__init__(model=model, temperature=temperature) self.api_key = api_key self.endpoint = endpoint def call( self, messages: Union[str, List[Dict[str, str]]], tools: Optional[List[dict]] = None, **kwargs ) -&gt; Union[str, Any]: """Call the LLM with the given messages.""" # Convert string to message format if needed if isinstance(messages, str): messages = [{"role": "user", "content": messages}] payload = { "messages": messages, "temperature": self.temperature, "max_tokens": 1000 # This can be modified as per your use case. You can parameterize this as well. } headers = { 'AI-Resource-Group': "&lt;your-resource-group-name&gt;", 'Content-Type': 'application/json', 'Authorization': f'Bearer {BtpLlmAccessToken}', } # Make API call response = requests.post( self.endpoint, headers= headers, json=payload, timeout=30 ) response.raise_for_status() result = response.json() return result["choices"][0]["message"]["content"]</code></pre><H2 id="toc-hId-390128450">Step 4: Initialize your Custom LLM Class</H2><P>Alright, time to bring that class to life. &nbsp;</P><P>The cool thing here is that you can hook it up to any model you’ve deployed in SAP AI Core, whether it’s GPT-4o, Claude, or your own fine-tuned beast.</P><P>In this example, we’ll wire it up to a **<STRONG>GPT-4o</STRONG>** deployment and let CrewAI start chatting through it. You can get the deployment endpoint from your SAP AI Launchpad. You can find the payload formats for different models <A href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/example-payloads-for-inferencing-sap-ai-core-hosted" target="_self" rel="noopener noreferrer">here</A>.</P><pre class="lia-code-sample language-python"><code>deployment_url = "&lt;deployment URL&gt;" + "/chat/completions?api-version=2024-02-01" # instantiating Custom LLM object custom_llm = CustomLLM( model="gpt-4o", api_key=BtpLlmAccessToken, endpoint=deployment_url, temperature=0.7 )</code></pre><H2 id="toc-hId-193614945">Step 5: Build a Simple CrewAI Agent</H2><P>Now that your LLM class is ready, let’s create a basic Research CrewAI agent that uses it.</P><pre class="lia-code-sample language-python"><code>from crewai import Agent, Task, Crew # Load credentials agent = Agent( role="Research Assistant", goal="Find and analyze information", backstory="You serve as a research assistant, helping to gather and analyze information about SAP tools and related topics.", llm=custom_llm ) # Create and execute tasks task = Task( description="Research the latest developments in SAP AI Core", expected_output="A comprehensive summary", agent=agent ) crew = Crew(agents=[agent], tasks=[task]) result = crew.kickoff()</code></pre><P>If all goes well, you’ll see a clean response from your SAP-hosted LLM, directly through your CrewAI agent. Here is a sample output:</P><BLOCKQUOTE><P>CrewOutput(raw="The latest developments in SAP AI Core focus on enhancing the capabilities for deploying and managing AI models in a scalable and efficient manner. SAP AI Core is part of SAP Business Technology Platform (BTP) and is designed to integrate with other SAP applications to streamline AI operat....ding tools that enhance productivity, and driving innovation through AI-powered solutions.", pydantic=None, json_dict=None, agent='Research Assistant', output_format=&lt;OutputFormat.RAW: 'raw'&gt;)], token_usage=UsageMetrics(total_tokens=0, prompt_tokens=0, cached_prompt_tokens=0, completion_tokens=0, successful_requests=0))</P></BLOCKQUOTE><H1 id="toc-hId--131981279">&nbsp;</H1><H1 id="toc-hId-441245299">Conclusion</H1><P>And that’s it — you just built your first CrewAI agent powered by <STRONG>SAP AI Core</STRONG>.</P><P>From here, you can:</P><UL><LI>Add tools for the agent to interact with SAP APIs (e.g., fetching invoice data).</LI><LI>Add memory and multi-agent orchestration.</LI><LI>Deploy it on <STRONG>SAP BTP</STRONG>&nbsp;or your internal infrastructure for enterprise use.</LI></UL><P>This quick start gives you the foundation — the next step is making your agent truly <EM>yours</EM>.</P><P>&nbsp;</P><H1 id="toc-hId-244731794">References</H1><UL><LI><A href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/example-payloads-for-inferencing-sap-ai-core-hosted" target="_blank" rel="noopener noreferrer">Example Payloads for Inferencing - SAP AI Core Hosted | SAP Help Portal</A></LI><LI><A href="https://developers.sap.com/tutorials/ai-core-generative-ai.html" target="_blank" rel="noopener noreferrer">Prompt LLMs in the generative AI hub in SAP AI Core &amp; Launchpad | SAP Tutorials</A></LI></UL><P>&nbsp;</P><P>&nbsp;</P> 2025-12-24T06:41:18.594000+01:00 https://community.sap.com/t5/technology-blog-posts-by-sap/new-machine-learning-nlp-and-ai-features-in-sap-hana-cloud-2025-q3/ba-p/14304443 New Machine Learning, NLP and AI features in SAP HANA Cloud 2025 Q3 2026-01-09T12:54:46.437000+01:00 ChristophMorgen https://community.sap.com/t5/user/viewprofilepage/user-id/14106 <P><SPAN>With the SAP HANA Cloud 2025 Q3 release, several new embedded Machine Learning / AI functions&nbsp;have been released with the SAP HANA Cloud Predictive Analysis Library (PAL) and the Automated Predictive Library (APL). </SPAN></P><UL><LI><SPAN>An enhancement summary is available in the What’s new document for <A href="https://help.sap.com/whats-new/2495b34492334456a49084831c2bea4e?Category=Predictive+Analysis+Library&amp;Valid_as_Of=2025-09-01:2025-09-30&amp;locale=en-US" target="_self" rel="noopener noreferrer">SAP HANA Cloud database 2025.28 (QRC 3/2025)</A>.</SPAN></LI></UL><H2 id="toc-hId-1787736735">&nbsp;</H2><H2 id="toc-hId-1591223230"><SPAN>Time series analysis and forecasting function enhancements</SPAN></H2><P><STRONG><SPAN>Threshold support in timeseries outlier detection </SPAN></STRONG></P><P><SPAN>In time series, an outlier is a data point that is different from the general behavior of remaining data points.&nbsp; In the PAL <STRONG><EM>time series outlier detection</EM></STRONG> function, the outlier detection task is divided into two steps</SPAN></P><UL><LI><SPAN>In step 1 the residual values are derived from the original series, </SPAN></LI><LI><SPAN>In step 2, the outliers are detected from the residual values.</SPAN></LI></UL><P><SPAN>Multiple methods are available to evaluate a data point to be an outlier or not. </SPAN></P><UL><LI><SPAN>Including Z1 score, Z2 score, IIQR score, MAD score, IsolationForest, DBSCAN</SPAN></LI><LI><SPAN>If used in combination, outlier voting can be applied for a combined evaluation.&nbsp;</SPAN></LI></UL><P><SPAN>Now, <STRONG>new</STRONG> and in addition, <STRONG><EM>thresholds values for outlier scores</EM></STRONG> are supported</SPAN></P><UL><LI><SPAN>New parameter OUTPUT_OUTLIER_THRESHOLD </SPAN></LI><LI><SPAN>Based on the given threshold value, if the time series value is beyond the (upper and lower) outlier threshold for the time series, the corresponding data point as an outlier.</SPAN></LI><LI><SPAN>Only valid when outlier_method = 'iqr', 'isolationforest', 'mad', 'z1', 'z2'.</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_0-1767958753257.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359750iE20F7716FF87FA07/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="ChristophMorgen_0-1767958753257.jpeg" alt="ChristophMorgen_0-1767958753257.jpeg" /></span></P><P>&nbsp;</P><P><SPAN>&nbsp;</SPAN></P><H2 id="toc-hId-1394709725"><SPAN>Classification and regression function enhancements</SPAN></H2><P><STRONG><SPAN>Corset sampling support with SVM models</SPAN></STRONG></P><P><STRONG>Coreset sampling</STRONG>&nbsp;is a machine learning technique to</P><UL><LI>select a small, representative subset (the "coreset") from larger datasets,</LI><LI>enabling faster, more efficient training and processing while maintaining similar model accuracy as using the full data.</LI><LI>It works by identifying the most "informative" samples, filtering out redundant or noisy data, and allowing complex algorithms to run on a manageable dataset sizes.</LI></UL><P><STRONG>Support Vector Machine (SVM)</STRONG>&nbsp;model training is computationally expensive, and computational costs are specifically sensitive to the number of training points, which makes SVM models often impractical for large datasets.&nbsp;</P><P><SPAN>Therefore SVM in the Predictive Analysis Library has been enhanced and now</SPAN></P><UL><LI>offers&nbsp;<STRONG>embedded coreset sampling</STRONG>&nbsp;capabilities</LI><LI>enabled with the new parameters USE_CORESET and CORESET_SCALE as the <SPAN>sampling ratio when constructing coreset</SPAN>.</LI></UL><P>This enhancement significantly reduces SVM training time with minimal impact on accuracy.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ChristophMorgen_1-1767958753264.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359751iDA955B4D29D2C3A9/image-size/large/is-moderation-mode/true?v=v2&amp;px=999" role="button" title="ChristophMorgen_1-1767958753264.png" alt="ChristophMorgen_1-1767958753264.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><H2 id="toc-hId-1198196220"><SPAN>AutoML and pipeline function enhancements</SPAN></H2><P><STRONG><SPAN>Target encoding support in&nbsp;AutoML&nbsp;</SPAN></STRONG></P><P>The PAL AutoML framework introduces a new pipeline operator for target encoding of categorial features</P><UL><LI><SPAN>Categorical data is often required to be preprocessed and required to get converted from non-numerical features into formats suitable for the respective machine learning algorithm, i.e. numeric values</SPAN><UL><LI><SPAN>Examples features: text labels (e.g., “red,” “blue”) or discrete categories (e.g., “high,” “medium,” “low”)</SPAN></LI></UL></LI><LI><SPAN>One-hot encoding converts each categorial feature value &nbsp;into a binary column (0 or 1), which works well for features with a limited number of unique values. PAL already applies an optimized one-hot encoding method aggregating very low frequent values.</SPAN></LI><LI><SPAN>Target encoding replaces the categorial values with the mean of the target / label column for high-cardinality features, which avoids to create large and sparse one-hot encoded feature matrices</SPAN><UL><LI><SPAN>Example of a high cardinality feature: “city” column with hundreds-thousands of unique values, postal code, product IDs etc.</SPAN></LI></UL></LI></UL><P>The PAL AutoML engine will analyze the input feature cardinality and then automatically decide if to apply target encoding or another encoding method. For medium to high cardinality categorial features, target encoding may improve the performance significantly.</P><P><SPAN>By automating target encoding, the PAL AutoML engine aims to improve model performance and generalization, especially when dealing with complex, high-cardinality categorical features, without requiring manual intervention.</SPAN></P><P>In addition, the AutoML and pipeline function now also support columns of type half precision vector.</P><H2 id="toc-hId-1001682715">&nbsp;</H2><H2 id="toc-hId-805169210"><SPAN>Misc. Machine Learning and statistics function enhancements</SPAN></H2><P><STRONG><SPAN>High-dimensional feature data reduction using UMAP</SPAN></STRONG></P><P>UMAP (Uniform Manifold Approximation and Projection) is a non-linear dimensionality reduction algorithm used to simplify complex, high-dimensional feature spaces, while preserving its essential structure. It is widely considered the modern gold standard for visualizing targeted dimension reduction of large-scale datasets, because it balances computational speed with the ability to maintain both local and global relationships.</P><UL><LI><SPAN>It reduces thousands of variables (dimensions) into 2D or 3D scatter plots that humans can easily interpret.</SPAN></LI><LI><SPAN>Unlike comparable methods like t-SNE, UMAP is better at preserving global structure, meaning the relative positions between different clusters remain more meaningful.</SPAN></LI><LI><SPAN>It is significantly faster and more memory-efficient than t-SNE, capable of processing datasets with millions of points in a reasonable timeframe.</SPAN></LI><LI><SPAN>It can be used as a "transformer" preprocessing step in Machine Learning scenarios to reduce large feature spaces before applying clustering (e.g., k-means, HDBSCAN) or classification models, often improving their performance.</SPAN></LI></UL><P><SPAN>The following new functions are introduced</SPAN></P><UL><LI><SPAN>_SYS_AFL.PAL_UMAP</SPAN>​ with the most important <SPAN>parameters N_NEIGHBORS, MIN_DIST, N_COMPONENTS, DISTANCE_LEVEL</SPAN>​</LI></UL><UL><LI><SPAN>_SYS_AFL.PAL_TRUSTWORTHINESS</SPAN>​, u<SPAN>sed to measure the structure similarity between original high dimensional space and embedded low dimensional space based on K nearest neighbors.</SPAN></LI></UL><P><STRONG><SPAN>&nbsp;</SPAN></STRONG></P><P><STRONG><SPAN>Calculating pairwise distances</SPAN></STRONG></P><P><SPAN>Many algorithms, for example clustering algorithms utilize distance matrixes as a preprocessing step, often inbuild to the functions. While often there is the wish to decouple though the distance matrix calculation from the follow-up task like the actual clustering. Moreover, if decoupled custom calculated matrixes can be fed into algorithms as input.</SPAN></P><UL><LI><SPAN>Most PAL clustering functions support to feed-in a pre-calculated similarity matrix</SPAN></LI></UL><P><SPAN>Now, a dedicated <A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-predictive-analysis-library/distance-md?version=LATEST&amp;q=distance&amp;locale=en-US" target="_blank" rel="noopener noreferrer">pairwise distance calculation</A> function is provided </SPAN></P><UL><LI><SPAN>It supports distance metrics like <EM>Manhattan, Euclidien, Minkowski, Chebyshey</EM> as well as <STRONG>Levenshtein</STRONG></SPAN></LI><LI><SPAN>The <STRONG><EM>Levenshtein distance</EM></STRONG> (or “edit distance”) is a distance metric specifically targeting distance between text-columns. </SPAN><UL><LI><SPAN>It calculates the minimum number of single-character edits (insertions, deletions, or substitutions) needed to transform one word into another, acting as a measure of their similarity. A lower distance indicates a higher similarity.</SPAN></LI></UL></LI></UL><P><SPAN>Applicable use cases</SPAN></P><UL><LI><SPAN>It is useful in data cleaning, table column similarity analysis between columns of the same data type.</SPAN></LI><LI><SPAN>After calculating the column similarity across all data types, clustering like K-Means can be applied to group similar fields and propose mappings for fields within the same cluster</SPAN></LI></UL><P><SPAN>&nbsp;</SPAN></P><P><STRONG><SPAN>Real Vector data type support</SPAN></STRONG></P><P>The following PAL functions have been enhanced to support columns of type real vector</P><UL><LI><SPAN>Spectral Clustering</SPAN></LI><LI><SPAN>Cluster Assignment</SPAN></LI><LI><SPAN>Decision tree</SPAN></LI><LI><SPAN>Sampling</SPAN></LI></UL><P>In addition the AutoML and pipeline function now also support columns of type half precision vector.</P><P>&nbsp;</P><H2 id="toc-hId-608655705"><SPAN>Creating Vector Embeddings enhancements</SPAN></H2><P><SPAN>The SAP HANA Database Vector Engine function VECTOR_EMBEDDING()&nbsp;</SPAN><SPAN>has added support for remote, SAP AI Core exposed embedding models. Detailed instruction are given in the documentation at&nbsp;</SPAN><SPAN><A href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/creating-text-embeddings-with-sap-ai-core" target="_blank" rel="noopener noreferrer">Creating Text Embeddings with SAP AI Core | SAP Help Portal</A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-412142200"><SPAN>Python ML client (hana-ml) enhancements</SPAN></H2><P><EM>The full list of new methods and enhancements with hana_ml 2.26&nbsp; is summarized in the </EM><SPAN><A href="https://help.sap.com/doc/cd94b08fe2e041c2ba778374572ddba9/2025_3_QRC/en-US/change_log.html" target="_blank" rel="noopener noreferrer"><EM>changelog for hana-ml 2.26</EM></A> </SPAN><EM>as part of the documentation. The key enhancements in this release include</EM></P><P><STRONG>New&nbsp;Functions</STRONG></P><UL><LI>Added text tokenization API.</LI><LI>Added explainability support with IsolationForest Outlier Detection</LI><LI>Added constrained clustering API.</LI><LI>Added intermittent time series data test in time series report.</LI></UL><P><STRONG>Enhancements</STRONG></P><UL><LI>Support time series SHAP visualizations for AutoML Timeseries model explanations</LI></UL><P>You can find an examples notebook illustrating the highlighted feature enhancements <SPAN><A href="https://github.com/SAP-samples/hana-ml-samples/blob/main/Python-API/pal/notebooks/25QRC03_2.26.ipynb" target="_blank" rel="nofollow noopener noreferrer">here 25QRC03_2.26.ipynb</A>.&nbsp; </SPAN></P> 2026-01-09T12:54:46.437000+01:00 https://community.sap.com/t5/technology-blog-posts-by-members/deploy-machine-learning-model-as-fast-api-to-cloud-foundry-btp-trial/ba-p/14307572 Deploy Machine Learning Model as Fast API to Cloud Foundry BTP Trial 2026-01-14T18:17:09.330000+01:00 rajeevgoswami1 https://community.sap.com/t5/user/viewprofilepage/user-id/141735 <P><STRONG>Deploy Machine Learning Model as Fast API to Cloud Foundry BTP Trial </STRONG></P><P><STRONG>Objective: </STRONG>This Blog helps an SAP developer who is new to Machine Learning and want to learn how a python machine learning model can be deployed to BTP trial account.&nbsp;</P><P>Later this model api can be consumed to SAP UI5 application.</P><P>Project structure was the challenging part for me&nbsp;being an on-prem ABAP consultant&nbsp; <span class="lia-unicode-emoji" title=":grinning_face:">😀</span> with zero knowledge in BTP deployment.&nbsp;</P><P>Note: This model is not an enterprise grade machine learning model. This is a beginner friendly model for learning purpose.</P><P><STRONG>Pre-requisite: </STRONG></P><P>BTP trial account and Business application Studio.</P><P><STRONG>Create Project Structure:</STRONG></P><P>Step 1: Create simple machine learning python program using scikit learn and expose it using FAST API.</P><P>My Project structure:</P><P>mypython/</P><P>|-- app.py&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# FastAPI python code</P><P>|-- requirements.txt&nbsp;&nbsp;&nbsp;&nbsp; # Python Dependencies</P><P>|-- Procfile&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Command for CF(Cloud Foundry) to start the web application</P><P>|--manifest.yml&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;# CF deployment config</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_0-1768409918460.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361411iF1FB5247434AEDD3/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_0-1768409918460.png" alt="rajeevgoswami1_0-1768409918460.png" /></span></P><P>&nbsp;</P><P>File 1: app.py</P><P>This is a sample python program to create <STRONG>REST API</STRONG>&nbsp;that serves a machine learning model for classifying Iris flowers using The&nbsp;<STRONG>Gaussian Naive Bayes</STRONG>&nbsp;classifier.</P><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_1-1768409918467.png" style="width: 455px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361413i0CE8DEA162DEB8EC/image-dimensions/455x488/is-moderation-mode/true?v=v2" width="455" height="488" role="button" title="rajeevgoswami1_1-1768409918467.png" alt="rajeevgoswami1_1-1768409918467.png" /></span></P><P>&nbsp;</P><P>File 2: manifest.yml</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_2-1768409918469.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361412iF792FACB5929E914/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_2-1768409918469.png" alt="rajeevgoswami1_2-1768409918469.png" /></span></P><P>&nbsp;</P><P>File 3: Procfile</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_3-1768409918470.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361415i4F31F584C37586CD/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_3-1768409918470.png" alt="rajeevgoswami1_3-1768409918470.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>File 4: requirement.txt</P><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_4-1768409918471.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361416i7836FC8B49C7099E/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_4-1768409918471.png" alt="rajeevgoswami1_4-1768409918471.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG>Local Testing:</STRONG></P><P>Test the program before deployment to cloud foundry.</P><P>Click on the app.py file and right-click -&gt; Open in integrated Terminal. Terminal will open with current project directory.</P><UL><LI>Run command pip install -r requirements.txt</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_5-1768409918472.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361414i96E099A52A47AA7F/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_5-1768409918472.png" alt="rajeevgoswami1_5-1768409918472.png" /></span></P><P>Run command to test the fast api locally.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_13-1768410545449.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361426i556835FE29C27C9A/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_13-1768410545449.png" alt="rajeevgoswami1_13-1768410545449.png" /></span></P><P>&nbsp;</P><P>Code written is used for running the fast api locally</P><P># Run locally with: app.py</P><P>if __name__ == "__main__":</P><P>&nbsp; &nbsp; import uvicorn</P><P>&nbsp; &nbsp; uvicorn.run(app, host="0.0.0.0", port=8000)</P><P>&nbsp;</P><P>Hover to the link and click on follow link.</P><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_6-1768409918475.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361419i818A5B9C1009A970/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_6-1768409918475.png" alt="rajeevgoswami1_6-1768409918475.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>Below links will open.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_7-1768409918476.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361417i191CE3E3FFE06B90/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_7-1768409918476.png" alt="rajeevgoswami1_7-1768409918476.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>Add postfix \docs to test the application in browser.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_8-1768409918479.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361418i96440A43FEF7C11C/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_8-1768409918479.png" alt="rajeevgoswami1_8-1768409918479.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG>Deployment to Cloud Foundry</STRONG></P><UL><LI>In Business application studio, First login to cloud foundry</LI></UL><P>Press ctrl+shift+P to login to cloud foundry and login to cloud foundry.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_9-1768409918480.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361420i920193BE710AB5CC/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_9-1768409918480.png" alt="rajeevgoswami1_9-1768409918480.png" /></span></P><P>&nbsp;</P><UL><LI>Push command to final deploy to cloud foundry</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_10-1768409918480.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361421iEA2C36985C5C1118/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_10-1768409918480.png" alt="rajeevgoswami1_10-1768409918480.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>In case deployment is fails logs can be checked by</P><P>&nbsp;cf logs &lt; CF app name e.g. &gt; --recent</P><P>&nbsp;</P><P><STRONG>Check the deployed app in cloud foundry</STRONG></P><P>Go to the sub-account and the dev space where you have deployed the app you can get all the necessary details.</P><P>&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_11-1768409918490.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361422iB7CB56F5EF5F2765/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_11-1768409918490.png" alt="rajeevgoswami1_11-1768409918490.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>You can click on the api link and check test the api by added \docs postfix.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rajeevgoswami1_12-1768409918494.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361423iB3E4C2A6F5F23262/image-size/medium/is-moderation-mode/true?v=v2&amp;px=400" role="button" title="rajeevgoswami1_12-1768409918494.png" alt="rajeevgoswami1_12-1768409918494.png" /></span></P><P>&nbsp;</P><P><STRONG>Conclusion:</STRONG></P><P>You got the basic understanding how python api gets deployed to cloud foundry which can consumed by the UI5 or CAP application for integrating it to business application.</P><P>Happy Learning!</P><P>Reference:</P><P><A href="https://developers.sap.com/tutorials/btp-cf-buildpacks-python-create.html" target="_blank" rel="noopener noreferrer">Create an Application with Cloud Foundry Python Buildpack | SAP Tutorials</A></P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG>&nbsp;</STRONG></P> 2026-01-14T18:17:09.330000+01:00 https://community.sap.com/t5/technology-blog-posts-by-members/displaying-sap-analytics-cloud-kpi-tiles-from-stories-using-rest-apis/ba-p/14320914 Displaying SAP Analytics Cloud KPI Tiles from Stories Using REST APIs 2026-02-04T08:51:58.622000+01:00 Ajay105 https://community.sap.com/t5/user/viewprofilepage/user-id/2102459 <H2 id="toc-hId-1789469326">Introduction</H2><P class="lia-align-justify" style="text-align : justify;">SAP Analytics Cloud (SAC) is widely used by organizations to provide interactive storytelling and track the business KPIs with advanced visualizations. However, companies often need to obtain KPI information in ways other than the SAC user interface, including in custom web apps. Although SAC does not support the direct embedding of KPI tiles into external apps, it does provide REST APIs that allow programmatic access to widget-level data from SAC stories. These APIs can be used to collect and display KPI tile data, including number (value), number state (status), title, and subtitle, in a bespoke user interface.</P><P class="lia-align-justify" style="text-align : justify;">In this blog, I walk through a detailed, end-to-end implementation that illustrates how to fetch KPI tile data from a SAP Analytics Cloud story using the <FONT face="courier new,courier">widgetquery/getWidgetData</FONT> REST API. The approach uses Python for backend processing and Flask as a lightweight web framework to securely call SAC APIs and output KPI values on a web page.</P><H2 id="toc-hId-1592955821">Configuration of the Project</H2><P class="lia-align-justify" style="text-align : justify;">We may test API access and retrieve KPI tile data using a straightforward Python script before developing the Flask application. This program shows you how to:</P><OL class="lia-align-justify" style="text-align : justify;"><LI>Use OAuth 2.0 to authenticate with SAC</LI><LI>Acquire a token of access</LI><LI>Use the SAC widgetquery/getWidgetData RESTAPI.</LI><LI>Show the console’s KPI values</LI></OL><pre class="lia-code-sample language-python"><code>import requests import webbrowser import urllib.parse # ---------------- CONFIG ---------------- TENANT_URL = "https://&lt;your-tenant&gt;.hanacloudservices.cloud.sap" CLIENT_ID = "&lt;YOUR_CLIENT_ID&gt;" CLIENT_SECRET = "&lt;YOUR_CLIENT_SECRET&gt;" AUTHORIZATION_ENDPOINT = "https://&lt;your-tenant&gt;.hana.ondemand.com/oauth/authorize" TOKEN_ENDPOINT = "https://&lt;your-tenant&gt;.hana.ondemand.com/oauth/token" REDIRECT_URI = "https://your-app-domain.com/oauth/callback" # used only to capture code manually STORY_ID = "&lt;your-storyid&gt;" WIDGET_IDS = [ "Chart_1", "Chart_2", "Chart_3", "Chart_4", "Chart_5", "Chart_6", "Chart_8" ] # ---------------- STEP 1: LOGIN ---------------- params = { "response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI } auth_url = AUTHORIZATION_ENDPOINT + "?" + urllib.parse.urlencode(params) print("\n Opening browser for SAC login...") webbrowser.open(auth_url) code = input("\n Paste the authorization code here: ").strip() # ---------------- STEP 2: TOKEN ---------------- payload = { "grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } token_resp = requests.post( TOKEN_ENDPOINT, data=payload, headers={"Content-Type": "application/x-www-form-urlencoded"} ) token_resp.raise_for_status() access_token = token_resp.json()["access_token"] print("\n Access token received") # ---------------- STEP 3: FETCH KPI ---------------- headers = { "Authorization": f"Bearer {access_token}", "Accept": "application/json" } print("\n KPI VALUES\n" + "-" * 40) for widget_id in WIDGET_IDS: url = f"{TENANT_URL}/widgetquery/getWidgetData" params = { "storyId": STORY_ID, "widgetId": widget_id, "type": "kpiTile" } r = requests.get(url, headers=headers, params=params) if r.ok: data = r.json() number = data.get("number", "N/A") title = data.get("title", widget_id) print(f"{title}: {number}") else: print(f"{widget_id}: Error") print("\n Done")</code></pre><P class="lia-align-justify" style="text-align : justify;"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="pythonkpi_output_terminal.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368640iF530E352835892E6/image-size/large?v=v2&amp;px=999" role="button" title="pythonkpi_output_terminal.png" alt="pythonkpi_output_terminal.png" /></span></P><P class="lia-align-justify" style="text-align : justify;">&nbsp;<SPAN>How this operate</SPAN></P><OL class="lia-align-justify" style="text-align : justify;"><LI>Login Step:&nbsp;Launches a web browser to obtain the permission code and log into SAC.</LI><LI>Token Step: Provides an access token in exchange for the authorization code.</LI><LI>Fetch KPI Step: Prints the KPI number and title after contacting the SAC REST API for each widget ID.</LI></OL><P class="lia-align-justify" style="text-align : justify;"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="RESTAPI_Flow_diagram.jpg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368634i0FF9C05120008397/image-size/large?v=v2&amp;px=999" role="button" title="RESTAPI_Flow_diagram.jpg" alt="RESTAPI_Flow_diagram.jpg" /></span></P><P class="lia-align-justify" style="text-align : justify;">&nbsp;</P><H2 id="toc-hId-1396442316">Code of Application</H2><P class="lia-align-justify" style="text-align : justify;">The full Flask application that is used to retrieve KPI tile data and authenticate with SAP Analytics Cloud is shown below. This code manages widget data fetching, token retrieval, login, and creates an eye-catching KPI dashboard in the browser.</P><pre class="lia-code-sample language-markup"><code>HTML_TEMPLATE = """ &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;title&gt;SAC KPI Dashboard&lt;/title&gt; &lt;style&gt; body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f4f6f8; margin: 0; padding: 0; } h1 { text-align: center; margin-top: 20px; color: #333; } .container { display: flex; flex-wrap: wrap; justify-content: center; margin: 40px auto; max-width: 1200px; gap: 20px; } .card { color: #fff; width: 250px; height: 150px; border-radius: 16px; box-shadow: 0 10px 20px rgba(0,0,0,0.2); display: flex; flex-direction: column; justify-content: center; align-items: center; transition: transform 0.3s, box-shadow 0.3s; cursor: pointer; text-align: center; padding: 10px; } .card:hover { transform: translateY(-10px); box-shadow: 0 20px 30px rgba(0,0,0,0.3); } .number { font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 90%; } .title { font-size: 1em; margin-top: 10px; color: #e0e0e0; } /* Rainbow colors for cards */ .rainbow-0 { background: linear-gradient(135deg, #ff6b6b, #f06595); } .rainbow-1 { background: linear-gradient(135deg, #feca57, #ff9f43); } .rainbow-2 { background: linear-gradient(135deg, #1dd1a1, #10ac84); } .rainbow-3 { background: linear-gradient(135deg, #54a0ff, #2e86de); } .rainbow-4 { background: linear-gradient(135deg, #5f27cd, #341f97); } .rainbow-5 { background: linear-gradient(135deg, #ee5253, #c0392b); } .rainbow-6 { background: linear-gradient(135deg, #48dbfb, #00d2d3); } &lt;/style&gt; &lt;script&gt; // Adjust font size based on length function adjustFontSize() { const numbers = document.querySelectorAll('.number'); numbers.forEach(num =&gt; { const length = num.innerText.length; if(length &lt;= 5) num.style.fontSize = '2.5em'; else if(length &lt;= 8) num.style.fontSize = '2em'; else num.style.fontSize = '1.5em'; }); } window.onload = adjustFontSize; &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;h1&gt;SAP Analytics Cloud - RESTAPI Fetched Sales KPI Dashboard&lt;/h1&gt; &lt;div class="container"&gt; {% for kpi in kpis %} &lt;div class="card rainbow-{{ loop.index0 % 7 }}"&gt; &lt;div class="number"&gt;{{ kpi.number }}&lt;/div&gt; &lt;div class="title"&gt;{{ kpi.title }}&lt;/div&gt; &lt;/div&gt; {% endfor %} &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; """</code></pre><pre class="lia-code-sample language-python"><code>from flask import Flask, render_template_string import requests import urllib.parse import webbrowser # ---------------- CONFIG ---------------- TENANT_URL = "https://yourtenant.hanacloudservices.cloud.sap" CLIENT_ID = "&lt;YOUR_CLIENT_ID&gt;" CLIENT_SECRET = "&lt;YOUR_CLIENT_SECRET&gt;" AUTHORIZATION_ENDPOINT = "https://yourtenant.hana.ondemand.com/oauth/authorize" TOKEN_ENDPOINT = "https://yourtenant.hana.ondemand.com/oauth/token" REDIRECT_URI = "https://your-app-domain.com/oauth/callback" STORY_ID = "&lt;your_storyid&gt;" WIDGET_IDS = [ "Chart_1", "Chart_2", "Chart_3", "Chart_4", "Chart_5", "Chart_6", "Chart_8" ] # ---------------- FLASK APP ---------------- app = Flask(__name__) def get_access_token(): # Step 1: login manually params = {"response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI} auth_url = AUTHORIZATION_ENDPOINT + "?" + urllib.parse.urlencode(params) print("\nOpen this URL in browser to login to SAC:") print(auth_url) webbrowser.open(auth_url) code = input("\nPaste the authorization code here: ").strip() # Step 2: get token payload = { "grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } r = requests.post(TOKEN_ENDPOINT, data=payload, headers={"Content-Type": "application/x-www-form-urlencoded"}) r.raise_for_status() return r.json()["access_token"] def fetch_kpis(token): headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"} kpis = [] for widget_id in WIDGET_IDS: url = f"{TENANT_URL}/widgetquery/getWidgetData" params = {"storyId": STORY_ID, "widgetId": widget_id, "type": "kpiTile"} r = requests.get(url, headers=headers, params=params) if r.ok: data = r.json() kpis.append({ "title": data.get("title", widget_id), "number": data.get("number", "N/A") }) else: kpis.append({"title": widget_id, "number": "Error"}) return kpis # HTML Code will be written Here @app.route("/") def dashboard(): token = get_access_token() kpis = fetch_kpis(token) return render_template_string(HTML_TEMPLATE, kpis=kpis) if __name__ == "__main__": app.run(debug=True,port=8000)</code></pre><P class="lia-align-justify" style="text-align : justify;"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="webpageoutput_terminal.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368641iF1104ACD8CBAC86C/image-size/large?v=v2&amp;px=999" role="button" title="webpageoutput_terminal.png" alt="webpageoutput_terminal.png" /></span></P><P class="lia-align-justify" style="text-align : justify;">&nbsp;<SPAN>An explanation of the code</SPAN></P><OL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="courier new,courier">/route</FONT> logic<UL><LI>To manually retrieve an OAuth token, use <FONT face="courier new,courier">get_access_token()</FONT>.</LI><LI>Uses <FONT face="courier new,courier">fetch_kpis()</FONT> to retrieve KPI tile data.</LI><LI>Uses the HTML_TEMPLATE to render all KPIs with rainbow-colored cards.</LI></UL></LI><LI>Managing OAuth (<FONT face="courier new,courier">get_access_token</FONT>)<UL><LI>Launches the browser's SAC login.</LI><LI>The authorization code is pasted by the user. To call SAC REST APIs, code is exchanged for access tokens.</LI></UL></LI><LI>Data retrieval for widgets (<FONT face="courier new,courier">fetch_kpis</FONT>)<UL><LI>Repeats over every WIDGET_IDS.</LI><LI>For every widget, the <FONT face="courier new,courier">widgetquery/getWidgetData</FONT> endpoint is called.</LI><LI>Gathers the number and title for the display.</LI></UL></LI><LI>Using HTML Templates for UI rendering<UL><LI>Flex arrangement is used for tiles.</LI><LI>Each KPI card has a rainbow gradient background.</LI><LI>For a contemporary appearance, use shadow and hover effects.</LI><LI>The length of the integer automatically modifies the font size.</LI></UL></LI></OL><H2 id="toc-hId-1199928811">Result/Output</H2><P class="lia-align-justify" style="text-align : justify;">The KPI data is retrieved and shown in a personalized web dashboard after the program has launched and the user has successfully logged in using SAP Analytics Cloud OAuth.</P><H6 id="toc-hId-1519746182">SAP Analytics Cloud Story KPI Tiles</H6><P class="lia-align-justify" style="text-align : justify;">The original KPI tiles as they appear in the SAP Analytics Cloud narrative are depicted in the image below. Business users in SAC are in charge of configuring and maintaining these KPIs.<BR /><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="SAC_Story_pic.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368642i28BB476CF6F1D331/image-size/large?v=v2&amp;px=999" role="button" title="SAC_Story_pic.png" alt="SAC_Story_pic.png" /></span></P><P class="lia-align-justify" style="text-align : justify;">&nbsp;</P><H6 id="toc-hId-1323232677">Custom Web Dashboard with KPI Tiles</H6><P class="lia-align-justify" style="text-align : justify;">The <FONT face="courier new,courier">widgetquery/getWidgetData</FONT> REST API is used to retrieve the same KPI values, which are then shown in a specially created web application. Custom Web Dashboard Screenshot<BR /><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="webpage_pic_restapi.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368643i9989EF78FB6413D9/image-size/large?v=v2&amp;px=999" role="button" title="webpage_pic_restapi.png" alt="webpage_pic_restapi.png" /></span></P><H3 id="toc-hId-739471015">Important Findings</H3><P class="lia-align-justify" style="text-align : justify;">The KPI numbers in the SAC story and the web dashboard are same.<BR /><STRONG>Every KPI tile shows:</STRONG></P><OL class="lia-align-justify" style="text-align : justify;"><LI>Value or Number&nbsp;</LI><LI>The title</LI></OL><P class="lia-align-justify" style="text-align : justify;">The website's dashboard uses the following to improve visualization:</P><OL class="lia-align-justify" style="text-align : justify;"><LI>Gradient cards with rainbows</LI><LI>Hover animations and shadows</LI><LI>Adaptable design</LI></OL><P class="lia-align-justify" style="text-align : justify;">This proves to the secure consumption and reuse of SAC KPI data outside of the SAC user interface without requiring the duplication of business logic.</P><H2 id="toc-hId-413874791">Conclusion</H2><P class="lia-align-justify" style="text-align : justify;">In this blog post, we shown a simple yet effective technique for extracting SAP REST APIs are used to tile Analytics Cloud KPI data and display it on a special webpage.</P><H6 id="toc-hId-733692162">Important findings:</H6><UL><LI><P>SAC KPIs can be consumed using custom dashboards outside of the SAC UI.</P></LI><LI><P>Python provides an adaptable and lightweight backend for API integration.&nbsp;REST APIs enable KPI tracking in real-time, while front-end. Style increases visibility.</P></LI><LI><P>Setting up the environment and security is essential for a safe execution.</P></LI><LI><P class="lia-align-justify" style="text-align : justify;">SAC insights can be incorporated into executive dashboards, intranet portals, or external web apps, businesses are able to give users a consolidated view of key performance indicators without having to Launch SAC directly.</P></LI></UL> 2026-02-04T08:51:58.622000+01:00