AI in ABAP Cloud: Joule SDK & SAP AI Foundation

kategorie
Trends
Veröffentlicht
autor
Johannes

Künstliche Intelligenz wird ein zunehmend wichtiger Bestandteil moderner Enterprise-Anwendungen. SAP bietet mit dem Joule SDK und den AI Foundation Services auf der Business Technology Platform (BTP) Möglichkeiten, KI-Funktionen direkt in ABAP Cloud Anwendungen zu integrieren. Dieser Artikel zeigt, wie du generative KI programmatisch nutzen kannst.

Übersicht: KI-Optionen für ABAP Cloud

OptionBeschreibungVerfügbarkeit
Joule in ADTKI-Assistent für Code-Generierung im EditorSeit 2024
Joule SDKProgrammatische AI-Integration für AnwendungenSeit 2025
AI FoundationBTP-basierte ML/AI ServicesSeit 2023
Generative AI HubZentrale LLM-Integration auf BTPSeit 2024
SAP AI CoreCustom ML Model DeploymentSeit 2022
┌────────────────────────────────────────────────────────────────────┐
│ SAP Business Technology Platform │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ AI Foundation Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌────────────────────────┐ │ │
│ │ │ Generative │ │ SAP AI │ │ Document │ │ │
│ │ │ AI Hub │ │ Core │ │ Information │ │ │
│ │ │ (LLM Access)│ │ (Custom ML) │ │ Extraction │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────────┬─────────────┘ │ │
│ │ │ │ │ │ │
│ │ └───────────────┼────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────┴─────┐ │ │
│ │ │ Joule SDK │ │ │
│ │ └─────┬─────┘ │ │
│ └─────────────────────────│───────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────┼───────────────────────────────────┐ │
│ │ ABAP Cloud │ │
│ │ ┌──────────────┐ ┌─────┴─────┐ ┌─────────────────────────┐ │ │
│ │ │ S/4HANA Cloud│ │ BTP ABAP │ │ Communication │ │ │
│ │ │ (Embedded) │ │Environment│ │ Arrangements │ │ │
│ │ └──────────────┘ └───────────┘ └─────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘

Joule SDK: Generative KI für ABAP

Das Joule SDK ermöglicht den programmatischen Zugriff auf SAPs generative KI-Modelle direkt aus ABAP Cloud Anwendungen.

Voraussetzungen

  • SAP BTP Subaccount mit AI Foundation Berechtigung
  • Generative AI Hub Service aktiviert
  • ABAP Cloud System (BTP oder S/4HANA Cloud)
  • Communication Arrangement für AI Services

Joule SDK Klassen

KlasseBeschreibung
CL_JOULE_AI_CLIENTHauptklasse für AI-Aufrufe
CL_JOULE_PROMPT_BUILDERPrompt-Konstruktion
CL_JOULE_RESPONSE_HANDLERResponse-Verarbeitung
IF_JOULE_COMPLETIONInterface für Completions
CX_JOULE_ERRORException-Klasse

Communication Scenario einrichten

  1. Communication Scenario erstellen:
<?xml version="1.0" encoding="utf-8"?>
<scn:scenario xmlns:scn="http://sap.com/xi/BASIS/Communication"
scn:id="Z_JOULE_AI_SCENARIO"
scn:version="1">
<scn:label>Joule AI Integration</scn:label>
<scn:description>Communication Scenario für Joule SDK</scn:description>
<scn:allowedInstances>MULTIPLE</scn:allowedInstances>
<scn:communicationType>OUTBOUND</scn:communicationType>
<scn:outboundServices>
<scn:service scn:id="Z_JOULE_AI_SERVICE" scn:authMethod="OAUTH2"/>
</scn:outboundServices>
</scn:scenario>
  1. Communication Arrangement im Fiori Launchpad:
FeldWert
ScenarioZ_JOULE_AI_SCENARIO
Arrangement NameZ_JOULE_PROD
Communication SystemSAP_AI_CORE
Auth MethodOAuth 2.0 Client Credentials
Token Endpointhttps://[ai-core-url]/oauth/token

Einfacher AI-Aufruf mit Joule SDK

Text-Generierung

