まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:agent

IBM Cloud から提供されている生成 AI の自動化ソリューションである watsonx Orchestrate の agent builder を使ってみました。システムプロンプトや RAG 、 MCP 、外部エージェントといった生成 AI の補助機能を簡単に使って独自エージェントを定義したり、定義したエージェントを外部公開したり、といったことが簡単に実現できる IBM の生成 AI ソリューションです(IBM Cloud アカウントがあると1か月間無料トライアルで使えます):
2025121801


ここで作成した自分のエージェントを REST API として公開することもできますが、より簡単な外部連携の方法として、小さなスクリプト(JavaScript)を HTML に貼り付けて、その HTML ページ内にチャット機能を埋め込む形でエージェントを使う、といったこともできます(embedded agent 機能と呼びます):
2025121802
(agent builder を使って作ったエージェントを・・)


2025121803
(外部ウェブページに貼り付けてチャットボットとして活用する)


この embedded agent を使うことで HTML 内を編集可能なウェブページであれば(WordPress のような CMS も含めて)作成したエージェントを使ったチャットボット機能を任意のページに埋め込むことができるようになります。比較的簡単な外部連携方法の1つです。


しかし、この embedded agent 機能を私が使おうとしたところうまく動きませんでした。そこで色々調べた所、IBM Cloud 版の watsonx Orchestrate では現状(2025年12月) embedded agent はデフォルト設定のままでは正しく動作しない、ということがわかりました。2026 年初頭のアップデートによって IBM Cloud 画面内からの設定変更が可能になるような予定もあるらしいのですが、現時点では REST API を呼び出す形で watsonx Orchestrate の設定内容を変更(Security を Disabled に変更)しないと embedded agent が使えない、ということが分かりました。

そのためのセキュリティ変更を curl と jq が導入された Linux/macOS 環境で実施するためのシェルスクリプトを独自に作って用意しました。以下の内容を set_watson_embed_security.sh として保存し、実行権限を付けてください:

#!/usr/bin/env bash
# set_watson_embed_security.sh
# IBM Cloud IAM トークンを取得し、Watson Orchestrate Embed Secure Config を更新する。
# Usage:
#   bash set_watson_embed_security.sh --api-key "(API キー)" --instance-id "(インスタンスID)" [--insecure]

set -euo pipefail

IAM_URL="https://iam.cloud.ibm.com/identity/token"
WATSON_BASE_URL="https://api.jp-tok.watson-orchestrate.cloud.ibm.com"
INSECURE_FLAG=""
API_KEY=""

INSTANCE_ID=""

log() { printf '[INFO] %s\n' "$*" >&2; }
err() { printf '[ERROR] %s\n' "$*" >&2; }

# 引数パース
while (( $# )); do
  case "$1" in
    --api-key)
      API_KEY="${2:-}"; shift 2 ;;
    --instance-id)
      INSTANCE_ID="${2:-}"; shift 2 ;;
    --insecure)
      INSECURE_FLAG="--insecure"; shift ;;
    -*)
      err "不明なオプション: $1"; exit 1 ;;
    *)
      err "不明な引数: $1"; exit 1 ;;
  esac
done

# 必須チェック
[[ -n "${API_KEY}" ]] || { err "API KEY が指定されていません。--api-key を指定してください。"; exit 1; }
[[ -n "${INSTANCE_ID}" ]] || { err "INSTANCE ID が指定されていません。--instance-id を指定してください。"; exit 1; }

# jq があるか確認(任意)
JQ_AVAILABLE=0
if command -v jq >/dev/null 2>&1; then
  JQ_AVAILABLE=1
fi

log "IBM Cloud IAM アクセストークンを取得中..."


# IAM トークン取得
# 参考のコマンド例:
# curl --fail -sS --insecure --request POST \
#   --url "https://iam.cloud.ibm.com/identity/token" \
#   --header "Content-Type: application/x-www-form-urlencoded" \
#   --data "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=(API KEY)"

