
ServiceNow AI Agentで作る類似インシデント自動クローズ― 分析はData Lakeに任せる②
はじめに
前回の記事では、ServiceNowが「分析基盤」を持てない構造的な理由を解説した上で、Databricks側の類似インシデント検索エンジンの実装まで完成しました。
今回はその続きとして、ServiceNow AI Agentが実際に動くところまで実装してゆきます。
ただし、前回の検証で1つの壁にぶつかっていました。
Databricks Free EditionのServerless環境では、毎回コンテナがリセットされる。類似性を比較するライブラリsentence-transformersのインストールに約45秒かかる一方、ServiceNowのScript Stepのタイムアウトは約20秒。構造的に接続が成立しません。
この問題を今回はGoogle Apps Script(GAS)をエミュレータとして使うことで乗り切ります。
GASが返すレスポンスとDatabricksが返すレスポンスは、完全に同じJSON形式になります。
つまり今回作るAI Agentは、エンドポイントのURLなどを書き換えるだけで、本番のDatabricks環境にそのまま繋がります。
Part 1:インシデント類似性の分析から自動仕分けまでの連携システムの構成
今回の実装で完成するのは以下アーキテクチャー図の通りです。

前回は、"データレイクのDatabricks上で類似性の比較分析を実行させ、インシデントに該当する解決策や類似性スコアを生成" するところまでは到達しました。

したがって、今回は
・”データレイクからの成果物(回答)を受領した後のオーケストレーション”
を作成してゆけば、AIエージェント全体機能として繋がるようになるわけです。

今回作成するコンポーネント群は以下となります。

では、さっそく作成をしてゆきます!
Part 2: Google Apps Script側の実装
2-1. プロジェクト作成
script.google.com にアクセスし、「新しいプロジェクト」を作成します。
プロジェクト名を SimilarIncidentEmulator に変更します。
デフォルトの初期ステップ・コードは削除します。

2-2. doGet()の実装
いろいろと、GASへの接続検証をしていた際に、
・GASはdoPostを使うと、302リダイレクトで/macro/echoへ強制リダイレクトを発生させる仕様
ということが分かりました。。💦
これではServiceNowの要求設定でノーコードでの対応ではなく、全ローコードステップ(JavaScript)の実装対応が必要になるので、GETで受けるようにしました。
※エミュレータの機能への接続用途だけなので、複雑な実装構成は避けてます。
以下をのコードを貼り付けてください。
javascript
function doGet(e) {
try {
const query = e.parameter.query || "";
const topK = parseInt(e.parameter.top_k) || 3;
let score = 0.45;
let incNumber = "INC010";
let shortDesc = "該当なし";
let resolution = "類似インシデントが見つかりませんでした。";
if (query.includes("メール") || query.includes("送受信")) {
score = 0.8828; incNumber = "INC001";
shortDesc = "メールサーバーが応答しない";
resolution = "SMTPサーバー再起動で解消。ポート25の疎通確認が先決。";
} else if (query.includes("ログイン") || query.includes("認証")) {
score = 0.8620; incNumber = "INC007";
shortDesc = "Active Directoryへのログインが失敗";
resolution = "アカウントロックアウト。管理者がロック解除で解消。";
} else if (query.includes("ネットワーク") || query.includes("VPN")) {
score = 0.6924; incNumber = "INC003";
shortDesc = "VPN接続が切断される";
resolution = "認証証明書の期限切れ。証明書更新で解消。";
}
const result = {
query: query,
matched: score >= 0.8,
best_score: score,
results: [{
number: incNumber,
short_desc: shortDesc,
resolution: resolution,
score: score
}]
};
return ContentService
.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService
.createTextOutput(JSON.stringify({ error: err.message }))
.setMimeType(ContentService.MimeType.JSON);
}
}1)メール・ログイン・ネットワーク系の3パターンでスコアを変化させています。
2)スコア0.8以上で matched: true となり、ServiceNow側のフロー分岐に使えます。
3)エラーハンドリングも入れているので、デバッグが楽に。

2-3. Web Appとしてデプロイ
右上「デプロイ」→「新しいデプロイ」をクリックします。
設定は以下の通りです:

[OK] ボタンをクリックすると、以下形式のURLが払い出されます:
https://script.google.com/macros/s/xxxxxxxxxx/execこのURLをコピーしておきます。ServiceNowから叩くエンドポイントになります。

2-4. 動作確認
デプロイ前にまず 「デプロイをテスト」 で簡易確認できます。
本確認はServiceNow側から叩く前に、以下のcurlコマンドまたはPythonのrequetsを使って、REST電文を送り応答が返るかの単体テストをしておきます。
bash
curl "https://script.google.com/macros/s/XXXXXX/exec?query=メールが送受信できない&top_k=3"XXXXには実際のアプリのURLパスを記載します。
python : test_gas.pyファイルとして保存し実行。
import requests
url = "https://script.google.com/macrosXXXXXX/s/X/exec"
params = {"query": "メールが送受信できない", "top_k": 3}
response = requests.get(url, params=params)
print(response.json())期待するレスポンス:
json
{
"query": "メールが送受信できない",
"matched": true,
"best_score": 0.8828,
"results": [{
"number": "INC001",
"short_desc": "メールサーバーが応答しない",
"resolution": "SMTPサーバー再起動で解消。ポート25の疎通確認が先決。",
"score": 0.8828
}]
}以下のような応答が戻れば成功です。

クエリに、以下のようなパターンを取り込んで電文を投げてみます
・メールが送受信できない
・ログインできない
・ネットワークが遅い
import requests
url = "https://script.google.com/macros/s/XXXXXXX/exec"
queries = [
"メールが送受信できない",
"ログインできない",
"ネットワークが遅い"
]
for query in queries:
params = {"query": query, "top_k": 3}
response = requests.get(url, params=params)
result = response.json()
print(f"\nクエリ: {query}")
print(f" matched : {result['matched']}")
print(f" best_score: {result['best_score']}")
print(f" resolution: {result['results'][0]['resolution']}")以下のような応答が返ってきます。
クエリ: メールが送受信できない
matched : True
best_score: 0.8828
resolution: SMTPサーバー再起動で解消。ポート25の疎通確認が先決。
クエリ: ログインできない
matched : True
best_score: 0.862
resolution: アカウントロックアウト。管理者がロック解除で解消。
クエリ: ネットワークが遅い
matched : False
best_score: 0.6924
resolution: 認証証明書の期限切れ。証明書更新で解消。
これでエミュレータの実装は完成です。
Part 3: ServiceNow側連携機能の実装
ここでは、ServiceNow側の連携機能、Integration Hubのカスタムアクションを作成してゆきます。
作成手順
3-1.Workflow Studioよりアクションを定義
Workflow Studio画面より、[新規] → [アクション]を選択し、以下を定義します。
・アクション名:GAS - Find Similar Incidents
・アプリケーション:Dbricks Integration(既存スコープ)

3-2.入力変数の定義
外部からクエリ入力を受けて、電文送信できるように入力変数の器を作成しておきましょう。前回と同じ3つです
変数名型
・query_text:String
・threshold:Floating Point Number
・top_k:Integer

3-3.REST Stepの追加
Stepsタブより、 「+」を押下し、 RESTステップを挿入します。

以下を定義します。
・接続:インラインで接続を定義
・ベースURL:https://script.google.com/macros/s

続けて、リクエスト部分を定義します。
・リソースパス:/XXXXXX/exec
・メソッド:GET
・ヘッダー:Content-Type | application/json
クエリパラメータ
query : {入力変数値_query_text}
top_k : {入力変数値_top_k}
threshold : {入力変数値_threshold}1)query:入力変数値をデータピル値よりドラッグ&ドロップで指定
2)top_k:同様に入力変数値をデータピル値より指定
3)threshold:同様に入力変数値をデータピル値より指定

テスト走行させてみます。入力値を以下のように定義して実行します。
・query:メールが送受信できない
・thereshold:0.0
・top_k:3