CLASS zcl_joule_example DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_ai_response,
text TYPE string,
tokens TYPE i,
model TYPE string,
finish_reason TYPE string,
END OF ty_ai_response.
METHODS generate_text
IMPORTING iv_prompt TYPE string
RETURNING VALUE(rs_result) TYPE ty_ai_response
RAISING cx_joule_error.
PRIVATE SECTION.
CONSTANTS:
c_comm_scenario TYPE if_com_scenario_factory=>ty_cscn_id
VALUE 'Z_JOULE_AI_SCENARIO'.
ENDCLASS.
CLASS zcl_joule_example IMPLEMENTATION.
METHOD generate_text.
" Joule AI Client erstellen
DATA(lo_client) = cl_joule_ai_client=>create(
iv_comm_scenario = c_comm_scenario
).
" Prompt konfigurieren
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
'Du bist ein hilfreicher Assistent für SAP-Entwickler. ' &&
'Antworte präzise und technisch korrekt auf Deutsch.'
).
lo_prompt->add_user_message( iv_prompt ).
" Completion-Parameter
DATA(ls_params) = VALUE cl_joule_ai_client=>ty_completion_params(
max_tokens = 1000
temperature = '0.7'
model = 'gpt-4' " oder 'gemini-pro', 'claude-3'
).
" AI-Aufruf ausführen
DATA(lo_response) = lo_client->create_completion(
io_prompt = lo_prompt
is_parameters = ls_params
).
" Response verarbeiten
rs_result = VALUE #(
text = lo_response->get_text( )
tokens = lo_response->get_token_count( )
model = lo_response->get_model( )
finish_reason = lo_response->get_finish_reason( )
).
ENDMETHOD.
ENDCLASS.

Verwendung

" Beispiel: Produktbeschreibung generieren
DATA(lo_ai) = NEW zcl_joule_example( ).
TRY.
DATA(ls_response) = lo_ai->generate_text(
|Erstelle eine kurze Produktbeschreibung für: | &&
|SAP S/4HANA Cloud, Public Edition. | &&
|Zielgruppe: IT-Entscheider. Max 100 Wörter.|
).
DATA(lv_description) = ls_response-text.
CATCH cx_joule_error INTO DATA(lx_error).
" Fehlerbehandlung
DATA(lv_error_text) = lx_error->get_text( ).
ENDTRY.

Praktische Use Cases

Use Case 1: Automatische E-Mail-Antworten

CLASS zcl_email_ai_responder DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_email,
subject TYPE string,
body TYPE string,
sender TYPE string,
END OF ty_email,
BEGIN OF ty_response,
suggested_reply TYPE string,
sentiment TYPE string,
priority TYPE string,
category TYPE string,
END OF ty_response.
METHODS analyze_and_respond
IMPORTING is_email TYPE ty_email
RETURNING VALUE(rs_result) TYPE ty_response
RAISING cx_joule_error.
PRIVATE SECTION.
METHODS build_analysis_prompt
IMPORTING is_email TYPE ty_email
RETURNING VALUE(rv_prompt) TYPE string.
METHODS parse_ai_response
IMPORTING iv_json TYPE string
RETURNING VALUE(rs_result) TYPE ty_response.
ENDCLASS.
CLASS zcl_email_ai_responder IMPLEMENTATION.
METHOD analyze_and_respond.
DATA(lo_client) = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
" Strukturierter Prompt für Analyse
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du bist ein E-Mail-Analyse-Assistent. | &&
|Analysiere E-Mails und schlage professionelle Antworten vor. | &&
|Antworte immer im JSON-Format mit den Feldern: | &&
|suggested_reply, sentiment (positiv/neutral/negativ), | &&
|priority (hoch/mittel/niedrig), category (Anfrage/Beschwerde/Info/Auftrag).|
).
lo_prompt->add_user_message( build_analysis_prompt( is_email ) ).
" AI-Aufruf
DATA(lo_response) = lo_client->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #(
max_tokens = 1500
temperature = '0.3' " Niedrig für konsistente Ausgabe
)
).
" JSON-Response parsen
rs_result = parse_ai_response( lo_response->get_text( ) ).
ENDMETHOD.
METHOD build_analysis_prompt.
rv_prompt =
|Analysiere diese E-Mail und erstelle eine Antwort:\n\n| &&
|Von: { is_email-sender }\n| &&
|Betreff: { is_email-subject }\n\n| &&
|Inhalt:\n{ is_email-body }\n\n| &&
|Erstelle eine professionelle Antwort auf Deutsch.|.
ENDMETHOD.
METHOD parse_ai_response.
" JSON parsen
/ui2/cl_json=>deserialize(
EXPORTING
json = iv_json
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = rs_result
).
ENDMETHOD.
ENDCLASS.

