
IBM watsonx is an all-in-one platform to train, validate, tune and deploy models for generative AI. It comes with a compilation of deployed generative AI models that can be used out-of-the-box. The ABAP SDK for IBM watsonx is a set of ABAP classes and data types that allows generative AI inferencing by pure ABAP means.
It is assumed that you have some basic knowledge in generative AI, in particular prompt engineering. Otherwise get familiar with the most important terms, for example by reading this blog.
Follow the steps in this blog to implement a generative AI test data generator in ABAP. The program will
Throughout this blog all parts that are related to watsonx are explained in detail. Other parts are only mentioned and not elaborated. However, at the end of this post you find the full code of a working demo program. Use this as a reference.
If you do not already have an IBM watsonx or an IBM Cloud account, "Start your free trail" on https://www.ibm.com/watsonx and proceed through the onboarding process.
After login to watsonx, you get to the home screen. See the right upper corner for the region where your watsonx instance resides. You need this information later.
Create a new project by clicking the plus sign next to the "Projects" header line.
Enter a name and a description for your project and click "Create".
Inside the watsonx project, click tab "Manage" and copy and save the "Project ID". You will need it later.
You must associate a Watson Machine Learning service to your project. Click on "Service & integration", tab "IBM services". Click "Associate service", check service of type "Watson Machine Learning" and click "Associate"
If there is no service of type "Watson Machine Learning" that can be selected, click "New Service" and instantiate a new Waston Machine Learning service.
Next you must generate an apikey. To do so, click on the navigation menu at the left upper corner and select "Administration" → "Access (IAM)". Select "API keys" and click "Create". Enter a name and a description and click "Create". An apikey is created and shown to you. Copy and save the apikey; you will not be able to see it again.
You need to identify the URL to the watsonx API. It complies to scheme
https://<region code>.ml.cloud.ibm.com
where <region code> is the region code of the region where your watsonx instance resides (see above). For exmaple eu-de for Europe (Frankfurt) or us-south for Dallas.
Finally, you have the credentials that are needed to access watsonx. (Certainly, the values below are invalid and for demonstration only.)
url: https://eu-de.ml.cloud.ibm.com
apikey: t8a0OQpVgs6z9jZNKGt9Ghk498f0biyZvRf-E4eqw11K
project_id: e8db8f7c-36f6-40ca-804a-238c6e557c46
You must import the ABAP SDK for IBM watsonx using abapGIT pull. If your runtime environment is NetWeaver 7.50 (or above) or S/4HANA on premise, follow instructions on https://github.com/IBM/abap-sdk-nwas-x. For the BTP ABAP Environment, follow instructions on https://github.com/IBM/abap-sdk-btp-x.
For this tutorial a set of tables with some contents is required. If such tables are not available or not known to you, import the ABAP Flight Reference Scenario to your environment and generate the demo data.
Select or create a database table that you want to populate with generative AI data. For example, create package ZWATSONX under structure package ZLOCAL and create table ZIBM_CUSTOMER in that package. This table can be a copy of table /DMO/CUSTOMER. Thus, the database table definition may look like below.
@EndUserText.label : 'Copy of table /DMO/CUSTOMER'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zibm_customer {
key client : abap.clnt not null;
key customer_id : /dmo/customer_id not null;
first_name : /dmo/first_name;
last_name : /dmo/last_name;
title : /dmo/title;
street : /dmo/street;
postal_code : /dmo/postal_code;
city : /dmo/city;
country_code : land1;
phone_number : /dmo/phone_number;
email_address : /dmo/email_address;
}
You must build a prompt that instructs the generative AI model to generate test data. This works best if you use few-shot prompting, which means to include some examples in the prompt. For the sake of convenience and flexibility, do not hard-code those examples. Instead, implement some ABAP code that reads a small portion of the contents and the data dictionary information of some given tables and generates examples for the prompt. A single example can be added to the prompt using this format:
[example]
records: n
format: <table field names and types>
data: <n table records in JSON format>
[end]
The complete prompt looks like this:
Generate data in JSON format as shown in examples below. Insert tag [end] after given number of records.
[example]
<example data generated from table #1>
[end]
[example]
<example data generated from table #2>
[end]
[example]
<example data generated from table #3>
[end]
[input]
records: n
format: <table field names and type of target table ZIBM_CUSTOMER>
data:
The tables that are used to generate the examples should in total include all data types that appear in the target table. They should be client-dependent if (and only if) that target table is client-dependent.
After the prompt has been generated, you can call watsonx to execute the prompt and thus generate test data. First you must provide watsonx.ai credentials. You can either specify those in your ABAP code or, recommended, configure table ZIBMX_CONFIG accordingly.
CONSTANTS c_url TYPE string VALUE 'https://eu-de.ml.cloud.ibm.com'.
CONSTANTS c_apikey TYPE string VALUE 't8a0OQpVgs6z9jZNKGt9Ghk498f0biyZvRf-E4eqw11K'.
CONSTANTS c_project_id TYPE string VALUE 'e8db8f7c-36f6-40ca-804a-238c6e557c46'.
Next instantiate the wrapper class for watsonx.ai.
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 = '2023-05-29'
IMPORTING
eo_instance = lo_watsonx_ai ).
Now you can call method text_generation. It calls watsonx and returns the generative AI inference result.
As parameters, you must specify the prompt that has been generated before, the model id and a set of model parameters like decoding method and the maximal number of generated tokens. watsonx supports several generative AI models, for example IBM's Granite models.
Also, you must provide the id of the watsonx project that you have created before.
TRY.
lo_watsonx_ai->text_generation(
EXPORTING
i_textgenrequest = VALUE zcl_ibmx_watsonx_ai_ml_v1=>t_text_gen_request(
" prompt
input = <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 = 2000
repetition_penalty = '1.05'
stop_sequences = VALUE #( ( `[end]` ) ) " stop at [end] tag
)
" watsonx project id
project_id = c_project_id
)
IMPORTING
e_response = DATA(ls_generated_document) ).
CATCH zcx_ibmx_service_exception INTO DATA(lo_service_exception).
" handle exception
ENDTRY.
The inference result is returned in an internal table that contains a single record which holds the generated data in JSON format as indicated in the prompt. Insert the data into the target database table using a JSON parser.
DATA lr_data TYPE REF TO data.
CREATE DATA lr_data TYPE TABLE OF ZIBM_CUSTOMER.
/ui2/cl_json=>deserialize(
EXPORTING
json = ls_generated_document-results[ 1 ]-generated_text
CHANGING
data = lr_data->* ).
INSERT ZIBM_CUSTOMER FROM TABLE @LR_data->*.
Now you are ready to run your ABAP code. After the program has finished, check the data in your table.
CLIENT CUSTOMER_ID FIRST_NAME LAST_NAME TITLE STREET POSTAL_CODE CITY COUNTRY_CODE PHONE_NUMBER EMAIL_ADDRESS
100 001001 John Doe Mr. 34 South Road 12345 Anytown US +1 212-555-1212 johndoe@email.com
100 001002 Jane Smith Ms. 43 East Street 67890 Emeryville US +1 415-555-1234 janesmith@email.com
100 001003 Jim Johnson Dr. 56 North Avenue 55111 Seattle US +1 206-555-5678 jimjohnson@email.com
100 001004 Anna Miller Mrs. 71 West Lane 33333 London GB +44 20-7654-5309 annamiller@email.com
100 001005 Bob Williams Mr. 89 Maple Street 12345 Toronto CA +1 416-555-1111 bobwilliams@email.com
You learnt how to generate a prompt, execute that prompt on a generative AI model deployed on watsonx and process the response. All this is done with ABAP code only while the complexity of the API calls is hidden by the ABAP SDK for IBM watsonx.
The same technique can be used to include generative AI in any business process that is implemented in ABAP.
Here is the full code of a demo program. It was implemented on BTP ABAP Environment and runs as a console application. To run it on a NetWeaver system or on S/4HANA on premise, remove or replace all references to interface if_oo_adt_classrun, including the out->write statements. In all cases you have to adjust watsonx credentials before running the application.
CLASS zwatsonx_genai_data_generator DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun .
PROTECTED SECTION.
PRIVATE SECTION.
METHODS field_data
IMPORTING
i_tabname TYPE string
RETURNING
VALUE(e_text) TYPE string.
METHODS content_data
IMPORTING
i_tabname TYPE string
i_count TYPE i
RETURNING
VALUE(e_text) TYPE string.
METHODS prompt_section
IMPORTING
i_kind TYPE string
i_tabname TYPE string
i_count TYPE i
RETURNING
VALUE(e_text) TYPE string.
METHODS insert_json
IMPORTING
i_json TYPE string
i_tabname TYPE string
RAISING
cx_sy_open_sql_db.
ENDCLASS.
CLASS zwatsonx_genai_data_generator IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
CONSTANTS c_url TYPE string VALUE 'https://eu-de.ml.cloud.ibm.com'. " <-- ADJUST
CONSTANTS c_apikey TYPE string VALUE 't8a0OQpVgs6z9jZNKGt9Ghk498f0biyZvRf-E4eqw11K'. " <-- ADJUST
CONSTANTS c_project_id TYPE string VALUE 'e8db8f7c-36f6-40ca-804a-238c6e557c46'. " <-- ADJUST
CONSTANTS c_tabname TYPE string VALUE 'ZIBM_CUSTOMER'.
CONSTANTS c_count TYPE i VALUE 5.
" compile prompt (featuring 3-shot prompting)
DATA(lv_prompt) =
" instruction
`Generate data in JSON format as shown in examples below. Insert tag [end] after given number of records.` && cl_abap_char_utilities=>newline &&
" examples
prompt_section( i_kind = 'example' i_tabname = '/DMO/AGENCY' i_count = 2 ) &&
prompt_section( i_kind = 'example' i_tabname = '/DMO/AIRPORT' i_count = 4 ) &&
prompt_section( i_kind = 'example' i_tabname = '/DMO/FLIGHT' i_count = 3 ) &&
" input
prompt_section( i_kind = 'input' i_tabname = c_tabname i_count = c_count ).
" instantiate wrapper class for watsonx.ai API
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 = '2023-05-29'
IMPORTING
eo_instance = lo_watsonx_ai ).
" run text generation
TRY.
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 = 2000
repetition_penalty = '1.05'
stop_sequences = VALUE #( ( `[end]` ) ) " stop at [end] tag
)
" watsonx project id
project_id = c_project_id
)
IMPORTING
e_response = DATA(ls_generated_document) ).
CATCH zcx_ibmx_service_exception INTO DATA(lo_service_exception).
out->write( `ERROR when generating text: ` && lo_service_exception->get_longtext( ) ). EXIT.
ENDTRY.
" collect generated texts (single record in ls_generated_document-results is expected)
DATA(lv_json) = ``.
LOOP AT ls_generated_document-results INTO DATA(ls_result).
lv_json = lv_json && ls_result-generated_text.
ENDLOOP.
" if LLM has generated [end] tag, remove it
REPLACE ALL OCCURRENCES OF '[end]' IN lv_json WITH ''.
" insert JSON data into table
TRY.
insert_json( i_json = lv_json i_tabname = c_tabname ).
CATCH cx_sy_open_sql_db INTO DATA(lo_osql_exception).
out->write( `ERROR when inserting data: ` && lo_osql_exception->get_longtext( ) ). EXIT.
ENDTRY.
" success message
out->write( `Data in table ` && c_tabname && ` that has been generated by watsonx generative AI: ` ).
DATA lr_data TYPE REF TO data.
CREATE DATA lr_data TYPE TABLE OF (c_tabname).
SELECT * FROM (c_tabname) INTO TABLE @LR_data->*.
out->write( lr_data->* ).
ENDMETHOD.
METHOD field_data.
" generates field type information
" example: table has CHAR field of length 8, INT field and STRING field
" e_text = FIELDNAME1,C(8) | FIELDNAME2,I | FIELDNAME3,g |
DATA ref_descr TYPE REF TO cl_abap_structdescr.
DATA lt_comp TYPE abap_compdescr_tab.
" initialize return value
e_text = ``.
" get field type information
ref_descr ?= cl_abap_typedescr=>describe_by_name( i_tabname ).
lt_comp[] = ref_descr->components[].
" compile fields type list
LOOP AT lt_comp INTO DATA(ls_comp).
" get type length info
DATA(lv_length) = ``.
CASE ls_comp-type_kind.
WHEN 'C' OR 'N'.
lv_length = condense( CONV string( ls_comp-length DIV 2 ) ). " UTF-16 length to number of chars
WHEN 'P'.
lv_length = condense( CONV string( ls_comp-length ) ) && `,` && condense( CONV string( ls_comp-decimals ) ).
ENDCASE.
IF NOT lv_length EQ ''. lv_length = `(` && lv_length && `)`. ENDIF.
" add field data to return value
e_text = e_text && ls_comp-name && `,` && ls_comp-type_kind && lv_length && ` | `.
ENDLOOP.
ENDMETHOD.
METHOD content_data.
" returns first rows of a table in JSON notation
DATA lr_data TYPE REF TO data.
e_text = `[`.
DATA(lv_separator) = ``.
" read table content
CREATE DATA lr_data TYPE TABLE OF (i_tabname).
SELECT *
FROM (i_tabname)
INTO TABLE @LR_data->*
UP TO @i_count ROWS.
" serialize record-wise and add newlines
LOOP AT lr_data->* ASSIGNING FIELD-SYMBOL(<ls_reocord>).
e_text = e_text && lv_separator && /ui2/cl_json=>serialize( data = <ls_reocord> ).
lv_separator = `,` && cl_abap_char_utilities=>newline.
ENDLOOP.
e_text = e_text && `]`.
ENDMETHOD.
METHOD prompt_section.
" returns example table data to be added to prompt, i.e.
" [example]
" records: 2
" format: PET,C(8) | AGE,I | COMMENT,g |
" data:
" [{"PET": "cat", "AGE": 4, "COMMENT": "my pet"},
" {"PET": "dog", "AGE": 3, "COMMENT": "your pet"}]
" [end]
e_text =
`[` && i_kind && `]` && cl_abap_char_utilities=>newline &&
`records: ` && condense( CONV string( i_count ) ) && cl_abap_char_utilities=>newline &&
`format: ` && field_data( i_tabname = i_tabname ) && cl_abap_char_utilities=>newline &&
`data:` && cl_abap_char_utilities=>newline.
IF i_kind EQ 'example'.
e_text = e_text && content_data( i_tabname = i_tabname i_count = i_count ) && cl_abap_char_utilities=>newline &&
`[end]` && cl_abap_char_utilities=>newline && cl_abap_char_utilities=>newline.
ENDIF.
ENDMETHOD.
METHOD insert_json.
" inserts JSON data into database table
DATA lr_data TYPE REF TO data.
CREATE DATA lr_data TYPE TABLE OF (i_tabname).
/ui2/cl_json=>deserialize( EXPORTING json = i_json CHANGING data = lr_data->* ).
DELETE from (i_tabname).
INSERT (i_tabname) FROM TABLE @LR_data->*.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
3 | |
3 | |
3 | |
2 | |
2 | |
2 | |
2 | |
1 | |
1 | |
1 |