IAM_RESP="$(curl --fail -sS ${INSECURE_FLAG} \
  --request POST \
  --url "${IAM_URL}" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=${API_KEY}" \
  || { err 'IAM トークン取得に失敗しました。API KEY が正しいか、ネットワーク/プロキシ/証明書を確認してください。'; exit 1; })"

# アクセストークン抽出
ACCESS_TOKEN=""
if [[ "${JQ_AVAILABLE}" -eq 1 ]]; then
  ACCESS_TOKEN="$(printf '%s' "${IAM_RESP}" | jq -r '.access_token // empty')"
else
  # jq が無い場合の簡易抽出(最低限の JSON 形式想定)
  ACCESS_TOKEN="$(printf '%s' "${IAM_RESP}" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')"
fi


if [[ -z "${ACCESS_TOKEN}" ]]; then
  err "アクセストークン抽出に失敗しました。応答: ${IAM_RESP}"
  exit 1
fi

log "アクセストークンの取得に成功しました。"

# Watson Orchestrate Embed Secure Config 更新
CONFIG_URL="${WATSON_BASE_URL}/instances/${INSTANCE_ID}/v1/embed/secure/config"
BODY='{"is_security_enabled": false}'

log "Watson Orchestrate の Embed Secure Config を更新します: is_security_enabled=false"
# ステータスコードを取得して判定

HTTP_CODE="$(curl -sS ${INSECURE_FLAG} \
  -X POST "${CONFIG_URL}" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -d "${BODY}" \
  -w '%{http_code}' -o /tmp/watson_embed_resp.$$ || true)"

if [[ "${HTTP_CODE}" =~ ^2[0-9][0-9]$ ]]; then
  log "設定更新に成功しました。(HTTP ${HTTP_CODE})"
  printf '\n=== API 応答 ===\n'
  cat /tmp/watson_embed_resp.$$

  printf '\n===============\n'
  rm -f /tmp/watson_embed_resp.$$
else
  err "設定更新に失敗しました。(HTTP ${HTTP_CODE})応答ボディ:"
  printf '\n=== API 応答 ===\n'
  cat /tmp/watson_embed_resp.$$
  printf '\n===============\n'
  rm -f /tmp/watson_embed_resp.$$
  exit 1
fi

log "一連の処理が完了しました。"
exit 0

実行時は以下のように API キー(※1)と watsonx Orchestrate のインスタンス ID (※2)を指定します:
$ ./set_watson_embed_security.sh --api-key "(API キー)" --instance-id "(インスタンスID)"

※1※2いずれも IBM Cloud で watsonx Orchestrate インスタンスを選択した画面の Manage メニュー内 "Credentials" 部に記載されています。なおインスタンス ID は URL と書かれた文字列内の最後、"/instances/" に続くランダムに見える文字列部分がインスタンス ID です:
2025121901


これらの情報をコマンドラインで指定して実行すると、API キーからアクセストークンを取得して、セキュリティを無効化するための API が実行されます。

処理が成功すると「設定更新に成功しました。」「一連の処理が完了しました。」というメッセージが表示されます。また API 応答結果のJSON 文字が表示され、その最後に "is_security_enabled": false と表示されていればセキュリティの無効化に成功しています:
2025121902


現時点では、このセキュリティ無効化に成功できている状況下で watsonx Orchestrate の embedded agent が動きます:
2025121803


なお watsonx Orchestrate における「セキュリティの有効化/無効化」についてはこちらを参照ください:
https://developer.watson-orchestrate.ibm.com/manage/channels#enabling-security




先日紹介したこのエントリの続きです。今度はノーツデータベースの中身を取得する Web API をエージェントで作成してみます:
ノーツの Web エージェント


対象とするノーツデータベースは「ログ」にします:
2016120301


このログデータベースは Domino サーバー上に log.nsf として存在しています。個々のログ内容は Events フォームのドキュメント内の EventList フィールド内に配列の形で格納されています:
2016120303


というわけで、同データベースからログを取り出して、XML 化して出力する、という Web API(Web エージェント)を作ってみました。基本的な考え方や作り方は前回紹介したものと同様ですが、実際の処理内容だけを変えています:
  :
