Dieser Leitfaden erklärt, wie SAP (ABAP) sicher mit der Google Gemini API (Generative Language API) kommuniziert – von Zertifikaten in STRUST über die RFC-Destination in SM59 bis zum produktiven HTTP-POST in ABAP.
Warum Gemini aus SAP?
- Textanalyse, Zusammenfassungen, Klassifikation und Prompt-basierte Automatisierung direkt aus ABAP.
- Einfache Anbindung über einen API-Key und eine standardisierte REST-Schnittstelle.
- Skalierbar vom Testbetrieb bis zur produktiven Nutzung.
Voraussetzungen
- SAP-System mit aktivem HTTPS (ICM) und CommonCryptoLib.
- Berechtigungen für STRUST und SM59.
- Gültiger Gemini API-Key.
- ABAP-JSON-Utility, z. B.
/UI2/CL_JSON
.
Schritt 1: SM59 – HTTP-Destination für Google-APIs anlegen
In SM59 legst du eine HTTP-Destination an, über die SAP externe Webservices oder APIs anspricht.
Für generativelanguage.googleapis.com
sieht die Grundkonfiguration so aus:
Inhaltsverzeichnis
Neue HTTP-Verbindung anlegen
- Auf Anlegen klicken.
- Verbindungstyp G – HTTP-Verbindung zu externem Server wählen.
- Einen sprechenden Namen vergeben (z. B.
GOOGLE_GENAI_API
oderGOOGLE_GEMINI_API
).
Technische Einstellungen
- Host:
generativelanguage.googleapis.com
- Service-Nr.:
443
(HTTPS) - Pfad-Präfix: zunächst leer lassen – der vollständige Pfad wird im ABAP-Code gesetzt
(z. B./v1/models/gemini-2.5-flash:generateContent
).
Logon & Security
- SNC: nicht erforderlich (es wird HTTPS verwendet).
- SSL: Aktiv (SSL-Client Standard) wählen
(oder eure dedizierte SSL-Client-Identität, sofern in STRUST hinterlegt).
Anmeldung & Sicherheit
Für Google-APIs werden in der Destination normalerweise keine Benutzer-/Passwortdaten gepflegt. Die Authentifizierung
erfolgt im ABAP-Code – entweder per API-Key im Header oder über OAuth-2.0-Tokens (Header
Authorization: Bearer
).
- Benutzer/Passwort: leer lassen.
- SSL-Client-Zertifikat: entspricht der Auswahl unter Technische Einstellungen.
Bei mehreren Identitäten ggf. gezielt auswählen.
Spezielle Optionen
- HTTP-Proxy: nur pflegen, wenn der Internetzugang darüber läuft.
- Timeouts: nach Bedarf anpassen.
Zusammenfassung
- Host:
generativelanguage.googleapis.com
- Service:
443
- Pfad-Präfix: leer (Pfad wird im Code gesetzt)
- SSL: aktiv, SSL-Client Standard
- Anmeldung: leer (Auth im ABAP-Code)
Schritt 2: STRUST – Zertifikate für den HTTPS-Zugriff auf Google-APIs
In STRUST pflegst du die SSL/TLS-Vertrauensanker, damit dein SAP-System die von Google präsentierten Serverzertifikate akzeptiert.
Wo eintragen?
Wechsle in STRUST zum Knoten SSL client (Standard) (oder zu eurer spezifischen SSL-Client-Identität). Rechts in der
Zertifikatsliste siehst du alle bereits vertrauten Zertifikate. Hier müssen die Root- und – falls vorhanden –
die Intermediate-Zertifikate der ausstellenden CA von Google liegen.
Welche Zertifikate werden benötigt?
Google nutzt in der Regel Ketten von bekannten CAs wie Google Trust Services (GTS), GlobalSign oder DigiCert.
In vielen Systemen sind die wichtigen Root-Zertifikate bereits vorhanden (Betriebssystem/CCL liefern diese oft mit).
Fehlt etwas, ergänze die Kette manuell.
Zertifikate ermitteln und exportieren
https://generativelanguage.googleapis.com
im Browser öffnen.- Auf das Schloss-Symbol klicken und die Zertifikatskette anzeigen.
- Root- und ggf. Intermediate-Zertifikate (z. B. GTS Root R1, GTS CA 1O1) im Format Base64-X.509
(.cer
oder.pem
) speichern. Alternativ von den offiziellen Seiten der Google Trust Services laden.
Import in STRUST
- STRUST öffnen und SSL client (Standard) auswählen.
- Schaltfläche Importzertifikat nutzen und die gespeicherten Dateien auswählen.
- Die Zertifikate der Liste hinzufügen und anschließend Datenbank sichern.
- Nach dem Import müssen die Einträge sichtbar sein.
Verbindung prüfen
Führe in SM59 bei deiner Destination einen Verbindung testen aus. Schlägt der Test fehl, fehlt oft ein
Root- oder Intermediate-Zertifikat. Beachte: Dieser Test prüft Netzwerk und TLS-Handshake, nicht die spätere
Authentifizierung per API-Key oder OAuth.
Zusammenfassung
- Ort: SSL client (Standard) (oder die verwendete SSL-Client-PSE).
- Inhalt: Vertrauenswürdige Root-/Intermediate-Zertifikate der Google-CAs (z. B. GTS Root R1, GTS CA 1O1).
- Aktion: Vorhandensein prüfen, fehlende Zertifikate importieren und speichern.
Nächster Schritt in ABAP
Die eigentliche API-Kommunikation (HTTP-Methode, Header, Body, Authentifizierung) erfolgt anschließend im Code,
z. B. mit CL_HTTP_CLIENT
oder CL_REST_HTTP_CLIENT
.
Schritt 3: ABAP – POST-Request an Gemini (Minimalbeispiel)
Beispielendpunkt (v1, Modell gemini-2.5-flash
): /v1/models/gemini-2.5-flash:generateContent
REPORT z_ki_google_test.
DATA: lo_http TYPE REF TO if_http_client,
lv_body TYPE string,
lv_result TYPE string,
lv_api_key TYPE string VALUE '***PLACE_YOUR_API_KEY_SECURELY***'.
" 1) HTTP-Client aus SM59-Destination (G: HTTP)
cl_http_client=>create_by_destination(
EXPORTING destination = 'GEMINI'
IMPORTING client = lo_http ).
" 2) Header setzen
lo_http->request->set_method( if_http_request=>co_request_method_post ).
lo_http->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
lo_http->request->set_header_field( name = 'x-goog-api-key' value = lv_api_key ).
" 3) Request-Pfad (Endpoint) – setze ihn NUR hier ODER als Präfix in SM59 (nicht beides!)
lo_http->request->set_header_field(
name = '~request_uri'
value = '/v1/models/gemini-2.5-flash:generateContent' ).
" ---------- Request-JSON aufbauen (mit name_mappings für lowercase keys) ----------
TYPES: BEGIN OF ty_part_req,
text TYPE string,
END OF ty_part_req.
TYPES: ty_t_part_req TYPE STANDARD TABLE OF ty_part_req WITH EMPTY KEY.
TYPES: BEGIN OF ty_content_req,
parts TYPE ty_t_part_req,
END OF ty_content_req.
TYPES: ty_t_content_req TYPE STANDARD TABLE OF ty_content_req WITH EMPTY KEY.
TYPES: BEGIN OF ty_request,
contents TYPE ty_t_content_req,
END OF ty_request.
DATA ls_req TYPE ty_request.
DATA(lv_prompt_text) = |Erkläre KI in einem Satz.|.
APPEND VALUE ty_content_req(
parts = VALUE #( ( text = lv_prompt_text ) ) )
TO ls_req-contents.
DATA lt_req_map TYPE /ui2/cl_json=>name_mappings.
DATA lt_res_map TYPE /ui2/cl_json=>name_mappings.
" Request-Mapping (ABAP-Feldname -> JSON-Key, lowercase)
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CONTENTS' json = 'contents' ) INTO TABLE lt_req_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'PARTS' json = 'parts' ) INTO TABLE lt_req_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'TEXT' json = 'text' ) INTO TABLE lt_req_map.
" Response-Mapping
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CANDIDATES' json = 'candidates' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CONTENT' json = 'content' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'PARTS' json = 'parts' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'TEXT' json = 'text' ) INTO TABLE lt_res_map.
lv_body = /ui2/cl_json=>serialize(
data = ls_req
name_mappings = lt_req_map
compress = abap_true ).
" 4) Senden & Empfangen
lo_http->request->set_cdata( lv_body ).
lo_http->send( ).
lo_http->receive( ).
" 5) Antwort lesen (Status + Body)
DATA: lv_status TYPE i,
lv_reason TYPE string.
lo_http->response->get_status(
IMPORTING
code = lv_status
reason = lv_reason ).
lv_result = lo_http->response->get_cdata( ).
" ---------- Response-JSON deserialisieren ----------
TYPES: BEGIN OF ty_part_res,
text TYPE string,
END OF ty_part_res.
TYPES: ty_t_part_res TYPE STANDARD TABLE OF ty_part_res WITH EMPTY KEY.
TYPES: BEGIN OF ty_content_res,
parts TYPE ty_t_part_res,
END OF ty_content_res.
TYPES: BEGIN OF ty_candidate,
content TYPE ty_content_res,
END OF ty_candidate.
TYPES: ty_t_candidate TYPE STANDARD TABLE OF ty_candidate WITH EMPTY KEY.
TYPES: BEGIN OF ty_response,
candidates TYPE ty_t_candidate,
END OF ty_response.
DATA ls_resp TYPE ty_response.
TRY.
/ui2/cl_json=>deserialize(
EXPORTING json = lv_result
name_mappings = lt_res_map
CHANGING data = ls_resp ).
DATA(lv_text) = ||.
IF lines( ls_resp-candidates ) > 0
AND lines( ls_resp-candidates[ 1 ]-content-parts ) > 0.
lv_text = ls_resp-candidates[ 1 ]-content-parts[ 1 ]-text.
ENDIF.
"=== ALV-Vorbereitung ======================================================
TYPES: BEGIN OF ty_company_info,
field TYPE string,
value TYPE string,
END OF ty_company_info.
DATA: lt_company_info TYPE STANDARD TABLE OF ty_company_info,
ls_company_info TYPE ty_company_info.
IF lv_status = 200 AND lv_text IS NOT INITIAL.
" Antwort in Zeilen aufteilen und Feld: Wert extrahieren
DATA(lt_lines) = VALUE stringtab( ).
SPLIT lv_text AT cl_abap_char_utilities=>newline INTO TABLE lt_lines.
LOOP AT lt_lines INTO DATA(lv_line).
IF lv_line CS ':'.
SPLIT lv_line AT ':' INTO ls_company_info-field ls_company_info-value.
CONDENSE ls_company_info-field.
SHIFT ls_company_info-value LEFT DELETING LEADING space.
ELSE.
ls_company_info-field = 'Text'.
ls_company_info-value = lv_line.
ENDIF.
APPEND ls_company_info TO lt_company_info.
CLEAR ls_company_info.
ENDLOOP.
ELSE.
" Fehlerfall als ALV zeigen
ls_company_info-field = |HTTP|.
ls_company_info-value = |{ lv_status } { lv_reason }|.
APPEND ls_company_info TO lt_company_info.
CLEAR ls_company_info.
ls_company_info-field = |Fehlermeldung|.
ls_company_info-value = lv_result.
APPEND ls_company_info TO lt_company_info.
ENDIF.
"=== ALV-Ausgabe ===========================================================
DATA: lo_alv TYPE REF TO cl_salv_table,
lo_columns TYPE REF TO cl_salv_columns_table,
lo_column TYPE REF TO cl_salv_column_table.
cl_salv_table=>factory(
IMPORTING r_salv_table = lo_alv
CHANGING t_table = lt_company_info ).
lo_columns = lo_alv->get_columns( ).
lo_columns->set_optimize( abap_true ).
lo_column ?= lo_columns->get_column( 'FIELD' ).
lo_column->set_short_text( 'Kategorie' ).
lo_column->set_medium_text( 'Kategorie' ).
lo_column->set_long_text( 'Informationskategorie' ).
lo_column ?= lo_columns->get_column( 'VALUE' ).
lo_column->set_short_text( 'Inhalt' ).
lo_column->set_medium_text( 'Beschreibung' ).
lo_column->set_long_text( 'Detail' ).
lo_alv->display( ).
CATCH cx_root INTO DATA(lx).
" Fallback: Fehler in einfacher Liste ausgeben
WRITE: / 'JSON-/ALV-Fehler:', lx->get_text( ).
WRITE: / 'Raw-Response:', / lv_result.
ENDTRY.
lo_http->close( ).
Best Practices
- API-Key nicht hartkodieren: sicher ablegen (Secure Store, Variablen, SICF-Handler).
- Timeouts und Retries einbauen; Fehlerzustände klar loggen.
- Prompts kurz und eindeutig halten.
- Modellwahl:
gemini-2.5-flash
für Kosten/Speed,gemini-2.5-pro
für Qualität.
Troubleshooting
- 401 Unauthorized:
x-goog-api-key
fehlt oder ist falsch. - 400 Bad Request: JSON-Struktur prüfen (
contents → parts → text
). - 404 Not Found: Modellname oder URI prüfen.
- SSL-Handshake-Fehler: CA-Kette in STRUST unvollständig.
- Proxy/Firewall: SM59-Proxy und Netzwerkfreigaben prüfen.
Checkliste
- CA-Kette in STRUST importiert
- SM59: Host, Port 443, SSL aktiv
- API-Key sicher hinterlegt, Header gesetzt
- Endpoint korrekt:
/v1/models/gemini-2.5-flash:generateContent
- Timeouts, Retries und Logging vorhanden