以下のような応答が返るはずです。
クエリ: メールが送受信できない
matched : True
best_score: 0.8828
resolution: SMTPサーバー再起動で解消。ポート25の疎通確認が先決。
クエリ: ログインできない
matched : True
best_score: 0.862
resolution: アカウントロックアウト。管理者がロック解除で解消。
クエリ: ネットワークが遅い
matched : False
best_score: 0.6924
resolution: 認証証明書の期限切れ。証明書更新で解消。応答本文を確認します。matched:trueの結果が2件正しく取得できています。

3-4.Scriptステップの定義
受領本文から、値を抽出するステップをScriptで作成します。
JSON Parserステップの対応が不可だったので、スクリプトで対応します。
ステップの追加から、[スクリプト]を追加します。

応答されるJSONペイロード:
{
"query": "メールが送受信できない",
"matched": true,
"best_score": 0.8828,
"results": [
{
"number": "INC001",
"short_desc": "メールサーバーが応答しない",
"resolution": "SMTPサーバー再起動で解消。ポート25の疎通確認が先決。",
"score": 0.8828
}
]
}スクリプトステップを定義します。
・入力変数
- JsonResponse:REST Stepの応答本文を指定
・スクリプト:以下のコードを貼り付け
(function execute(inputs, outputs) {
var result = JSON.parse(inputs.JsonResponse);
outputs.matched = result.matched;
outputs.best_score = result.best_score;
outputs.resolution = result.results && result.results.length > 0
? result.results[0].resolution : "";
outputs.top_number = result.results && result.results.length > 0
? result.results[0].number : "";
})(inputs, outputs);
スクリプトで抽出した値を変数に引き継ぎます。
・出力値の定義:
①matched:True/False
②best_score:Floating Point Number
③resolution:String
④top_number:String

これで、JSON応答から値の抽出はできるようになりました。
3-5. 出力変数の定義
続いて、出力変数を作成し、外側のプログラムやフロー処理に値を連携できる構成を作っておきます。
出力ステップより、[新規]ボタンで以下を定義します。
・matched :True/False
・best_score:Floating Point Number
・resolution:String
・top_number:String

スクリプトステップの出力値との紐付けを定義します。
[編集モード]を停止すると以下のような画面に切り替わります。
・それぞれの値を前ステップの出力値と紐づけます。

テスト走行させ、以下のような応答が得られれば、成功です。
・best_score: 0.8828 ✅
・matched: True ✅
・resolution: SMTPサーバー再起動で解消。ポート25の疎通確認が先決。 ✅
・top_number: INC001 ✅

これで、カスタムアクションが完成したので、[公開]をしておきます。
Part 4: ServiceNow側フロー機能の実装
4-1. インシデント自動クローズのSubflow作成
インシデントを自動クローズするサブフローを作成します。
・Similar Incident - Auto Close
・スコープ:<既存アプリスコープ>

入力変数を定義します。
・number: String
・top_number:String
・resolution:String
・best_score:Floating Point Number

[レコードの検索] アクションを追加します。
・番号 = 入力変数のnumber

[レコードの更新] アクションを追加します。
・レコード:レコードの検索ステップのRecordピル
・作業メモ:
[AI Agent] 類似インシデント:入力変数のtop_numberピル
スコア:入力変数のbest_scoreピル
・状況:解決済み
・解決メモ:入力変数のresoltionピル
・解決コード:解決策が提供済み

これで、自動クローズSubflowが完成しました。公開します。
4-2. 類似なしの場合のエスカレーション用Subflow作成
インシデントを自動クローズするサブフローを作成します。
・Similar Incident - Escalation
・スコープ:<既存アプリスコープ>

入力変数を定義します。
・number:String
・short_description:String
・best_score:Floating Point Number

[レコードの検索] アクションを追加します。
・番号 = 入力変数のnumber

[レコードの更新] アクションを追加します。
・レコード:レコードの検索ステップのRecordピル
・作業メモ:
[AI Agent] 類似インシデント 類似インシデントが見つかりませんでした。手動対応が必要です。(best_score:入力変数のbest_scoreピル)
・状況:進行中

通知アクションも追加しておきます。

これで、自動対応ができない場合のエスカレーションSubflowが完成です。公開します。
4-3. AIエージェントの作成
ここから、AIエージェントの作成に入ります。
AIエージェントの定義は全てノーコードの設定で完成まで漕ぎつけられるので、簡単です。
AI Agent StudioのAIエージェントタブ画面より、[追加]を押下します。