Use Case 2: Dokumentenklassifizierung

CLASS zcl_document_classifier DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_classification,
category TYPE string,
confidence TYPE decfloat16,
keywords TYPE string_table,
summary TYPE string,
END OF ty_classification,
BEGIN OF ty_category,
name TYPE string,
description TYPE string,
END OF ty_category,
tt_categories TYPE STANDARD TABLE OF ty_category WITH EMPTY KEY.
METHODS classify_document
IMPORTING iv_document_text TYPE string
it_categories TYPE tt_categories
RETURNING VALUE(rs_result) TYPE ty_classification
RAISING cx_joule_error.
ENDCLASS.
CLASS zcl_document_classifier IMPLEMENTATION.
METHOD classify_document.
DATA(lo_client) = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
" Kategorien als Kontext aufbereiten
DATA(lv_categories) = REDUCE string(
INIT result = ||
FOR cat IN it_categories
NEXT result = result && |{ cat-name }: { cat-description }\n|
).
" Prompt für Klassifizierung
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du bist ein Dokumentenklassifizierer. | &&
|Klassifiziere Dokumente in vorgegebene Kategorien. | &&
|Antworte im JSON-Format: | &&
|category (exakte Kategorie), confidence (0.0-1.0), | &&
|keywords (Array wichtiger Begriffe), summary (1-2 Sätze).|
).
lo_prompt->add_user_message(
|Klassifiziere dieses Dokument:\n\n| &&
|{ iv_document_text }\n\n| &&
|Verfügbare Kategorien:\n{ lv_categories }|
).
DATA(lo_response) = lo_client->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #(
max_tokens = 500
temperature = '0.1' " Sehr niedrig für konsistente Klassifizierung
)
).
" Response parsen
/ui2/cl_json=>deserialize(
EXPORTING
json = lo_response->get_text( )
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = rs_result
).
ENDMETHOD.
ENDCLASS.

Use Case 3: RAP-Integration mit AI-Enrichment

