CAPで作った簡易的な発注アプリを @cap-js/agents でAIエージェント化し、データの保存先をローカルのABAP環境のZテーブルにして、SAP BTP上で動かしました。Fiori Elementsの画面から発注を登録し、チャットで発注を検索して承認するところまで試しています。
この記事は概要編です。全体の構成、設計で決めたこと、はまった点の一覧、動かした結果と制約をまとめます。個々の対処の詳細は、別の記事に分けて書く予定です。
個人の検証環境で試した記録です(2026年9月末時点)。認証を付けていないなど、本番にそのまま持ち込める構成ではありません。制約は「制約と注意」にまとめています。
作ったもの
検証用に作った、簡易的な発注アプリです。
ここでいう「発注」は、SAP標準の購買発注(ME21NやEKKO/EKPO)ではありません。検証用にアドオンで作った簡易機能で、Zテーブル2つ(ヘッダと明細)に保存します。仕入先は検証用のデータをCAP側に持ち、品目は自由入力です。SAPのマスタや、入庫・請求といった後続の処理とは連動していません。
構成要素は3つです。
- 画面:Fiori Elements(List Report / Object Page)で発注を登録・承認・削除する
- エージェント:チャットで発注を検索・集計し、承認待ちの発注を承認・却下する
- データ:発注ヘッダ・明細は、ローカルのABAP環境のZテーブルに保存する。BTP上のCAPにはコピーを持たず、自作したRAPのOData V4をその場で読み書きする
業務ルールは、入力チェック(数量・単価・発注日・品目の重複)と、金額による承認の判定です。合計金額が10万円を超える発注は「承認待ち」になり、それ以下は自動で「承認済み」になります。
動かしたシナリオ
担当者が画面で発注を登録し、承認者がチャットで内容を確かめて承認する、という流れです。認証を付けていないので、担当者と承認者の区別は想定上のものです。
| # | 誰が | どこで | 何をするか | データの動き |
|---|---|---|---|---|
| 1 | 担当者 | 画面 | 「作成」を押し、発注日・仕入先・明細(品目・数量・単位・単価)を入力する | 下書きとしてCAP側に保存される。SAPにはまだ書かれない |
| 2 | 担当者 | 画面 | 「保存」を押す | CAPが入力チェックと承認の判定をして、SAPのZテーブルに登録する。合計が10万円を超えていれば「承認待ち」になる |
| 3 | 承認者 | チャット | 「何件?」「内訳は?」「承認待ちの詳細は?」と聞く | エージェントがSAPのZテーブルを検索して答える |
| 4 | 承認者 | チャット | 「承認して」と頼む | エージェントが承認のToolを呼び、Zテーブルの承認ステータスと承認者が更新される |
| 5 | 承認者 | SAP側 | Zテーブルの該当行を見る | 「承認済み」になり、承認者が入っている |
4の承認には、実行前に人の確認を挟む指定を付けています。ただし、今回は機能せず、確認なしでそのまま承認されました(「制約と注意」を参照)。
承認・却下は画面からもできます。承認待ちの発注を選んで「承認」か「却下」を押すと、承認者の入力画面が開くので、名前を入れて実行します。
画面で見ると、次のようになります。
シナリオ1の登録画面です。発注番号・発注日・通貨は自動で入ります。
シナリオ2のあとの一覧です。合計金額と承認ステータスが並びます。ちょうど10万円の発注は自動で承認済みになり、10万円を超えた3件は承認待ちになりました。そのうち2件は、画面から承認・却下したあとの状態です。
シナリオ3のチャットです。件数、内訳、承認待ちの詳細を順に聞いています。
なぜ試したか
CAPを学んだ機会に、2026年8月に公開された @cap-js/agents を使ってみたくなったのがきっかけです。確かめたかったのは次の2点です。
- 保存先をSAP側(ABAP)に置いたまま、CAPのアプリをエージェント化できるか
- SAP AI Coreを使わずにエージェントを動かせるか
標準のODataをエージェントから使うことについてはSAP API Policyの確認が必要だと考え、今回は自作(Z)のサービスだけを対象にしました。ポリシーの解釈はSAP社に確認していません。
環境
| 項目 | 内容 |
|---|---|
| CAP | Node.js 24、@sap/cds 10.0.3 |
| エージェント |
@cap-js/agents 0.9.7(1.0未満)、A2Aで公開 |
| LLM | OpenAI gpt-4o-mini(@langchain/openai 1.6.0) |
| 画面 | Fiori Elements、OData V4、UI5 1.152(CDNから読み込み) |
| SAP側 | ABAP Cloud Developer Trial 2025(Docker)。RAPはmanaged・Draftなし |
| BTP | トライアル、Cloud Foundry。approuter、Connectivity、Destination、Cloud Connector。XSUAAは未使用 |
| 開発 | VS Code+Claude Code拡張機能、Windows 11 |
全体構成
図の番号に沿って説明します。
- 編集中:Fiori Elementsで入力中の下書き(Draft)は、CAP側のSQLiteだけに保存します。SAPには書きません。
- 保存時:CAPからCloud Connectorを通ってRAPのOData V4へ、ヘッダと明細を1回のDeep Insertで登録します。確定した発注を持つのはZテーブルだけです。
- チャット:エージェントがOpenAIのモデルで推論し、呼び出すToolを決めます。このとき、Toolの結果(発注データ)がBTPの外に送られます。
- Tool実行:検索と、承認・却下です。エージェントはCAPのサービスを通るので、SAPへの経路は画面と同じです。
BTPからローカルへの接続は、DestinationとConnectivity、Cloud Connectorを使いました。Cloud Connectorはローカル側で動かし、そこからBTPへトンネルを張ります。Claude Codeは開発時だけ使っていて、実行時の通信経路には入りません。
設計で決めたこと
ここでは考え方だけを書きます。コードは詳細編に回します。
下書きはCAP、確定データはSAP
当初はDraftを使わず、保存時にそのままSAPへ登録するつもりでした。しかし、UI5 1.152とこの構成では、Draft(かスティッキーセッション)が無いと「作成」ボタンが出ず、明細の行追加もできませんでした。
そこで、役割を2つに分けました。
- 編集中:入力途中の下書きは、CAP側のSQLiteだけに置きます。SAPには何も書きません。
- 保存時:CAPが入力チェックと承認の判定をしてから、ヘッダと明細をまとめて1回でSAPに登録します。
CAPのサービスは、cds import で取り込んだRAPのエンティティをそのまま公開し、そこにDraftを有効にしています。SAP側のRAPはDraftなしのままです。
「Draftが無いと作成できない」のは、この構成で確認した挙動です。Fiori Elementsの一般的な仕様なのか、外部モデルから引き継いだ注釈の影響なのかは、切り分けられていません。RAP側にDraftを持たせる構成は試していません。
RAPは薄く、業務ルールはCAPに置く
RAPはmanaged・Draftなしで、ABAPの実装クラスもありません。登録・更新・削除を公開し、作成者と日時を記録するだけです。
入力チェック、承認の判定、キー(UUID)と発注番号の採番は、CAPのサービス層に置きました。もともとCAPにあったルールをそのまま残した形で、画面とエージェントのどちらから操作しても同じルールが効きます。
承認専用のアクションもRAPには作っていません。承認・却下はCAPで受け、RAPへは発注ヘッダの更新として送っています。
エージェントはCAPのサービスをそのまま使う
@cap-js/agents では、サービスに @agent の指定を付けると、エンティティの検索と、サービス直下のアクションがエージェントのToolになります。エージェント専用の仕組みを別に作る必要はありませんでした。
気を付けた点は3つです。
- 画面用のアクションはToolにならない:特定の発注に紐付くアクション(バウンドアクション)は対象外でした。そのため、発注番号で呼ぶアクションを別に用意し、中では画面と同じ承認処理を呼んでいます。
-
承認・却下には人の確認を挟む:実行前に人の確認を待つ指定(
@agent.hitl)を付けました。ただし、今回は機能しませんでした(「制約と注意」を参照)。 - ODataの公開を明示する:エージェントの指定だけだとODataの公開が外れ、画面が404になりました。
LLMについては、プラグインが標準で使えるのはSAP AI Core、Anthropic、モックの3種類でした。OpenAIを使うため、モデルを用意する処理(buildModel)を差し替えています。差し替えは10行ほどで済み、SAP AI Coreなしで動かせました。
はまったところ(一覧)
問題はどれも、層と層の境目で起きました。エラーを見たら、まずどの層の話かを切り分けると調べやすくなります。
| 層 | 症状 | 対処 |
|---|---|---|
| SAP(OData V4の設定) | 登録が500 Virus scan profile /IWBEP/V4/ODATA_UPLOAD is not active
|
/IWBEP/V_V4_SETT で VIRUS_SCANER_IGNORED = X
|
| SAP(DDIC) | 承認ステータスが大文字で保存される | ドメインを小文字可のもの(CHAR20 → TEXT20)に変更 |
| CAP(外部モデル) | Draftが作られない、下書きの読み書きが501 | 引き継がれた @cds.external と @cds.persistence.skip を打ち消す |
| CAP(注釈) | 明細の追加が405、「作成」でIDの入力を求められる | 引き継がれた InsertRestrictions を外し、IDを @Core.Computed にする |
| CAP → SAP | 一覧の取得が400 | Draftの一覧でCAPが作る not in の条件を、= と or に書き換える |
| CAP → SAP | 登録が400 Error while parsing an XML stream
|
下書きから文字列で来るDecimalを、数値にして送る |
| BTP | approuterが500 | XSUAAを使っていないので authenticationMethod: none にする |
| BTP(エージェント) |
content safety check is temporarily unavailable、チャット画面が404 |
LLMをOpenAIに差し替え、server.index: true にする |
ウイルススキャンを無視する設定は、スキャナの無いローカルの検証環境だから行ったものです。本番では、スキャンのプロファイルを正しく設定すべきです。
動かした結果
2026年9月末の時点で、次のことを確認しました。
- ローカル:テスト24件が成功。テストは実際にSAP側へ登録し、最後に削除します。画面から明細2行の発注を登録し、Zテーブルに入ったこと、承認・却下でSAP側が更新されることを確認しました。
- BTP:approuter経由で、登録 → 読み取り → 削除が動きました。エージェントは、仕入先の一覧と発注の件数を、SAP側の実データどおりに答えました。
- チャットでの承認:シナリオの3〜5を通しで実行し、Zテーブルの該当行が承認済みになり、承認者が更新されたことを確認しました。
シナリオ4で、チャットから承認を頼んだところです。
シナリオ5で、承認後にSAP側のデータを見たところです。test005 の行が「Approved」になり、承認者には「DEVELOPER」が入っています。画面から承認・却下した2件の承認者は伏せています。
制約と注意
検証用として割り切った点です。同じ構成を試す場合は、特に上の3つに注意してください。
- 認証がありません。 XSUAAを使っておらず、URLを知っていれば誰でも操作できます。試すときだけ起動し、終わったら止めています。
- 承認者は自己申告です。 画面から承認するときは、開いた入力画面に承認者の名前を入れます。ログインしたユーザーではありません。チャットから承認したときは、承認者を入力する機会がなく、「DEVELOPER」が入りました。SAPへは固定ユーザーでつないでいるため、SAP側の更新者も常に同じです。
-
人による確認(HITL)が機能していません。
@agent.hitlは指定していますが、チャットで承認を頼むと、確認なしでそのまま実行されました。上の画面コピーのとおりです。原因の調査と対応は今後の課題です。 - 発注データが外部のLLMに送られます。 OpenAIのAPIを直接呼んでいます。
- 2つのシステムをまたぐ不整合が起こり得ます。 SAPへの登録が成功したあとにCAP側で失敗すると、発注と下書きの両方が残ります。
- 登録後の編集はできません。 直すときは、削除して登録し直します。同時に承認した場合の排他も考慮していません。
- 下書きはメモリ上のSQLiteにあります。 CAPを再起動すると、保存していない下書きは消えます。
Claude Codeとの分担
実装の大部分はClaude Code(VS Codeの拡張機能)に任せました。
| 担当 | 内容 |
|---|---|
| Claude Code | テーブル定義の案、RAP一式(ADTのREST APIで作成・有効化・公開)、CAPのコード、テスト、ヘッドレスChromeでの画面確認、BTPへのビルドとデプロイ、ドキュメント |
| 自分 | 方針の判断(Draftの方式、データの置き場所)、SE11でのテーブル作成、ウイルススキャンの設定、cf login とAPIキーの入力、デプロイの承認 |
cf deploy はhookで止め、自分が承認したときだけ実行される仕組みにしています。成果の確認は、別のセッションのClaudeにドキュメントと突き合わせさせ、出てきた指摘に自分が判断を返す形で行いました。
保存先をSAP側に移す作業は、最初の指示から完了の報告まで約1時間40分でした。ウイルススキャンの設定作業を含みます。
今後試したいこと
- XSUAAによるログインと権限、ログインユーザーをSAPへ引き継ぐ接続
- 人による確認(HITL)が機能しない原因の調査と対応
- チャットからの承認で、承認者を指定できるようにする
- プラグイン付属のチャット画面以外のUI。付属の画面ではファイルのアップロードができませんでした
- 他のエージェントからA2Aで呼び出す
- RAP側にDraftを持たせる構成、登録後の編集
- 2つのシステムをまたぐ不整合への対策
まとめ
- 下書きはCAP、確定データはSAPに分けることで、Fiori ElementsのDraftと外部のODataを両立できました。
-
@cap-js/agentsは、buildModelを差し替えればSAP AI CoreなしでもBTP上で動きました。 - はまった点の多くは、
cds importしたモデルの注釈と、CAPからSAPへ送るクエリの差でした。 - 認証と承認者の扱いは未対応で、人による確認も機能していません。「AIが承認する」構成では、ここが本番化の前提になります。
詳細編では、DraftとODataの対処、エージェントの設定、Claude Codeでの進め方を順に書く予定です。








