Skip to content

IBM/abap-sdk-nwas-x

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ABAP SDK for IBM watsonx, using SAP NetWeaver

License

ABAP® client library to use the watsonx.ai Runtime (formerly Watson Machine Learning) and the watsonx.ai APIs.

This is a community SDK written by ABAPers for the ABAPers in the Open Source community, to provide easy usage of IBM® watsonx services in innovation initiatives combined with any SAP® application which is run on SAP NetWeaver® 7.50 and above, such as SAP Business Suite or SAP S/4HANA® on-premises edition. It is the choice and responsibility of application developers how this community SDK is used.

Additionally, as the ABAP SDK is a community release it is not updated with the same schedule as IBM-supported SDKs. Please see more information in Support and Feedback.

Choose ABAP SDK release for the applicable ABAP Runtime

abap-sdk-nwas-x abap-sdk-btp-x
for SAP NetWeaver® AS ABAP 7.50+ for SAP BTP ABAP Environment
tested on SAP Business Suite and SAP S/4HANA on-premises edition tested with SAP BTP ABAP Environment 2405
this repository

Table of Contents

Announcements

Version 1.5.0 released

Version v1.5.0 of the SDK has been released - see what's changed in the migration guide.

Before you begin

Installation

The client library is provided as abapGit repository. Proceed as follows to clone the ABAP SDK code to your SAP system.

  1. Install abapGit, using the abapGit Docs
  2. Use abapGit to clone the ABAP SDK Git repository into your SAP system.
  3. Assign the ABAP SDK to the package ZIBMX when performing setup of the abapGit repository.

SAP System Configuration

SAP Profile Parameters

The following SAP profile parameter settings are recommended.

icm/HTTPS/client_sni_enabled = TRUE
ssl/ciphersuites = 135:PFS:HIGH::EC_P256:EC_HIGH
ssl/client_ciphersuites = 150:PFS:HIGH::EC_P256:EC_HIGH

Proxy configuration

The client library respects the ABAP proxy settings. If you are using a central proxy server that is not already configured in your SAP system, proceed as follows.

  1. Logon to the SAP system and call transaction SICF.
  2. Click Execute (or press F8).
  3. Select menu item ClientProxy Settings.
  4. On tab Global Settings: Select Proxy Setting is Active and No Proxy Setting for Local Server.
  5. On tabs HTTP Protocol and HTTPS Protocol specify the proxy Host Name and Port. If the proxy server requires logon credentials, also specify User Name and Password.
  6. Click Execute (F8) and OK.

SSL Certificates

Communication between SAP and the IBM Cloud is secured by the Transport Layer Security (TLS) protocol, also known as Secure Sockets Layer (SSL). SSL requires certificates that must be stored on the SAP application server in the Personal Security Environment (PSE). Transaction STRUST is used to maintain the PSE.

Determine required certificate

If a call to an IBM Cloud service failed due to a missing SSL certificate, check the ICM trace file to find the required certificate. To do so, call transaction SMICM and select menu item GotoTrace FileDisplay End. Alternatively, call transaction AL11 and navigate to file DIR_HOME/dev_icm. Find last entry in trace file that indicates error SSSLERR_PEER_CERT_UNTRUSTED, as shown in the example below.

[Thr nn] Peer not trusted
[Thr nn] Certificate:
[Thr nn]  Certificate:
[Thr nn]   Subject:                CN=*.watsonplatform.net, O=INTERNATIONAL BUSINESS MACHINES CORPORATION, L=Armonk, SP=New York, C=US
[Thr nn]   Issuer:                 CN=GeoTrust RSA CA 2018, OU=www.digicert.com, O=DigiCert Inc, C=US
[Thr nn]   Serial Number:          05:1C:46:1B:6E:C2:29:48:BD:F0:03:B3:A6:87:1A:5B
[Thr nn]  Verification result:
[...]
[Thr nn]    SignerCert:
[Thr nn]     Certificate:
[Thr nn]      Subject:                CN=GeoTrust RSA CA 2018, OU=www.digicert.com, O=DigiCert Inc, C=US
[Thr nn]      Issuer:                 CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US
[Thr nn]      Serial Number:          05:46:FE:18:23:F7:E1:94:1D:A3:9F:CE:14:C4:61:73
[Thr nn]     Verification result:
[...]
[Thr nn]       SignerCert:
[Thr nn]        Certificate:
[Thr nn]         Subject:                CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US
[...]
[Thr nn] << ---------- End of Secu-SSL Errorstack ----------
[Thr nn]   Target Hostname="stream.watsonplatform.net"
[Thr nn]   SSL NI-hdl 463: local=9.152.20.145:56542  peer=169.48.115.62:443
[Thr nn] <<- ERROR: SapSSLSessionStartNB(sssl_hdl=3fef0000c70)==SSSLERR_PEER_CERT_UNTRUSTED
[Thr nn] *** ERROR => SSL handshake with stream.watsonplatform.net:443 failed: SSSLERR_PEER_CERT_UNTRUSTED (-102)
[Thr nn] The peer's X.509 Certificate (chain) is untrusted