" Behavior Definition
managed implementation in class zbp_i_ticket unique;
strict ( 2 );
define behavior for ZI_Ticket alias Ticket
{
// AI-basierte Determination
determination enrichWithAI on modify { field Description; }
// AI Action
action suggestSolution result [1] $self;
}
CLASS lhc_ticket DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
enrichWithAI FOR DETERMINE ON MODIFY
IMPORTING keys FOR Ticket~enrichWithAI,
suggestSolution FOR MODIFY
IMPORTING keys FOR ACTION Ticket~suggestSolution RESULT result.
ENDCLASS.
CLASS lhc_ticket IMPLEMENTATION.
METHOD enrichWithAI.
" Tickets lesen
READ ENTITIES OF zi_ticket IN LOCAL MODE
ENTITY Ticket
FIELDS ( Description )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_tickets).
DATA(lo_ai) = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
LOOP AT lt_tickets INTO DATA(ls_ticket).
TRY.
" AI-Analyse der Beschreibung
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du analysierst Support-Tickets. | &&
|Extrahiere: Kategorie, Priorität (1-5), Schlagworte. | &&
|JSON-Format: category, priority, keywords (Array).|
).
lo_prompt->add_user_message( ls_ticket-Description ).
DATA(lo_response) = lo_ai->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #( max_tokens = 200 temperature = '0.2' )
).
" Response parsen
DATA: BEGIN OF ls_analysis,
category TYPE string,
priority TYPE i,
keywords TYPE string_table,
END OF ls_analysis.
/ui2/cl_json=>deserialize(
EXPORTING json = lo_response->get_text( )
CHANGING data = ls_analysis
).
" Ticket aktualisieren
MODIFY ENTITIES OF zi_ticket IN LOCAL MODE
ENTITY Ticket
UPDATE FIELDS ( Category Priority Keywords )
WITH VALUE #( (
%tky = ls_ticket-%tky
Category = ls_analysis-category
Priority = ls_analysis-priority
Keywords = concat_lines_of( table = ls_analysis-keywords sep = ',' )
) ).
CATCH cx_joule_error INTO DATA(lx_error).
APPEND VALUE #(
%tky = ls_ticket-%tky
%msg = new_message_with_text( text = lx_error->get_text( ) )
) TO reported-ticket.
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD suggestSolution.
" Ticket mit Historie lesen
READ ENTITIES OF zi_ticket IN LOCAL MODE
ENTITY Ticket
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_tickets).
DATA(lo_ai) = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
LOOP AT lt_tickets INTO DATA(ls_ticket).
TRY.
" Ähnliche gelöste Tickets als Kontext laden
DATA(lt_similar) = get_similar_resolved_tickets( ls_ticket-Category ).
DATA(lv_context) = REDUCE string(
INIT ctx = ||
FOR sim IN lt_similar
NEXT ctx = ctx && |Problem: { sim-Description }\nLösung: { sim-Solution }\n\n|
).
" AI-Lösungsvorschlag
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du bist ein Support-Experte. | &&
|Basierend auf ähnlichen gelösten Tickets, schlage eine Lösung vor. | &&
|Antworte strukturiert mit: Diagnose, Lösungsschritte, Prävention.|
).
lo_prompt->add_user_message(
|Aktuelles Ticket:\n{ ls_ticket-Description }\n\n| &&
|Ähnliche gelöste Tickets:\n{ lv_context }|
).
DATA(lo_response) = lo_ai->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #( max_tokens = 1000 temperature = '0.5' )
).
" Lösungsvorschlag speichern
MODIFY ENTITIES OF zi_ticket IN LOCAL MODE
ENTITY Ticket
UPDATE FIELDS ( SuggestedSolution )
WITH VALUE #( (
%tky = ls_ticket-%tky
SuggestedSolution = lo_response->get_text( )
) ).
CATCH cx_joule_error.
ENDTRY.
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_ticket IN LOCAL MODE
ENTITY Ticket ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR t IN lt_result ( %tky = t-%tky %param = t ) ).
ENDMETHOD.
ENDCLASS.

SAP AI Foundation Services

Document Information Extraction

Der Document Information Extraction Service extrahiert strukturierte Daten aus Dokumenten.