:
public class JavaAgent extends AgentBase{ public void NotesMain(){ try{ Session session = getSession(); AgentContext agentContext = session.getAgentContext(); // (Your code goes here) String xml = "<?xml version=\"1.0\"?>\n<eventlist>\n"; //. 直近1週間のログのみ対象とする Date dt0 = new Date(); long t = dt0.getTime(); t -= ( 7 * 24 * 60 * 60 * 1000 ); dt0.setTime( t ); //. この(エージェントのある)データベースと同じサーバー上の log.nsf を対象とする Database cdb = session.getCurrentDatabase(); String server = cdb.getServer(); Database logdb = session.getDatabase( server, "log.nsf" ); //. Events フォームの文書を取り出す DocumentCollection docs = logdb.search( "Form=\"Events\"" ); for( int i = 1; i <= docs.getCount(); i ++ ){ Document doc = docs.getNthDocument( i ); //. EventList フィールドの配列値を取り出す Vector eventlist = doc.getItemValue( "EventList" ); Enumeration event = eventlist.elements(); while( event.hasMoreElements() ){ String line = ( String )event.nextElement();      //. 日付とログ文字列に分離 int x = line.indexOf( " " ); if( x > 0 ){ String dt = line.substring( 0, x ); //. YYYY/MM/DD hh:mm:ss String msg = line.substring( x + 3 ); //. 対象日(7日以内)のログかどうかを確認 Date dt1 = new Date( dt ); if( dt1.after( dt0 ) ){ //. XML 用にサニタイズ msg = msg.replaceAll( "\n", "" ); msg = msg.replaceAll( "\r", "" ); msg = msg.replaceAll( "&", "&amp;" ); msg = msg.replaceAll( "<", "&lt;" ); msg = msg.replaceAll( ">", "&gt;" ); msg = msg.replaceAll( "\"", "&quot;" ); xml += ( "<event datetime=\"" + dt + "\">" + msg + "</event>\n" ); } } } } xml += "</eventlist>\n"; //. 画面に出力 PrintWriter pw = getAgentOutput(); pw.println( "Content-Type: text/xml" ); pw.println( "" ); pw.println( xml ); pw.close(); }catch( Exception e ){ e.printStackTrace(); } } }

↑基本的な考え方は前回紹介したものと同じです。出力内容は log.nsf の内容なので、このデータベースを取得して、対象(1週間以内)のログを探して1行ずつ XML を生成し、最後に text/xml として出力する、という内容を Java で記述しています。

※同様の Web エージェントを LotusScript で記述する場合、最後の出力部分は Print 文で代用してください。


このエージェントをブラウザから
  http://(domino サーバー名)/(DB名)/(エージェント名)?OpenAgent
という URL を指定して実行します
2016120302


Domino サーバーのログの内容が Web API で外部から取得できるようになりました。他のデータベースでも同様に応用できると思います。

IBM Bluemix をはじめ、多くの便利な REST API が公開されています。REST API は HTTP プロトコルベースなので、特定のプラットフォームやプログラミング言語環境に縛られることなく、HTTP クライアント機能を持っている多くのプラットフォームやプログラミング言語から利用できるようになっています。

その一方で、IBM ノーツを使っている技術者の立場として、こういった外部の REST API をノーツからどうやって使うか、という情報があまり公開されておらず、「ノーツからは使えないんじゃないか?」と思われてしまっているように感じています。実際、ハードル高いと思う。

1点補足すると、これは「ドミノサーバー」からの話ではなく「ノーツクライアント」からの話です。ドミノサーバーであれば、Java サーブレットも使えるし、もちろんそのサーブレットからノーツデータベースにアクセスすることもできるので、連携もあまり問題ないと思うのですが、問題は「ノーツクライアント上」から「外部の」 REST API を使う、というケースです。 特に「外部の」という部分がミソで、JavaScript の AJAX などから使おうとしてもクロスサイトスクリプティング制約があったりするので、結構難しい問題だと思っています。


で、今回紹介するのは、ノーツクライアントから外部の REST API を使う方法です。"REST API" といっても、要は
 ・特定の URL に対して GET や POST でデータを送信してリクエストすると、
 ・結果が XML や JSON で返ってくるもの
という緩い定義で考えるものにします。これをノーツクライアントから実行して結果を受け取る、というものです。

今回は REST API のサンプルとして、マンホールマップの公開 API を使います。具体的には以下の URL に(GET で)アクセスすると、マンホールマップサービスに投稿されたデータから、最新20件のデータを取り出して JSON フォーマットで返す、というごく一般的(?)なものです:
http://manholemap.juge.me/searchcreated?limit=20&format=json

試しにウェブブラウザでこの URL にアクセスすると、取得結果の JSON がそのまま表示されて、こんな感じの結果になります。ちなみにこの JSON の中身の意味は・・・興味ある人はコメントなどで連絡ください、個人的に教えます(苦笑):
2015033001


この REST API にノーツクライアントからアクセスして JSON データを取得し、中身を解析してノーツで活用しよう、というのが今回の目的です。

まず最初に、これを実現するには「REST API を実行して、結果を取得する」という、ウェブ API ではごく当たり前のことを行う必要があるのですが、ノーツクライアントからこれを実行するには Java エージェントを使う必要があります。LotusScript や式関数などのマクロではノーツに関するカスタマイズ処理を記述することはできますが、TCP/IP 通信や HTTP プロトコルのレベルで処理をカスタマイズする方法は用意されていません。LSX(LotusScript eXtension) や WinSock の DLL を使って TCP/IP のソケット通信を行って・・・という手段がないわけではないと思いますが、そこまで自前で用意した上で更に HTTP プロトコルを実装、となるとさすがに非現実的です。その点、Java を使えば TCP/IP 通信は標準で持っているし、外部の便利なライブラリを使って比較的簡単に HTTP 通信を実現することもできます(更に言うと今回の例では JSON の解析も行う必要がありますが、それもライブラリに任せることができます)。 という背景もあって、今回は Java エージェントを使って REST API を実行します。

実際に Java エージェントを作る前の準備として、HTTP 通信用の Java ライブラリを用意しておきます。選択肢としてはいくつかあると思いますが、サンプルの多さなどから自分が個人的に使っているのが Jakarta Commons(Apache Commons)HTTP Client 3.1 です。この本体と、更に依存関係で必要な Logging ライブラリ、そして Codec ライブラリを全てダウンロードします。 更に実行結果の JSON テキストを取得した後の解析用に JSON ライブラリが必要になるので、これも JSON simple ライブラリをダウンロードしておきます。これらは同様の機能を持っているものであれば別のライブラリを使ってもいいのですが、以下のサンプルではこれらを使っている前提で紹介します。

以上をまとめて、以下の表のダウンロードサイトからライブラリ(のバイナリ)をダウンロードして、ダウンロードしたモジュールを展開するなどして表内右端列の4つの JAR ファイルをあらかじめ取得しておいてください。図のように同じフォルダに格納しておくと後で便利です(いずれも 2015年3月30日時点の最新バージョンです):
2015033002

ライブラリダウンロードサイト必要なファイル
HTTP Client 3.xCommons HttpClient Archivescommons-httpclient-3.1.jar
LoggingDownload Apache Commons Loggingcommons-logging-1.2.jar
CodecDownload Apache Commons Codeccommons-codec-1.10.jar
JSON simpleDownloads - JSON simplejson-simple-1.1.1.jar


では改めてノーツ上で Java エージェントを作ります。目的のデータベース(なければ新規に作って)をドミノデザイナーで開き、コード - エージェントをダブルクリックして選択後に「新規エージェント」ボタンをクリックして、エージェントを追加します:
2015033000


新規エージェント作成画面でエージェント名(図では "REST API Sample")を入力し、エージェントタイプに "Java" を選択して OK ボタンをクリックし、Java エージェントを作成します:
2015033003


新規に Java エージェントが追加されました。この中身を記述する前に、先ほど用意した4つのライブラリファイルを、このエージェント内にインポートして使えるようにしておきます。エージェント画面内の インポート - アーカイブ を選択します:
2015033004


ソースディレクトリに JAR ファイルが格納されているフォルダを指定し、インポートしたい JAR ファイルをまとめて指定して「終了」ボタンをクリックします。JAR ファイルが別々のフォルダに格納されている場合は、この手順を繰り返して全ての JAR ファイルをインポートします:
2015033005


Java エージェントのアーカイブに指定した JAR ファイルが追加されていることを確認してください。これで Java エージェント内でこれらのライブラリを使った処理を記述することができるようになりました。改めて javaAgent.java をダブルクリックして、エージェント処理の実装にとりかかります:
2015033006


ドミノデザイナー内でコードエディタが開き、javaAgent.java の編集状態になります。ここで記述された内容がこのエージェントの実行内容になります。コメントで "(Your code goes here)" と書かれた箇所の後に(というか、その前の部分を触らないように)実行内容を Java で記述します:
2015033007


今回の場合は、目的の REST API を実行して、その結果を取得する、という実装を行います。取得結果を最終的にどう扱うか(例えばノーツのデータとして格納するのか?別のデータベースに格納するのか?単に表示するだけか?など)は別に検討する必要があると思いますが、今回は結果を Java コンソールに出力する、という内容にします。なので、実装コードはこんな感じです。ここはもうノーツの世界ではなく、普通に Jakarta HTTP Client を使っているだけです:
  :
  :
  // (Your code goes here)
  HttpClient client = new HttpClient();
  GetMethod get = new GetMethod( "http://manholemap.juge.me/searchcreated?limit=20&format=json" );
  int sc = client.executeMethod( get );
  if( sc == 200 ){
    String json = get.getResponseBodyAsString();
    JSONParser parser = new JSONParser();
    JSONArray objs = ( JSONArray )parser.parse( json );
    int n = objs.size();
    for( int i = 0; i < n; i ++ ){
      JSONObject obj = ( JSONObject )objs.get( i );
      Long id = ( Long )obj.get( "id" );
      String username = ( String )obj.get( "username" );
      String text = ( String )obj.get( "text" );

      //. とりあえず整形してコンソールに出力
      String line = i + ": [" + id  + "] " + text + "(" + username + ")";
      System.out.println( line );
    }
  }
  :
  :

これで javaAgent.java およびこの(今回の例だと "REST API Sample")エージェントを保存します。


では試しにこのエージェントをノーツクライアント上で実行してみます。ノーツでこのエージェントを作ったデータベースを開きます。今回の例では最終結果を Java コンソールに出力しているので、その確認のためメニューから ツール - Java デバッグコンソールの表示 を選択します:
2015033001


こんな感じの Java コンソールウィンドウが開きます。実行結果はこの中に表示されるので、ウィンドウを閉じずにこのまま続けます:
2015033002


改めてノーツクライアントの画面に戻り、メニューから アクション - (作ったエージェント名(今回の例だと "REST API Sample")) を選択して、上記で作成した Java エージェントを実行します。実行方法はこれ以外にトリガを用意しても構いませんが、動作確認目的であれば、この方法が簡単でオススメです:
2015033003


作成した Java コードが正しく実行されると、REST API が呼び出され、結果の JSON テキストがパース&解析されて、取得したデータが1行ずつ Java コンソールに表示されるはずです:
2015033004


この例だと取得結果を Java コンソールに表示するだけでしたが、この結果を DB に格納するなり、これを元に HTML を作ってブラウザで表示する(参考記事)なり、色々な応用があると思います。ともあれノーツクライアントからであっても一般的な REST API を実行することができました。

後はこれを IBM Bluemix 上の各種 Watson API など、様々な外部 REST API を使う形で応用できると思っています。ノーツのデータを元に人工知能 API を使った解析を行うなど、いろんな使い方の可能性が広がると思っています。

 

このページのトップヘ