The SSL error stack shows the SSL certificate chain, which in the example above looks like follows:

Certificate Authority Type Certificate Authority (CA)
end-user *.watsonplatform.net
intermediate GeoTrust RSA CA 2018
root DigiCert Global Root CA

You can install any of these SSL certificates. However, it is recommended to install the root CA certificate.
Download the appropriate SSL certificate from the provider's website, for example https://www.digicert.com/digicert-root-certificates.htm. Store the certificate file on your computer.

Add an SSL certificate to the PSE

Proceed as follows to add the downloaded SSL certificate to your SAP system's Standard SSL Client PSE.

  1. Logon to the SAP system and call transaction STRUST.
  2. Switch to edit mode (press according tool bar icon).
  3. If a local PSE file does not exist already, create it by right-clicking on SSL client SSL Client (Standard) and selecting Create from context menu. Keep all default settings in next popup dialog.
  4. In Certificate section, click Import (alternatively select menu item CertificateImport). Choose the certificate file that you have downloaded and import the certificate.
  5. Click button Add to Certificate List.
  6. Click Save (F3).

Restart the ICM (Internet Communication Manager)

It is recommended to restart the ICM after a new SSL certificate has been applied to the PSE. To do so, proceed as follows.

  1. Logon to the SAP system and start transaction SMICM.
  2. If applicable, ensure that the ICM is restarted automatically when ended: AdministrationICMRestartYes.
  3. Select menu item AdministrationICMExit SoftGlobal.

Credentials

Before you can access a specific service from your SAP system, you must create a service instance in the IBM Cloud and obtain an API key. The API key can be shared among multiple end users of the SAP system, but you must ensure that the selected plan is sufficient to support the expected number of calls or the expected data volume.

All currently supported services support IAM authentication (see below). Service credentials consist of an API key and a URL. Both the API key and the URL are character values that need to be provided as parameters i_apikey and i_url to method zcl_ibmx_service_ext=>get_instance.

You can store the values with your application, but it is suggested to do that in an encrypted format. Using cloud services usually creates costs based on usage for the owner of the service instance, and anyone with the credentials can use the service instance at the owner's expenses. If you want to distribute the costs over multiple cost centers, you need to create a service instance and provide service credentials for each cost center separately.

Configuration table

Service credentials and other parameters that must be specified at watsonx service wrapper ABAP class instantiation can also be provided in table ZIBMX_CONFIG. This table has three keys:

Table Key Description
SERVICE The ABAP class name without prefix ZCL_IBMX_
INSTANCE_UID ID chosen by application developer that must be provided by application as parameter to method zcl_ibmx_service_ext=>get_instance()
PARAM The parameter name
List of configuration parameters in table ZIBMX_CONFIG
Parameter Name Default Value Description
URL service-dependent watsonx service url
APIKEY watsonx service API keys
PROXY_HOST Proxy server
PROXY_PORT Proxy server port
AUTH_NAME service-dependent Authorization, IAM or basicAuth
SSL_ID CLIENT SSL identity, defines PSE for SSL certificates: CLIENT or ANONYM

IAM Authentication

