In this series of blogs, I will showcase how to integrate SAP AI Core services into SAP Mobile Development Kit (MDK) to develop mobile applications with AI capabilities.
Part 1: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – SetupPart 2: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Business Use CasesPart 3: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Measurement ReadingsPart 4: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Anomaly Detection and Maintenance Guidelines Generation(Current Blog)Part 5: SAP MDK integration with SAP AI Core services – Retrieval Augmented Generation
In the previous blog, we showcased how to preprocess the images, transmit them to the SAP AI Core service for analysis, and manage the response to extract and store the meter readings. In this blog, we will explore how to detect anomalies and generate maintenance guidelines using a similar approach.
SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Anomaly Detection and Maintenance Guidelines Generation
The main task is to create an MDK rule for detecting anomalies, such as automatically detecting whether there is any water leaking from pictures taken by the maintenance technician on site. This rule preprocesses the images, sends them to the SAP AI Core service, and handles the response to detect the anomalies. It also generates a description of the anomalies and provides recommendations on how to fix them.
Code snippet of SendAnomalyDetectionRequest.js is as below
import GetMeterReadingResults from ‘./GetMeterReadingResults’;
import BinaryToBase64 from ‘../Utils/BinaryToBase64’;
const IMAGE_URL_PREFIX = “data:image/jpeg;base64,”;
const schema = {
“type”: “object”,
“properties”: {
“anomalyDetected”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“title”: {
“type”: “string”,
“description”: “Short title of the anomaly.”
},
“anomalyDescription”: {
“type”: “string”,
“description”: “Short description of the anomaly.”
},
“tasks”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“taskTitle”: {
“type”: “string”,
“description”: “Short title of the task.”
},
“shortDescription”: {
“type”: “string”,
“description”: “Short description of the action needed to fix the anomaly.”
},
“description”: {
“type”: “string”,
“description”: “Clear and concise explanation of the action needed to fix the anomaly. Do not use numbering. Display one paragraph only.”
},
“priority”: {
“type”: “string”,
“description”: “Indicate the urgency of the task (e.g., high, medium, low)”
}
},
“required”: [“taskTitle”, “shortDescription”, “description”, “priority”]
}
}
},
“required”: [“title”, “anomalyDescription”, “tasks”]
}
}
},
“required”: [“anomalyDetected”]
}
export default async function SendAnomalyDetectionRequest(context) {
const meterReadingResults = GetMeterReadingResults(context);
const prompt =
`Analyze the provided image(s) thoroughly to detect any anomalies or irregularities.
Identify areas that deviate from the expected standard or exhibit unusual features. There might not be any anomalies.
${meterReadingResults.length ? `The following are the meter readings of the component in JSON – ${JSON.stringify(meterReadingResults, null, 2)}. This meter reading is optional and not necessary required for anomaly detection.` : ‘There is no meter reading results’}
Identify the detected anomaly with its title.
Create a to-do list (minimum 1, maximum 2) to address and fix each detected anomaly.
Include the following details for each task:
– Title: Short title of the task
– Task Description: Clear and concise explanation of the action needed to fix the anomaly. Include the meter reading if the meter reading results is defined.
– Priority: Indicate the urgency of the task (e.g., high, medium, low).
`;
const method = “POST”;
const body = {
messages: [
{
“role”: “user”,
“content”: [
{
“type”: “text”,
“text”: prompt
}
]
}
],
temperature: 0.2,
max_tokens: 1024,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
functions: [{ name: “format_response”, parameters: schema }],
function_call: { name: “format_response” },
};
const headers = {
“content-type”: “application/json”,
“AI-Resource-Group”: “default”
}
const service = “/MDKDevApp/Services/AzureOpenAI.service”;
const path = “/chat/completions?api-version=2024-02-01”;
const pageProxy = context.getPageProxy();
const attachments = pageProxy.evaluateTargetPath(“#Control:AttachmentAnomalyDetection/#Value”);
if (!attachments || attachments.length === 0) {
alert(“Please attach an image”);
return;
}
// Preprocess images
attachments.forEach((attachment) => {
let base64String = BinaryToBase64(context, attachment.content);
const imageData = {
“type”: “image_url”,
“image_url”: {
“url”: `${IMAGE_URL_PREFIX}${base64String}`
}
}
body.messages[0].content.push(imageData);
});
// Send API request
console.log(“Sending anomaly detection request…”);
return context.executeAction({
“Name”: “/MDKDevApp/Actions/SendRequest.action”,
“Properties”: {
“Target”: {
“Path”: path,
“RequestProperties”: {
“Method”: method,
“Headers”: headers,
“Body”: JSON.stringify(body)
},
“Service”: service
},
“ActivityIndicatorText”: “Sending anomaly detection request…”,
}
}).then(async response => {
const results = JSON.parse(response.data.choices[0].message.function_call.arguments);
const mainPage = context.evaluateTargetPathForAPI(“#Page:GeneratedServiceOrderPage”);
const cd = mainPage.getClientData();
cd.AnomalyResults = results.anomalyDetected;
console.log(“Anomaly results:”, results.anomalyDetected);
console.log(“”);
// Get image type for manual input (limitation to only one type of equipment per request)
const description = JSON.stringify(results.anomalyDetected[0], null, 2);
console.log(“Fetching equipment name…”);
let spinnerID = context.showActivityIndicator(‘Fetching equipment name…’);
const equipmentName = await getEquipmentName(context, description);
context.dismissActivityIndicator(spinnerID);
cd.EquipmentNameResults = equipmentName;
console.log(“Equipment Name:”, equipmentName);
console.log(“”);
return true;
});
}
async function getEquipmentName(context, description) {
const functionName = ‘getRagResponse’;
const serviceName = ‘/MDKDevApp/Services/CAPVectorEngine.service’;
const parameters = {
“value”: `The descibed equipment type has the following anormaly: ${description}. What is the unique name of the equipment based on the anormaly described, it must only give the unique name of the equipment only without any explaination.`
};
let oFunction = { Name: functionName, Parameters: parameters };
try {
const response = await context.callFunction(serviceName, oFunction);
const responseObj = JSON.parse(response);
return responseObj.completion.content;
} catch (error) {
console.log(“Error:”, error.message);
}
return;
}
A step-by-step breakdown:
Imports
BinaryToBase64: A utility function to convert binary data to a Base64 string.
Constants
IMAGE_URL_PREFIX: A prefix for Base64 encoded images to be used in data URLs.schema: A JSON schema defining the structure of the response expected from the AI service. It includes properties for detected anomalies, tasks to fix them, and their priorities.
JSON Schema
The schema defines the expected structure of the response from the AI service:
anomalyDetected: An array of objects, each representing an anomaly.taskTitle: A short title of the task.shortDescription: A short description of the action needed to fix the anomaly.description: A detailed explanation of the action needed to fix the anomaly.priority: The urgency of the task (e.g., high, medium, low).title: A short title of the anomaly.anomalyDescription: A short description of the anomaly.tasks: An array of tasks to fix the anomaly.
Main Function: SendAnomalyDetectionRequest
Get Meter Reading Results:Retrieves meter reading results using GetMeterReadingResults(context).
Create Prompt:Constructs a prompt string that includes instructions for the AI service to analyze images and detect anomalies. It also includes meter reading results if available.
API Request Configuration:Defines the HTTP method (POST), request body, headers, service path, and other properties for the API request.The request body includes the prompt and specifies the schema for the expected response.
Check Attachments:Retrieves image attachments from the context. If no images are attached, it alerts the user to attach an image and exits.Preprocess Images:Converts each image attachment to a Base64 string and appends it to the request body.
Send API Request:Sends the request to the AI service using context.executeAction.Handles the response by parsing the detected anomalies and storing them in the client data of the main page.Logs the anomaly results.
Fetch Equipment Name:Calls getEquipmentName to fetch the unique name of the equipment based on the described anomaly.Displays an activity indicator while fetching the equipment name and logs the result.
Helper Function: getEquipmentName
Sends a request to SAP HANA Vector database service to get the unique name of the equipment based on the anomaly description.Handles the response and returns the equipment name.In the next blog, we will delve into the details of the SAP HANA Vector database and Retrieval Augmented Generation (RAG).
The function SendAnomalyDetectionRequest automates the process of detecting anomalies in images taken by maintenance technicians. It preprocesses the images, sends them to an AI service, and handles the response to identify anomalies and generate tasks to fix them. The function also fetches the unique name of the equipment based on the detected anomalies.
In one of the pages of your MDK app, you can specify the user interface to bind the anomaly detection and maintenance guidelines result from AI. For example,
{
“Sections”: [
{
“Header”: {
“UseTopPadding”: false
},
“_Type”: “Section.Type.Image”,
“_Name”: “Placeholder”,
“Image”: “/MDKDevApp/Images/placeholder.jpg”,
“ContentMode”: “ScaleAspectFill”,
“Height”: 500
},
{
“_Type”: “Section.Type.ObjectCollection”,
“_Name”: “AnomalyDetected”,
“Header”: {
“Caption”: “Anomalies Detected”,
“UseTopPadding”: false
},
“Layout”: {
“NumberOfColumns”: 1
},
“ObjectCell”: {
“_Name”: “Anomaly”,
“Title”: “{anomalyTitle}”,
“Subhead”: “Problem description:”,
“Footnote”: “{description}”,
“PreserveIconStackSpacing”: false,
“AccessoryType”: “disclosureIndicator”,
“OnPress”: “/MDKDevApp/Rules/ImageAnalysis/ShowAnomalyAlert.js”
},
“EmptySection”: {
“Caption”: “No data available.”,
“FooterVisible”: true
},
“Target”: “/MDKDevApp/Rules/ImageAnalysis/GetAnomalyData.js”,
“Separators”: {
“HeaderSeparator”: true
}
},
{
“_Type”: “Section.Type.ObjectCollection”,
“_Name”: “Operations”,
“Layout”: {
“NumberOfColumns”: 1
},
“Header”: {
“Caption”: “Operations Recommended by AI”
},
“ObjectCell”: {
“_Name”: “Operation”,
“Title”: “{taskTitle}”,
“Footnote”: “{description}”,
“Subhead”: “{shortDescription}”,
“PreserveIconStackSpacing”: false,
“AccessoryButtonText”: “/MDKDevApp/Rules/ImageAnalysis/GetAccessoryButtonText.js”,
“OnPress”: “/MDKDevApp/Rules/ImageAnalysis/NavToOperationDetail.js”
},
“Visible”: “/MDKDevApp/Rules/ImageAnalysis/OperationsVisibility.js”,
“Target”: “/MDKDevApp/Rules/ImageAnalysis/GetOperationsData.js”,
“Separators”: {
“HeaderSeparator”: true
}
}
],
“_Name”: “SectionedTable1”,
“_Type”: “Control.Type.SectionedTable”
}
Code snippet of GetAnomalyData.js
export default function GetAnomalyData(context) {
const pageProxy = context.evaluateTargetPathForAPI(“#Page:GeneratedServiceOrderPage”);
const cd = pageProxy.getClientData();
if (cd && cd.AnomalyData) {
for (const anomalyData of cd.AnomalyData) {
//workaround to avoid binding conflict with earlier page when calling #property:…
anomalyData.anomalyTitle = anomalyData.title
}
return cd.AnomalyData;
} else {
return [];
}
}
Code snippet of GetOperationsData.js
export default function GetOperationsData(context) {
const pageProxy = context.evaluateTargetPathForAPI(“#Page:MDKGenAIPage”);
const cd = pageProxy.getClientData();
if (cd && cd.OperationsData) {
for (const [index, value] of cd.OperationsData.entries()) {
value.index = index;
let priority = value.priority;
value.priority = priority && priority[0].toUpperCase() + priority.slice(1);
}
return cd.OperationsData;
} else {
return [];
}
}
With the above code snippets, we showcase how to preprocess the images, transmit them to the SAP AI Core service for analysis, and manage the response to extract the detected anomalies. It also generates a description of the anomalies and provides maintenance guidelines on how to fix them.
In our next blogs, we will explore how to record and track work orders and operations using voice input with Generative AI and Retrieval Augmented Generation.
In this series of blogs, I will showcase how to integrate SAP AI Core services into SAP Mobile Development Kit (MDK) to develop mobile applications with AI capabilities.Part 1: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – SetupPart 2: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Business Use CasesPart 3: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Measurement ReadingsPart 4: SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Anomaly Detection and Maintenance Guidelines Generation(Current Blog)Part 5: SAP MDK integration with SAP AI Core services – Retrieval Augmented GenerationIn the previous blog, we showcased how to preprocess the images, transmit them to the SAP AI Core service for analysis, and manage the response to extract and store the meter readings. In this blog, we will explore how to detect anomalies and generate maintenance guidelines using a similar approach.SAP Mobile Development Kit (MDK) integration with SAP AI Core services – Anomaly Detection and Maintenance Guidelines GenerationThe main task is to create an MDK rule for detecting anomalies, such as automatically detecting whether there is any water leaking from pictures taken by the maintenance technician on site. This rule preprocesses the images, sends them to the SAP AI Core service, and handles the response to detect the anomalies. It also generates a description of the anomalies and provides recommendations on how to fix them.Code snippet of SendAnomalyDetectionRequest.js is as below import GetMeterReadingResults from ‘./GetMeterReadingResults’;
import BinaryToBase64 from ‘../Utils/BinaryToBase64’;
const IMAGE_URL_PREFIX = “data:image/jpeg;base64,”;
const schema = {
“type”: “object”,
“properties”: {
“anomalyDetected”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“title”: {
“type”: “string”,
“description”: “Short title of the anomaly.”
},
“anomalyDescription”: {
“type”: “string”,
“description”: “Short description of the anomaly.”
},
“tasks”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“taskTitle”: {
“type”: “string”,
“description”: “Short title of the task.”
},
“shortDescription”: {
“type”: “string”,
“description”: “Short description of the action needed to fix the anomaly.”
},
“description”: {
“type”: “string”,
“description”: “Clear and concise explanation of the action needed to fix the anomaly. Do not use numbering. Display one paragraph only.”
},
“priority”: {
“type”: “string”,
“description”: “Indicate the urgency of the task (e.g., high, medium, low)”
}
},
“required”: [“taskTitle”, “shortDescription”, “description”, “priority”]
}
}
},
“required”: [“title”, “anomalyDescription”, “tasks”]
}
}
},
“required”: [“anomalyDetected”]
}
export default async function SendAnomalyDetectionRequest(context) {
const meterReadingResults = GetMeterReadingResults(context);
const prompt =
`Analyze the provided image(s) thoroughly to detect any anomalies or irregularities.
Identify areas that deviate from the expected standard or exhibit unusual features. There might not be any anomalies.
${meterReadingResults.length ? `The following are the meter readings of the component in JSON – ${JSON.stringify(meterReadingResults, null, 2)}. This meter reading is optional and not necessary required for anomaly detection.` : ‘There is no meter reading results’}
Identify the detected anomaly with its title.
Create a to-do list (minimum 1, maximum 2) to address and fix each detected anomaly.
Include the following details for each task:
– Title: Short title of the task
– Task Description: Clear and concise explanation of the action needed to fix the anomaly. Include the meter reading if the meter reading results is defined.
– Priority: Indicate the urgency of the task (e.g., high, medium, low).
`;
const method = “POST”;
const body = {
messages: [
{
“role”: “user”,
“content”: [
{
“type”: “text”,
“text”: prompt
}
]
}
],
temperature: 0.2,
max_tokens: 1024,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
functions: [{ name: “format_response”, parameters: schema }],
function_call: { name: “format_response” },
};
const headers = {
“content-type”: “application/json”,
“AI-Resource-Group”: “default”
}
const service = “/MDKDevApp/Services/AzureOpenAI.service”;
const path = “/chat/completions?api-version=2024-02-01”;
const pageProxy = context.getPageProxy();
const attachments = pageProxy.evaluateTargetPath(“#Control:AttachmentAnomalyDetection/#Value”);
if (!attachments || attachments.length === 0) {
alert(“Please attach an image”);
return;
}
// Preprocess images
attachments.forEach((attachment) => {
let base64String = BinaryToBase64(context, attachment.content);
const imageData = {
“type”: “image_url”,
“image_url”: {
“url”: `${IMAGE_URL_PREFIX}${base64String}`
}
}
body.messages[0].content.push(imageData);
});
// Send API request
console.log(“Sending anomaly detection request…”);
return context.executeAction({
“Name”: “/MDKDevApp/Actions/SendRequest.action”,
“Properties”: {
“Target”: {
“Path”: path,
“RequestProperties”: {
“Method”: method,
“Headers”: headers,
“Body”: JSON.stringify(body)
},
“Service”: service
},
“ActivityIndicatorText”: “Sending anomaly detection request…”,
}
}).then(async response => {
const results = JSON.parse(response.data.choices[0].message.function_call.arguments);
const mainPage = context.evaluateTargetPathForAPI(“#Page:GeneratedServiceOrderPage”);
const cd = mainPage.getClientData();
cd.AnomalyResults = results.anomalyDetected;
console.log(“Anomaly results:”, results.anomalyDetected);
console.log(“”);
// Get image type for manual input (limitation to only one type of equipment per request)
const description = JSON.stringify(results.anomalyDetected[0], null, 2);
console.log(“Fetching equipment name…”);
let spinnerID = context.showActivityIndicator(‘Fetching equipment name…’);
const equipmentName = await getEquipmentName(context, description);
context.dismissActivityIndicator(spinnerID);
cd.EquipmentNameResults = equipmentName;
console.log(“Equipment Name:”, equipmentName);
console.log(“”);
return true;
});
}
async function getEquipmentName(context, description) {
const functionName = ‘getRagResponse’;
const serviceName = ‘/MDKDevApp/Services/CAPVectorEngine.service’;
const parameters = {
“value”: `The descibed equipment type has the following anormaly: ${description}. What is the unique name of the equipment based on the anormaly described, it must only give the unique name of the equipment only without any explaination.`
};
let oFunction = { Name: functionName, Parameters: parameters };
try {
const response = await context.callFunction(serviceName, oFunction);
const responseObj = JSON.parse(response);
return responseObj.completion.content;
} catch (error) {
console.log(“Error:”, error.message);
}
return;
} A step-by-step breakdown:ImportsBinaryToBase64: A utility function to convert binary data to a Base64 string.ConstantsIMAGE_URL_PREFIX: A prefix for Base64 encoded images to be used in data URLs.schema: A JSON schema defining the structure of the response expected from the AI service. It includes properties for detected anomalies, tasks to fix them, and their priorities.JSON SchemaThe schema defines the expected structure of the response from the AI service:anomalyDetected: An array of objects, each representing an anomaly.taskTitle: A short title of the task.shortDescription: A short description of the action needed to fix the anomaly.description: A detailed explanation of the action needed to fix the anomaly.priority: The urgency of the task (e.g., high, medium, low).title: A short title of the anomaly.anomalyDescription: A short description of the anomaly.tasks: An array of tasks to fix the anomaly. Main Function: SendAnomalyDetectionRequestGet Meter Reading Results:Retrieves meter reading results using GetMeterReadingResults(context). Create Prompt:Constructs a prompt string that includes instructions for the AI service to analyze images and detect anomalies. It also includes meter reading results if available. API Request Configuration:Defines the HTTP method (POST), request body, headers, service path, and other properties for the API request.The request body includes the prompt and specifies the schema for the expected response. Check Attachments:Retrieves image attachments from the context. If no images are attached, it alerts the user to attach an image and exits.Preprocess Images:Converts each image attachment to a Base64 string and appends it to the request body. Send API Request:Sends the request to the AI service using context.executeAction.Handles the response by parsing the detected anomalies and storing them in the client data of the main page.Logs the anomaly results. Fetch Equipment Name:Calls getEquipmentName to fetch the unique name of the equipment based on the described anomaly.Displays an activity indicator while fetching the equipment name and logs the result. Helper Function: getEquipmentNameSends a request to SAP HANA Vector database service to get the unique name of the equipment based on the anomaly description.Handles the response and returns the equipment name.In the next blog, we will delve into the details of the SAP HANA Vector database and Retrieval Augmented Generation (RAG).The function SendAnomalyDetectionRequest automates the process of detecting anomalies in images taken by maintenance technicians. It preprocesses the images, sends them to an AI service, and handles the response to identify anomalies and generate tasks to fix them. The function also fetches the unique name of the equipment based on the detected anomalies.In one of the pages of your MDK app, you can specify the user interface to bind the anomaly detection and maintenance guidelines result from AI. For example, {
“Sections”: [
{
“Header”: {
“UseTopPadding”: false
},
“_Type”: “Section.Type.Image”,
“_Name”: “Placeholder”,
“Image”: “/MDKDevApp/Images/placeholder.jpg”,
“ContentMode”: “ScaleAspectFill”,
“Height”: 500
},
{
“_Type”: “Section.Type.ObjectCollection”,
“_Name”: “AnomalyDetected”,
“Header”: {
“Caption”: “Anomalies Detected”,
“UseTopPadding”: false
},
“Layout”: {
“NumberOfColumns”: 1
},
“ObjectCell”: {
“_Name”: “Anomaly”,
“Title”: “{anomalyTitle}”,
“Subhead”: “Problem description:”,
“Footnote”: “{description}”,
“PreserveIconStackSpacing”: false,
“AccessoryType”: “disclosureIndicator”,
“OnPress”: “/MDKDevApp/Rules/ImageAnalysis/ShowAnomalyAlert.js”
},
“EmptySection”: {
“Caption”: “No data available.”,
“FooterVisible”: true
},
“Target”: “/MDKDevApp/Rules/ImageAnalysis/GetAnomalyData.js”,
“Separators”: {
“HeaderSeparator”: true
}
},
{
“_Type”: “Section.Type.ObjectCollection”,
“_Name”: “Operations”,
“Layout”: {
“NumberOfColumns”: 1
},
“Header”: {
“Caption”: “Operations Recommended by AI”
},
“ObjectCell”: {
“_Name”: “Operation”,
“Title”: “{taskTitle}”,
“Footnote”: “{description}”,
“Subhead”: “{shortDescription}”,
“PreserveIconStackSpacing”: false,
“AccessoryButtonText”: “/MDKDevApp/Rules/ImageAnalysis/GetAccessoryButtonText.js”,
“OnPress”: “/MDKDevApp/Rules/ImageAnalysis/NavToOperationDetail.js”
},
“Visible”: “/MDKDevApp/Rules/ImageAnalysis/OperationsVisibility.js”,
“Target”: “/MDKDevApp/Rules/ImageAnalysis/GetOperationsData.js”,
“Separators”: {
“HeaderSeparator”: true
}
}
],
“_Name”: “SectionedTable1”,
“_Type”: “Control.Type.SectionedTable”
} Code snippet of GetAnomalyData.js export default function GetAnomalyData(context) {
const pageProxy = context.evaluateTargetPathForAPI(“#Page:GeneratedServiceOrderPage”);
const cd = pageProxy.getClientData();
if (cd && cd.AnomalyData) {
for (const anomalyData of cd.AnomalyData) {
//workaround to avoid binding conflict with earlier page when calling #property:…
anomalyData.anomalyTitle = anomalyData.title
}
return cd.AnomalyData;
} else {
return [];
}
}
Code snippet of GetOperationsData.js
export default function GetOperationsData(context) {
const pageProxy = context.evaluateTargetPathForAPI(“#Page:MDKGenAIPage”);
const cd = pageProxy.getClientData();
if (cd && cd.OperationsData) {
for (const [index, value] of cd.OperationsData.entries()) {
value.index = index;
let priority = value.priority;
value.priority = priority && priority[0].toUpperCase() + priority.slice(1);
}
return cd.OperationsData;
} else {
return [];
}
} With the above code snippets, we showcase how to preprocess the images, transmit them to the SAP AI Core service for analysis, and manage the response to extract the detected anomalies. It also generates a description of the anomalies and provides maintenance guidelines on how to fix them.In our next blogs, we will explore how to record and track work orders and operations using voice input with Generative AI and Retrieval Augmented Generation. Read More Technology Blogs by SAP articles
#SAP
#SAPTechnologyblog