AIエージェントの定義を行います。
・AI エージェント名:インシデントの類似性検索エージェント
・説明:新規インシデントの類似検索を行い、自動クローズまたはエスカレーションを判断するエージェント

AIエージェントのロール定義:
あなたはITSMのインシデント対応エージェントです。
新規インシデントの類似検索を行い、自動クローズまたはエスカレーションを判断します。ステップの設定
あなたはITSMのインシデント対応エージェントです。
新規インシデントが作成されたら以下の手順で対応してください。
1. GAS - Find Similar Incidentsツールを呼び出し、類似インシデントを検索する
2. best_scoreが0.8以上の場合:Auto Closeサブフローを呼び出す
3. best_scoreが0.8未満の場合:Escalationサブフローを呼び出す設定を以下のように定義し、保存します。

続いて、AIエージェントに利用させるツールを追加します。
[ツールを追加]を押下します。

・”類似性を分析” するカスタムアクションの追加
ツールを追加より、[フローアクション]を選択します。

フローアクションを定義します。
・アクション選択:GAS - Find Simlar Incidents
・名前:類似インシデント検索
・ツールの説明:
新規インシデントのテキストをもとに、過去インシデントから意味的に類似したものを検索し、スコアと解決手順を返す

・実行モード:自律
・出力返還形式:パラフレーズ
・進行中のメッセージ:類似インシデントを検索しています
・完了時メッセージ:類似インシデントの検索が完了しました

設定後に[追加]を押下します。
・自動クローズするサブフローの追加
サブフローを定義します。
・サブフロー選択:Simlar Incidents - Auto Close
・名前:インシデントの類似性分析結果から自動クローズ
・ツールの説明:
類似インシデントが見つかった場合(スコア0.8以上)に、インシデントを自動クローズし、解決手順を記録する

・実行モード:自律
・出力返還形式:パラフレーズ
・進行中のメッセージ:インシデントを自動クローズしています
・完了時メッセージ:インシデントの自動クローズが完了しました

・エスカレーションするサブフローの追加
サブフローを定義します。
・サブフロー選択:Similar Incident - Escalation
・名前:類似インシデントなし・エスカレーション
・ツールの説明:
類似インシデントが見つからなかった場合(スコア0.8未満)に、インシデントをエスカレーションし、手動対応が必要である旨を作業メモに記録する
・実行モード: 自律
・出力を表示: はい
・進行中のメッセージ:エスカレーション処理を実行しています
・完了メッセージ:エスカレーションが完了しました。

・ユーザーアクセス定義を行います。

このAIエージェントが発動するトリガーの定義を行います。


メッセージの定義を行います。

AIエージェントが完成です。
Part 5:動作確認
さて、ここから動作確認を行います。
インシデントレコードを作成します。
・簡単な説明:メールが送受信できない
・問い合わせユーザー:System Administrator

AI Agent Studioよりテスト走行します。
・タスク:
INC0010009のインシデントの類似検索を実行して、結果に応じて自動クローズまたはエスカレーションを行ってください。

走行させると、、
①Incidentレコードの取得
②Databricks(今回はエミュレータ)へ接続し、インシデント類似性の分析を要求。スコアや分析結果、解決策を応答で受領
③スコア値を元に、プロセスを "自動クローズ"へ自動で振り分け
④インシデントレコードへ、Databricksから受領した解決策を付与して、ステータスを”解決済”で自動更新

対象のインシデントレコードを確認します。
おおっ! 自動で ”解決済み”となっています。

データレイクから受領した解決策により、作業メモや解決メモが更新されています。
類似性が極めて高い(=0.88)分析からの提案回答対応が自動化されています。

如何でしょうか?
この機能の秀逸なところは、
・DataLakeの分析基盤を使って、類似事象の判定を、何万件といったボリュームレコードデータから抽出可能
・受領した結果を元に、インシデント業務の自動仕分け対応を実現
となっている点です。

これがまさに、ServiceNowとデータレイク分析基盤との融合によるAIエージェントの最大効果を発揮させるユースケースではないかと。
次回のブログも乞うご期待ください。でわ。