CLASS zcl_document_extraction DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_invoice_data,
vendor_name TYPE string,
invoice_number TYPE string,
invoice_date TYPE d,
total_amount TYPE decfloat16,
currency TYPE waers,
line_items TYPE string_table,
END OF ty_invoice_data.
METHODS extract_invoice
IMPORTING iv_document_base64 TYPE string
RETURNING VALUE(rs_result) TYPE ty_invoice_data
RAISING cx_ai_service_error.
PRIVATE SECTION.
CONSTANTS c_comm_scenario TYPE string VALUE 'Z_AI_DOC_EXTRACTION'.
ENDCLASS.
CLASS zcl_document_extraction IMPLEMENTATION.
METHOD extract_invoice.
" HTTP Client für AI Service
DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = c_comm_scenario
service_id = 'Z_DOC_EXTRACTION_SERVICE'
).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination
).
TRY.
" Request Body erstellen
DATA: BEGIN OF ls_request,
document_type TYPE string VALUE 'invoice',
file_content TYPE string,
options TYPE string VALUE '{"language":"de"}',
END OF ls_request.
ls_request-file_content = iv_document_base64.
DATA(lv_json) = /ui2/cl_json=>serialize(
data = ls_request
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
).
" Request konfigurieren
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( '/document/extraction' ).
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
lo_request->set_text( lv_json ).
" Ausführen
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
IF lo_response->get_status( )-code = 200.
" Response parsen
DATA: BEGIN OF ls_response,
extraction TYPE ty_invoice_data,
confidence TYPE decfloat16,
END OF ls_response.
/ui2/cl_json=>deserialize(
EXPORTING json = lo_response->get_text( )
CHANGING data = ls_response
).
rs_result = ls_response-extraction.
ENDIF.
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
ENDCLASS.
CLASS zcl_embedding_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
tt_embedding TYPE STANDARD TABLE OF decfloat16 WITH EMPTY KEY,
BEGIN OF ty_document_embedding,
document_id TYPE string,
text TYPE string,
embedding TYPE tt_embedding,
END OF ty_document_embedding,
tt_document_embeddings TYPE STANDARD TABLE OF ty_document_embedding WITH EMPTY KEY.
METHODS create_embedding
IMPORTING iv_text TYPE string
RETURNING VALUE(rt_embedding) TYPE tt_embedding
RAISING cx_ai_service_error.
METHODS find_similar_documents
IMPORTING iv_query_text TYPE string
it_documents TYPE tt_document_embeddings
iv_top_k TYPE i DEFAULT 5
RETURNING VALUE(rt_results) TYPE string_table
RAISING cx_ai_service_error.
PRIVATE SECTION.
METHODS calculate_cosine_similarity
IMPORTING it_vec1 TYPE tt_embedding
it_vec2 TYPE tt_embedding
RETURNING VALUE(rv_similarity) TYPE decfloat16.
ENDCLASS.
CLASS zcl_embedding_service IMPLEMENTATION.
METHOD create_embedding.
DATA(lo_client) = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
" Embedding-Modell aufrufen
rt_embedding = lo_client->create_embedding(
iv_text = iv_text
iv_model = 'text-embedding-ada-002'
)->get_vector( ).
ENDMETHOD.
METHOD find_similar_documents.
" Query-Embedding erstellen
DATA(lt_query_embedding) = create_embedding( iv_query_text ).
" Ähnlichkeit zu allen Dokumenten berechnen
DATA: BEGIN OF ls_scored,
document_id TYPE string,
similarity TYPE decfloat16,
END OF ls_scored,
lt_scored LIKE STANDARD TABLE OF ls_scored.
LOOP AT it_documents INTO DATA(ls_doc).
DATA(lv_similarity) = calculate_cosine_similarity(
it_vec1 = lt_query_embedding
it_vec2 = ls_doc-embedding
).
APPEND VALUE #(
document_id = ls_doc-document_id
similarity = lv_similarity
) TO lt_scored.
ENDLOOP.
" Nach Ähnlichkeit sortieren
SORT lt_scored BY similarity DESCENDING.
" Top-K zurückgeben
LOOP AT lt_scored INTO ls_scored FROM 1 TO iv_top_k.
APPEND ls_scored-document_id TO rt_results.
ENDLOOP.
ENDMETHOD.
METHOD calculate_cosine_similarity.
DATA lv_dot_product TYPE decfloat16 VALUE 0.
DATA lv_norm1 TYPE decfloat16 VALUE 0.
DATA lv_norm2 TYPE decfloat16 VALUE 0.
DATA(lv_count) = lines( it_vec1 ).
DO lv_count TIMES.
DATA(lv_idx) = sy-index.
DATA(lv_v1) = it_vec1[ lv_idx ].
DATA(lv_v2) = it_vec2[ lv_idx ].
lv_dot_product = lv_dot_product + ( lv_v1 * lv_v2 ).
lv_norm1 = lv_norm1 + ( lv_v1 * lv_v1 ).
lv_norm2 = lv_norm2 + ( lv_v2 * lv_v2 ).
ENDDO.
IF lv_norm1 > 0 AND lv_norm2 > 0.
rv_similarity = lv_dot_product / ( sqrt( lv_norm1 ) * sqrt( lv_norm2 ) ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Vollständiges Beispiel: AI-gestützter Helpdesk

1. CDS View für Tickets

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'AI Support Ticket'
define root view entity ZI_AITicket
as select from zai_ticket
{
key ticket_id as TicketId,
title as Title,
description as Description,
category as Category,
priority as Priority,
status as Status,
ai_category as AICategory,
ai_sentiment as AISentiment,
ai_suggested_solution as AISuggestedSolution,
ai_confidence as AIConfidence,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt
}

2. AI Service Klasse

CLASS zcl_ticket_ai_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_ticket_analysis,
category TYPE string,
sentiment TYPE string,
priority TYPE i,
confidence TYPE decfloat16,
keywords TYPE string_table,
suggested_solution TYPE string,
END OF ty_ticket_analysis.
METHODS analyze_ticket
IMPORTING iv_title TYPE string
iv_description TYPE string
RETURNING VALUE(rs_result) TYPE ty_ticket_analysis
RAISING cx_joule_error.
METHODS generate_solution
IMPORTING iv_description TYPE string
iv_category TYPE string
RETURNING VALUE(rv_solution) TYPE string
RAISING cx_joule_error.
METHODS summarize_ticket
IMPORTING iv_description TYPE string
RETURNING VALUE(rv_summary) TYPE string
RAISING cx_joule_error.
PRIVATE SECTION.
DATA mo_client TYPE REF TO cl_joule_ai_client.
METHODS get_client
RETURNING VALUE(ro_client) TYPE REF TO cl_joule_ai_client.
ENDCLASS.
CLASS zcl_ticket_ai_service IMPLEMENTATION.
METHOD get_client.
IF mo_client IS NOT BOUND.
mo_client = cl_joule_ai_client=>create(
iv_comm_scenario = 'Z_JOULE_AI_SCENARIO'
).
ENDIF.
ro_client = mo_client.
ENDMETHOD.
METHOD analyze_ticket.
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du analysierst Support-Tickets für ein IT-Helpdesk-System.\n| &&
|Analysiere das Ticket und extrahiere:\n| &&
|- category: Die Hauptkategorie (Hardware, Software, Netzwerk, Zugang, Sonstiges)\n| &&
|- sentiment: Stimmung des Kunden (positiv, neutral, frustriert, verärgert)\n| &&
|- priority: Dringlichkeit 1-5 (1=kritisch, 5=niedrig)\n| &&
|- confidence: Dein Vertrauen in die Analyse 0.0-1.0\n| &&
|- keywords: Wichtige technische Begriffe als Array\n| &&
|- suggested_solution: Kurzer Lösungsvorschlag (2-3 Sätze)\n| &&
|Antworte nur im JSON-Format, keine zusätzlichen Erklärungen.|
).
lo_prompt->add_user_message(
|Titel: { iv_title }\n\nBeschreibung:\n{ iv_description }|
).
DATA(lo_response) = get_client( )->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #(
max_tokens = 800
temperature = '0.2'
)
).
" JSON parsen
DATA(lv_json) = lo_response->get_text( ).
" Mögliche Markdown-Formatierung entfernen
REPLACE ALL OCCURRENCES OF '```json' IN lv_json WITH ''.
REPLACE ALL OCCURRENCES OF '```' IN lv_json WITH ''.
CONDENSE lv_json.
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_json
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = rs_result
).
ENDMETHOD.
METHOD generate_solution.
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Du bist ein erfahrener IT-Support-Mitarbeiter.\n| &&
|Erstelle eine detaillierte Lösungsanleitung für das folgende Problem.\n| &&
|Strukturiere die Antwort mit:\n| &&
|1. Problemdiagnose (2-3 Sätze)\n| &&
|2. Lösungsschritte (nummerierte Liste)\n| &&
|3. Präventionshinweise (falls zutreffend)\n| &&
|Schreibe präzise und verständlich.|
).
lo_prompt->add_user_message(
|Kategorie: { iv_category }\n\nProblem:\n{ iv_description }|
).
DATA(lo_response) = get_client( )->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #(
max_tokens = 1500
temperature = '0.5'
)
).
rv_solution = lo_response->get_text( ).
ENDMETHOD.
METHOD summarize_ticket.
DATA(lo_prompt) = cl_joule_prompt_builder=>create( ).
lo_prompt->set_system_message(
|Fasse das folgende Support-Ticket in maximal 2 Sätzen zusammen. | &&
|Konzentriere dich auf das Kernproblem und die gewünschte Lösung.|
).
lo_prompt->add_user_message( iv_description ).
DATA(lo_response) = get_client( )->create_completion(
io_prompt = lo_prompt
is_parameters = VALUE #(
max_tokens = 200
temperature = '0.3'
)
).
rv_summary = lo_response->get_text( ).
ENDMETHOD.
ENDCLASS.

