Claude Function Calling with ABAP: Safe BAPI Triggers
Claude API

Claude Function Calling with ABAP: Safe BAPI Triggers

One of the most exciting capabilities that modern LLMs bring to enterprise development is function calling — the ability for the model to not just generate text, but to decide when and how to invoke real code. When you wire this up with ABAP and live BAPIs, you are no longer building a chatbot. You are building an AI agent that can read purchase orders, create deliveries, or check stock — all driven by natural language intent.

This article walks you through how to implement claude function calling abap integration safely: defining tool schemas, parsing Claude's structured responses, routing to the right BAPI, and — critically — keeping humans in the loop before anything irreversible happens.

If you have already set up the basic Claude API connection from ABAP (no middleware required), make sure you have read Claude API ABAP Integration Without BTP first. This article builds directly on top of that foundation.

What Is Function Calling in Claude?

Claude's function calling (also called "tool use" in Anthropic's API) lets you declare a set of tools — with names, descriptions, and JSON Schema parameter definitions — and pass them along with your user message. Claude reads the user's intent and, if appropriate, responds not with prose but with a structured JSON payload naming the tool it wants to call and the arguments it has extracted.

Your ABAP code then reads that payload, validates the arguments, calls the actual BAPI, and optionally feeds the result back to Claude for a final natural-language reply.

The key insight: Claude does not call anything itself. It tells you what it wants to call. You are always in control of execution. This is what makes the pattern safe for production SAP systems.

The Architecture in Three Steps

  1. Define tools — describe your BAPIs as JSON Schema objects and send them with the API request.
  2. Parse the tool call — detect when Claude returns a tool_use block and extract arguments.
  3. Execute and respond — validate, call the BAPI, return the result to Claude for a human-readable summary.

Let's go through each step with real ABAP code.

Step 1 — Define Your BAPI as a Claude Tool

The tool definition is a JSON object you include in the API request body under the tools array. Here is a tool that wraps BAPI_MATERIAL_AVAILABILITY to check stock levels:

METHOD build_tool_definitions.
  DATA(lv_tools) = `[
    {
      "name": "check_material_stock",
      "description": "Check available stock for a material at a given plant. Use this when the user asks about stock, availability, or inventory levels.",
      "input_schema": {
        "type": "object",
        "properties": {
          "material": {
            "type": "string",
            "description": "SAP material number, e.g. MAT-1000"
          },
          "plant": {
            "type": "string",
            "description": "SAP plant code, e.g. 1000"
          }
        },
        "required": ["material", "plant"]
      }
    }
  ]`.

  rv_tools = lv_tools.
ENDMETHOD.

A second tool for a write operation — creating a transfer order — would follow the same pattern but with a much more explicit description warning about what it does. Description quality matters enormously here. Claude uses the description, not just the schema, to decide when to invoke a tool. Be precise about intent.

Step 2 — Sending the Request to Claude

The request body now needs a tools field alongside messages. Here is the relevant ABAP snippet:

METHOD call_claude_with_tools.
  DATA: lo_http_client TYPE REF TO if_http_client,
        lv_request_body TYPE string,
        lv_response_body TYPE string.

  " Build request payload
  DATA(lv_tools) = build_tool_definitions( ).

  lv_request_body = |\{| &
    |"model": "claude-opus-4-5",| &
    |"max_tokens": 1024,| &
    |"tools": { lv_tools },| &
    |"messages": [| &
    |  \{ "role": "user", "content": "{ escape_json( iv_user_message ) }" \}| &
    |]| &
    |\}|.

  " HTTP call using IF_HTTP_CLIENT (same pattern as base integration)
  CALL METHOD lo_http_client->request->set_cdata
    EXPORTING data = lv_request_body.

  lo_http_client->send( ).
  lo_http_client->receive( ).

  lv_response_body = lo_http_client->response->get_cdata( ).

  " Parse and route
  handle_claude_response( lv_response_body ).
ENDMETHOD.

Notice I am using string concatenation with |...| template literals. For production, you should build this with a proper JSON library or at minimum a dedicated JSON builder class to avoid injection issues. The OpenAI API ABAP authentication guide has a solid pattern for HTTP client reuse that applies here too.

Step 3 — Parsing the Tool Call Response

When Claude wants to use a tool, the response content array contains a block with type: "tool_use". Your parser needs to detect this and branch accordingly:

METHOD handle_claude_response.
  " Simplified JSON parsing — use /UI2/CL_JSON or similar in production
  IF iv_response_body CS '"type": "tool_use"'.
    process_tool_call( iv_response_body ).
  ELSE.
    " Regular text response — display to user
    DATA(lv_text) = extract_text_content( iv_response_body ).
    display_to_user( lv_text ).
  ENDIF.
ENDMETHOD.

METHOD process_tool_call.
  DATA: lv_tool_name TYPE string,
        lv_tool_id   TYPE string,
        ls_input     TYPE ty_tool_input.

  " Extract tool name and input from response JSON
  lv_tool_name = extract_json_value( iv_json = iv_response_body
                                     iv_path = 'content[0].name' ).
  lv_tool_id   = extract_json_value( iv_json = iv_response_body
                                     iv_path = 'content[0].id' ).

  CASE lv_tool_name.
    WHEN 'check_material_stock'.
      ls_input-material = extract_json_value( iv_json = iv_response_body
                                              iv_path = 'content[0].input.material' ).
      ls_input-plant    = extract_json_value( iv_json = iv_response_body
                                              iv_path = 'content[0].input.plant' ).
      execute_stock_check( is_input  = ls_input
                           iv_tool_id = lv_tool_id ).
    WHEN 'create_transfer_order'.
      " Write operation — always confirm before executing
      request_user_confirmation( iv_tool_name = lv_tool_name
                                 iv_raw_input = iv_response_body ).
    WHEN OTHERS.
      " Unknown tool — log and fail gracefully
      log_unknown_tool( lv_tool_name ).
  ENDCASE.
ENDMETHOD.

Executing the BAPI Safely

Read-only BAPIs like availability checks can execute immediately. Write BAPIs must go through a confirmation step. This is not optional — it is the whole point of building this safely.

METHOD execute_stock_check.
  DATA: ls_av_qty TYPE bapimrpavailability,
        lt_return TYPE TABLE OF bapiret2.

  " Input validation before BAPI call
  IF is_input-material IS INITIAL OR is_input-plant IS INITIAL.
    return_tool_error( iv_tool_id = iv_tool_id
                       iv_message = 'Material and plant are required' ).
    RETURN.
  ENDIF.

  " Whitelist check — only allow plants this user can access
  IF NOT is_plant_authorized( is_input-plant ).
    return_tool_error( iv_tool_id = iv_tool_id
                       iv_message = 'Plant not authorized for current user' ).
    RETURN.
  ENDIF.

  CALL FUNCTION 'BAPI_MATERIAL_AVAILABILITY'
    EXPORTING
      plant    = is_input-plant
      material = is_input-material
    IMPORTING
      av_qty_plt = ls_av_qty
    TABLES
      return = lt_return.

  " Build result and send back to Claude for summarization
  DATA(lv_result) = |Material { is_input-material } at plant { is_input-plant }: | &
                    |available qty = { ls_av_qty-avail_qty }|.

  send_tool_result_to_claude( iv_tool_id = iv_tool_id
                               iv_result  = lv_result ).
ENDMETHOD.

Two things to notice here. First, I am checking plant authorization against the current SAP user's authorizations — not just trusting Claude's output. Claude extracted the plant from user input; a user could say "plant 9999" for a plant they have no business accessing. Your authorization objects still apply. Second, I am returning errors to Claude via the tool result, not just raising an ABAP exception. This lets Claude give the user a sensible error message in natural language.

Feeding the Result Back to Claude

The multi-turn conversation looks like this: user message → Claude tool call → your BAPI execution → tool result back to Claude → Claude's final text response to user. The second Claude call includes the original messages plus the tool result:

METHOD send_tool_result_to_claude.
  DATA(lv_request) = |\{| &
    |"model": "claude-opus-4-5",| &
    |"max_tokens": 512,| &
    |"tools": { build_tool_definitions( ) },| &
    |"messages": [| &
    |  \{ "role": "user", "content": "{ mv_original_user_message }" \},| &
    |  \{ "role": "assistant", "content": { mv_assistant_tool_use_block } \},| &
    |  \{ "role": "user", "content": [| &
    |    \{| &
    |      "type": "tool_result",| &
    |      "tool_use_id": "{ iv_tool_id }",| &
    |      "content": "{ escape_json( iv_result ) }"| &
    |    \}| &
    |  ] \}| &
    |]| &
    |\}|.

  " Make the second HTTP call and display final response
  DATA(lv_final_response) = call_claude_api( lv_request ).
  display_to_user( extract_text_content( lv_final_response ) ).