Identity and Access Management (IAM) is a bearer-token based authentication method. Token management is either performed by the ABAP SDK or must be implemented by the SDK user.
If a value for i_apikey is provided by the caller in method zcl_ibmx_service_ext=>get_instance(), the ABAP SDK generates a bearer-token under the cover when needed and refreshes it when it is about to expire.
If i_apikey is not provided for method zcl_ibmx_service_ext=>get_instance(), the ABAP SDK user must implement an individual token management. Before a service method is called the first time, a valid bearer-token must be provided to the watsonx service wrapper ABAP class instance as follows:

  lo_service_class->set_bearer_token( i_bearer_token = '...' ).

Afterwards, service methods can be called as long as the provided token is valid. When the token has expired, the method above must be called again with a new (refreshed) bearer-token as parameter.

Usage

The client library is delivered as package ZIBMX. Once the Git repository has been cloned to the SAP system, a watsonx service instance is wrapped by an ABAP class instance.
The following watsonx services are currently supported:

Service ABAP Class Name
watsonx.ai Runtime ZCL_IBMX_WML_V4
watsonx.ai ZCL_IBMX_WATSONX_AI_ML_V1

Using the client library requires two steps:

  1. Create an instance of the watsonx service wrapper ABAP class by calling method zcl_ibmx_service_ext=>get_instance.

      data:
        lo_service_class type <ABAP Class Name>.
    
      zcl_ibmx_service_ext=>get_instance(
        exporting
          i_url      = <url>
          i_apikey   = <api key>
          ...
        importing
          eo_instance = lo_service_class ).

    Note
    Depending on the API operation, the <url> parameter may vary as described in Endpoint URLs. If you use API operations with different endpoint URLs in the same program, you need to create an instance of the watsonx service wrapper ABAP class for each endpoint URL and use the instances accordingly in your program.

  2. Call the watsonx service API endpoint by invoking the corresponding class method.

      try.
          lo_service_class->method(
            exporting
              is_input = ...
            importing
              es_output = ... ).
        catch zcx_ibmx_service_exception into data(lo_service_exception).
          ...
      endtry.

Examples

watsonx.ai Example (AI Sevices)
*  Z_WATSONX_AI_SERVICES_DEMO
*  This sample code deploys arbitrary Python code as Python AI Service on a watsonx deployment space
*    and calls the deployed function.
*    It also returns a CURL command that can be called to invoke the function endpoint url.
*
*  Copy this code into
*    - an ABAP report and remove all comment prefixes "[PRG]
*    - a Console Application (RAP) and remove all comment prefixes "[RAP]
*  Adjust credentials and run code.

constants:
  " Prerequisite: watsonx deployment space must be created and id specified here:
  c_url           type string value `https://eu-de.ml.cloud.ibm.com`,   " <- ADJUST
  c_apikey        type string value `KHds7uuH...A7892Gt6`,              " <- ADJUST
  c_space_id      type string value `c3a99662...21349082`,              " <- ADJUST
  c_function_name type string value `demo-ai-service-function`,
  c_serving_name  type string value `abapsdk_test`,
  c_version       type string value `2024-03-14`.

" convert Python function code to GZIP file (ZIP with file header 0x04034b50 is not supported)
try.
    " some Python code that bounces the received data (http method, path parameter, payload (= json request body)) as response body
    data(lx_code) = zcl_ibmx_service=>convert_string_to_utf8(
      i_string =
        `def ai_service_function(context):`                                                && cl_abap_char_utilities=>newline &&
        `  def generate(context) -> dict:`                                                 && cl_abap_char_utilities=>newline &&
        `    payload     = context.get_json()`                                             && cl_abap_char_utilities=>newline &&
        `    path_suffix = context.get_path_suffix()`                                      && cl_abap_char_utilities=>newline &&
        `    method      = context.get_method()`                                           && cl_abap_char_utilities=>newline &&
        `    return {'body': {'method': method, 'path': path_suffix, 'payload': payload}}` && cl_abap_char_utilities=>newline &&
        `  return generate`
    ).
    cl_abap_gzip=>compress_binary_with_header(
      exporting
        raw_in   = lx_code
      importing
        gzip_out = data(lx_gzipfile)
    ).
  catch cx_root into data(lo_zip_exception).
    "[RAP] out->write( lo_zip_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_zip_exception type 'E'.