3. RAP Behavior Implementation

CLASS lhc_aiticket DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
DATA mo_ai_service TYPE REF TO zcl_ticket_ai_service.
METHODS get_ai_service
RETURNING VALUE(ro_service) TYPE REF TO zcl_ticket_ai_service.
METHODS:
analyzeWithAI FOR DETERMINE ON MODIFY
IMPORTING keys FOR AITicket~analyzeWithAI,
generateSolution FOR MODIFY
IMPORTING keys FOR ACTION AITicket~generateSolution RESULT result,
refreshAnalysis FOR MODIFY
IMPORTING keys FOR ACTION AITicket~refreshAnalysis RESULT result.
ENDCLASS.
CLASS lhc_aiticket IMPLEMENTATION.
METHOD get_ai_service.
IF mo_ai_service IS NOT BOUND.
mo_ai_service = NEW zcl_ticket_ai_service( ).
ENDIF.
ro_service = mo_ai_service.
ENDMETHOD.
METHOD analyzeWithAI.
READ ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket
FIELDS ( Title Description )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_tickets).
DATA(lo_ai) = get_ai_service( ).
LOOP AT lt_tickets INTO DATA(ls_ticket).
TRY.
" AI-Analyse durchführen
DATA(ls_analysis) = lo_ai->analyze_ticket(
iv_title = ls_ticket-Title
iv_description = ls_ticket-Description
).
" Ergebnisse speichern
MODIFY ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket
UPDATE FIELDS ( AICategory AISentiment Priority AIConfidence )
WITH VALUE #( (
%tky = ls_ticket-%tky
AICategory = ls_analysis-category
AISentiment = ls_analysis-sentiment
Priority = ls_analysis-priority
AIConfidence = ls_analysis-confidence
) ).
CATCH cx_joule_error INTO DATA(lx_error).
APPEND VALUE #(
%tky = ls_ticket-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-warning
text = |AI-Analyse fehlgeschlagen: { lx_error->get_text( ) }|
)
) TO reported-aiticket.
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD generateSolution.
READ ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket
FIELDS ( Description AICategory )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_tickets).
DATA(lo_ai) = get_ai_service( ).
LOOP AT lt_tickets INTO DATA(ls_ticket).
TRY.
" Lösung generieren
DATA(lv_solution) = lo_ai->generate_solution(
iv_description = ls_ticket-Description
iv_category = ls_ticket-AICategory
).
" Speichern
MODIFY ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket
UPDATE FIELDS ( AISuggestedSolution )
WITH VALUE #( (
%tky = ls_ticket-%tky
AISuggestedSolution = lv_solution
) ).
CATCH cx_joule_error INTO DATA(lx_error).
APPEND VALUE #(
%tky = ls_ticket-%tky
) TO failed-aiticket.
APPEND VALUE #(
%tky = ls_ticket-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lx_error->get_text( )
)
) TO reported-aiticket.
ENDTRY.
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR t IN lt_result ( %tky = t-%tky %param = t ) ).
ENDMETHOD.
METHOD refreshAnalysis.
" Erneute AI-Analyse erzwingen
analyzeWithAI( CORRESPONDING #( keys ) ).
READ ENTITIES OF zi_aiticket IN LOCAL MODE
ENTITY AITicket ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR t IN lt_result ( %tky = t-%tky %param = t ) ).
ENDMETHOD.
ENDCLASS.