ENDMETHOD.

Claude will now generate a clean, human-readable response like: "The available stock for material MAT-1000 at plant 1000 is 340 units." Your user never sees raw JSON.

Safety Rules You Cannot Skip

Function calling in production SAP is powerful enough to do real damage if you are careless. Here are the non-negotiable rules:

  • Read operations run automatically; write operations require confirmation. Always. No exceptions for "trusted" users.
  • Validate all extracted arguments before passing to any BAPI. Claude is doing its best, but it can misread material numbers or plant codes, especially for ambiguous input.
  • Honor SAP authorization objects. Your BAPI call runs under the current dialog user's session. But add an explicit pre-check so you can return a clean Claude-readable error rather than a cryptic BAPI RETURN table message.
  • Log every tool invocation. Write to a custom Z-table: timestamp, user, tool name, input arguments, result, whether it was confirmed or auto-executed. This is your audit trail.
  • Limit your tool surface area. Start with two or three read-only BAPIs. Prove the pattern works and build trust before adding write operations.

For ideas on how to structure the quality review of AI-assisted ABAP code in general, the LLM-generated ABAP code quality review guide has a good checklist that adapts well here.

Handling Multi-Step Tool Chains

Users will inevitably ask questions that require more than one BAPI. "Show me the stock for MAT-1000 at all my plants" might trigger multiple check_material_stock calls in sequence. Claude can request tools iteratively — each response might contain another tool call rather than a final text response.

Your ABAP loop needs to handle this:

METHOD run_agent_loop.
  DATA: lv_iteration TYPE i VALUE 0,
        lv_max_iter  TYPE i VALUE 5,  " Safety cap
        lv_response  TYPE string,
        lv_done      TYPE abap_bool VALUE abap_false.

  lv_response = call_claude_with_tools( iv_user_message = iv_user_message ).

  WHILE lv_done = abap_false AND lv_iteration < lv_max_iter.
    ADD 1 TO lv_iteration.

    IF lv_response CS '"type": "tool_use"'.
      " Process tool call, get result, send back
      DATA(lv_tool_result) = process_and_execute_tool( lv_response ).
      lv_response = continue_conversation_with_result( lv_tool_result ).
    ELSE.
      " Final text response
      display_to_user( extract_text_content( lv_response ) ).
      lv_done = abap_true.
    ENDIF.
  ENDWHILE.

  IF lv_iteration >= lv_max_iter.
    display_to_user( 'Request required too many steps. Please be more specific.' ).
  ENDIF.
ENDMETHOD.

The iteration cap is important. Without it, a poorly described tool or an ambiguous user query can send you into an infinite loop of tool calls. Five iterations is usually plenty for real business queries.

Practical Use Cases Worth Implementing First

Based on what tends to deliver value quickly in SAP environments:

  • Stock availability checks — read-only, immediate, high query volume from warehouse and logistics teams
  • Purchase order status lookup — BAPI_PO_GETDETAIL, again read-only, asked constantly by procurement
  • Vendor master display — useful for accounts payable teams who know a name but not a vendor number
  • Delivery status queries — shipping teams love this; combine with BAPI_OUTB_DELIVERY_GET_LIST

If you are also exploring AI for document extraction in SAP — like processing inbound invoices — the approach in AI invoice extraction with SAP LLM vision complements this pattern well. You extract structured data from a document, then use function calling to post it through the right BAPI.

What Comes Next

Once you have the basic function calling loop working reliably, the natural next steps are:

  • Building a persistent conversation context store (so users can refer to "the order I mentioned earlier")
  • Adding anomaly detection on top of your BAPI results — surface outliers before the user even asks. The approach in AI anomaly detection for SAP transactional data gives you a solid starting point for this layer.
  • Wrapping your tool executor in an authorization-aware service class that multiple frontends (Fiori, Teams bot, email assistant) can call through a common interface

The function calling pattern is what separates an LLM integration that impresses in a demo from one that genuinely replaces manual lookup work. Getting the safety layer right from the start — authorization checks, confirmation flows, audit logging — is what makes it something you can actually deploy without losing sleep.

Build the read-only tools first. Ship them. Get feedback. Then carefully expand to write operations once your team trusts the plumbing.

Further Reading

Prefer OpenAI's API? See Calling the OpenAI API from ABAP: Authentication, Streaming, and Error Handling.

This article is part of the SAP AI integration & architecture hub — patterns, use cases, and guardrails for AI in SAP systems.