endtry.

" instantiate watsonx.ai wrapper class (explicit type declaration is required)
data: lo_watsonx_ai type ref to zcl_ibmx_watsonx_ai_ml_v1.
zcl_ibmx_service_ext=>get_instance(
  exporting
    i_url       = c_url
    i_apikey    = c_apikey
    i_version   = c_version
  importing
    eo_instance = lo_watsonx_ai ).

" create Python function as asset in watsonx deployment space
try.
    lo_watsonx_ai->ai_services_create(
      exporting
        i_aiservicerequest = value zcl_ibmx_watsonx_ai_ml_v1=>t_aiservice_request(
          space_id      = c_space_id
          name          = c_function_name
          description   = 'demo'
          software_spec = value zcl_ibmx_watsonx_ai_ml_v1=>t_software_spec_rel(
            name = `runtime-24.1-py3.11`
          )
        )
      importing
        e_response = data(ls_result_create)
    ).

  catch zcx_ibmx_service_exception into data(lo_create_exception).
    "[RAP] out->write( lo_create_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_create_exception type 'E'.
endtry.
data(lv_aiservice_id) = ls_result_create-metadata-id.

" upload AI Service Python function code
try.
    lo_watsonx_ai->ai_services_upload_code(
      exporting
        i_id          = lv_aiservice_id
        i_space_id    = c_space_id
        i_upload_code = lx_gzipfile
        i_contenttype = 'application/gzip'
      importing
        e_response    = data(ls_result_upload)
    ).
  catch zcx_ibmx_service_exception into data(lo_upload_exception).
    "[RAP] out->write( lo_upload_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_upload_exception type 'E'.
endtry.

" deploy AI Service Python function
try.
    lo_watsonx_ai->create_deployment(
      exporting
        i_deploymentresourceprototype = value zcl_ibmx_watsonx_ai_ml_v1=>t_deployment_resrc_prototype(
          space_id      = c_space_id
          name          = `Deployment for ` && c_function_name
          asset         = value zcl_ibmx_watsonx_ai_ml_v1=>t_rel(
             id         = lv_aiservice_id
          )
          hardware_spec = value zcl_ibmx_watsonx_ai_ml_v1=>t_hardware_spec(
            name       = 'S'
          )
          online        = value zcl_ibmx_watsonx_ai_ml_v1=>t_online_deployment(
            parameters = value zcl_ibmx_watsonx_ai_ml_v1=>t_online_deployment_parameters(
              serving_name = c_serving_name
            )
          )
        )
      importing
        e_response = data(ls_deployment_result)
    ).
  catch zcx_ibmx_service_exception into data(lo_deployment_exception).
    " http status 400 (bad request) -> delete existing deployment, if exists, and retry
    "[RAP] out->write( lo_deployment_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_deployment_exception type 'E'.
endtry.
data(lv_deployment_id) = ls_deployment_result-metadata-id.

" wait until deployment is complete
do 20 times.
  try.
      lo_watsonx_ai->deployments_get(
        exporting
          i_space_id      = c_space_id
          i_deployment_id = lv_deployment_id
        importing
          e_response      = data(ls_deployment)
      ).
    catch zcx_ibmx_service_exception into data(lo_deployment_get_exception).
      "[RAP] out->write( lo_deployment_get_exception->get_longtext(  ) ). exit.
      "[PRG] message lo_deployment_get_exception type 'E'.
  endtry.
  if not ls_deployment-entity-status-state eq 'initializing'.
    exit.
  endif.
  wait up to 5 seconds.
enddo.


" INFERENCE

" declare structure of request response
data:
  begin of lv_response,
    begin of payload,
      message type string,
    end of payload,
    path type string,
    method type string,
  end of lv_response.

" call AI Service function endpoint and parse response
data(lv_url) = `/ml/v4/deployments/` && c_serving_name && `/ai_service/mypath`.
data(ls_request_prop) = value zcl_ibmx_service=>ts_request_prop(
  url  = value zcl_ibmx_service=>ts_url(
    path = lv_url
  )
  body = `{ "message": "This message was sent via ABAP" }`
  header_content_type = 'application/json'
).
try.
    data(lo_response) = lo_watsonx_ai->http_post( i_request_prop = ls_request_prop ).
    data(lv_json) = zcl_ibmx_service=>get_response_string( lo_response ).
    zcl_ibmx_service=>parse_json(
      exporting
        i_json       = lv_json
      changing
        c_abap       = lv_response ).
  catch zcx_ibmx_service_exception into data(lo_inference_exception).
    "[RAP] out->write( lo_inference_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_inference_exception type 'E'.