Limitierungen und Best Practices

Limitierungen

LimitationBeschreibungWorkaround
LatenzAI-Aufrufe dauern 1-10 SekundenAsynchrone Verarbeitung, Caching
KostenTokens werden berechnetPrompt-Optimierung, Caching
Rate LimitsAPI-AufruflimitsRetry-Logik, Queuing
HalluzinationLLMs können falsche Infos generierenValidierung, Human Review
KontextlimitMax Token-Anzahl pro AufrufText-Chunking, Summarization
OfflineCloud-basiert, keine Offline-NutzungFallback-Logik
DatenschutzDaten gehen an externe ServicesAnonymisierung, On-Premise ML

Best Practices

✅ DO:
1. Strukturierte Prompts verwenden
- System-Message für Rolle und Format
- User-Message für konkreten Input
- Beispiele im Prompt (Few-Shot Learning)
2. Temperatur anpassen
- 0.1-0.3: Konsistente, faktische Ausgaben
- 0.5-0.7: Kreativere Antworten
- 0.9+: Sehr kreativ, weniger vorhersagbar
3. Fehlerbehandlung implementieren
- Retry bei transienten Fehlern
- Graceful Degradation
- Logging für Debugging
4. Ergebnisse validieren
- JSON-Schema-Validierung
- Plausibilitätsprüfungen
- Human-in-the-Loop für kritische Entscheidungen
5. Caching einsetzen
- Identische Prompts cachen
- Embeddings vorberechnen
- TTL basierend auf Datenaktualität
❌ DON'T:
1. Sensible Daten unverschlüsselt senden
- Personendaten anonymisieren
- Keine Passwörter/Secrets im Prompt
2. AI-Ergebnisse blind vertrauen
- Immer validieren
- Bei Unsicherheit: User fragen
3. Zu lange Prompts
- Kontext auf das Wesentliche beschränken
- Bei Bedarf: Summarization vorschalten
4. Synchrone Aufrufe in UI-kritischen Pfaden
- Background Jobs verwenden
- Asynchrone Patterns nutzen

Kostenoptimierung

" Prompt-Komprimierung für Kostenreduktion
METHOD optimize_prompt.
" Vor Optimierung: 500 Tokens
" Nach Optimierung: 150 Tokens (70% Ersparnis)
DATA(lv_optimized) =
|Klassifiziere: { iv_category }\n| &&
|Text: { substring( val = iv_text len = 500 ) }|. " Maximal 500 Zeichen
" Anstatt:
" "Du bist ein hilfreicher Assistent der Dokumente klassifiziert.
" Bitte analysiere den folgenden Text sorgfältig und ordne ihn
" einer der folgenden Kategorien zu: ..."
ENDMETHOD.
" Caching für häufige Anfragen
CLASS-DATA: gt_cache TYPE HASHED TABLE OF ty_cache_entry
WITH UNIQUE KEY prompt_hash.
METHOD get_cached_or_call.
DATA(lv_hash) = calculate_hash( iv_prompt ).
" Cache prüfen
READ TABLE gt_cache WITH KEY prompt_hash = lv_hash
INTO DATA(ls_cached).
IF sy-subrc = 0 AND ls_cached-timestamp > ( utclong_current( ) - 3600 ).
rv_result = ls_cached-response.
RETURN.
ENDIF.
" AI aufrufen und cachen
rv_result = call_ai( iv_prompt ).
INSERT VALUE #(
prompt_hash = lv_hash
response = rv_result
timestamp = utclong_current( )
) INTO TABLE gt_cache.
ENDMETHOD.

Ausblick und Roadmap

SAP entwickelt die KI-Integration kontinuierlich weiter:

ZeitraumErwartete Features
2025Joule SDK GA, erweiterte RAP-Integration
2025-2026Fine-Tuning auf SAP-Daten, Multimodale Modelle
2026+On-Premise LLM Support, Edge AI für S/4HANA

Trends:

  • Agentic AI: Autonome Agenten für komplexe Workflows
  • RAG (Retrieval Augmented Generation): Unternehmens-Wissensbasen einbinden
  • Multimodal: Bilder, Dokumente, Sprache verarbeiten
  • Fine-Tuning: Modelle auf SAP-spezifische Domänen anpassen

Fazit

Die Integration von KI in ABAP Cloud eröffnet neue Möglichkeiten für intelligente Anwendungen. Mit dem Joule SDK und den AI Foundation Services können Entwickler generative KI-Funktionen direkt in RAP-Anwendungen einbetten - von automatischer Klassifizierung über Textgenerierung bis zu semantischer Suche.

Wichtig: KI ist ein mächtiges Werkzeug, aber kein Ersatz für fachliche Validierung. Ergebnisse sollten immer geprüft und kritische Entscheidungen nicht vollautomatisch getroffen werden.

Weiterführende Themen