endtry.

" compile CURL command to test deployment endpoint
data(lv_curl_command) =
  `curl -X POST -H "Authorization: Bearer ${IAM_ACCESS_TOKEN}" `
  && `-d '{ "message": "This message was sent via CURL" }' `
  && `"` && ls_deployment-entity-status-inference[ 1 ]-url && `/mypath"`.

" write success message
data(lv_msg) = `Received message "` && lv_response-payload-message && `" from deployed AI Service function. Try CURL command below.`.
  "[RAP] out->write( lv_msg ).
  "[RAP] out->write( lv_curl_command ).
  "[PRG] write: lv_msg.
  "[PRG] write: / lv_curl_command.
watsonx.ai Example (Text Generation)
*  Z_WATSONX_AI_DEMO
*  This sample code runs text generation with a watsonx.ai foundation model.
*
*  Copy this code into
*    - an ABAP report and remove all comment prefixes "[PRG]
*    - a Console Application (RAP) and remove all comment prefixes "[RAP]
*  Adjust credentials and run code.

" instantiate watsonx.ai wrapper class (explicit type declaration is required)
data: lo_watsonx_ai type ref to zcl_ibmx_watsonx_ai_ml_v1.
zcl_ibmx_service_ext=>get_instance(
  exporting
    i_url     = 'https://eu-de.ml.cloud.ibm.com'   " <- ADJUST
    i_apikey  = 'X7Ziqsan...FfOAtAno'              " <- ADJUST
    i_version = '2023-05-29'
  importing
    eo_instance = lo_watsonx_ai ).

" run text generation
try.
    data(lv_prompt) = `The highest mountain in Europe is`.
    lo_watsonx_ai->text_generation(
      exporting
        i_textgenrequest = value zcl_ibmx_watsonx_ai_ml_v1=>t_text_gen_request(

          " prompt
          input = lv_prompt

          " model parameters
          model_id   = 'ibm/granite-13b-chat-v2'
          parameters = value zcl_ibmx_watsonx_ai_ml_v1=>t_text_gen_parameters(
            decoding_method    = 'greedy'
            max_new_tokens     = 20
            repetition_penalty = '1.05'
            stop_sequences = value #( ( `. ` ) )  " stop after first sentence
          )

          project_id = '3e606d0c...459b7c38'       " <- ADJUST
        )
      importing
        e_response = data(ls_generated_document) ).
  catch zcx_ibmx_service_exception into data(lo_service_exception).
    "[RAP] out->write( lo_service_exception->get_longtext(  ) ). exit.
    "[PRG] message lo_service_exception type 'E'.
endtry.

" display generated text
"[RAP] out->write( lv_prompt ).
"[PRG] write lv_prompt.
loop at ls_generated_document-results into data(ls_result).
"[RAP]   out->write( ls_result-generated_text ).
"[PRG]   write / ls_result-generated_text.
endloop.

API Reference

GitHub Pages contain the ABAP Client Library for watsonx API Reference.

Support and Feedback

The ABAP SDK is a community SDK for IBM watsonx, created by the IBM watsonx development community and SAP's ABAP development community – written by ABAPers from IBM Systems. Therefore, as a community release it is not updated with the same schedule as IBM-supported SDKs and does not include support by IBM.

The code in this repository is generated from the OpenAPI specs of the supported services. Hence, pull requests must not be integrated directly into the code. If you would like to request a new feature or report a bug, please raise an issue instead of sending a pull request.

License

This library is licensed under the Apache 2.0 license.

About

ABAP code for using IBM watsonx services with SAP NetWeaver Application Server, imported via abapGit

Resources

License

Security policy

Stars

7 stars

Watchers

4 watching

Forks

Packages

 
 
 

Contributors

Languages