この記事はProtoPedia Advent Calendar 2024 の 22日目の記事です。
作ろうと思ったきっかけ
自分はよくハッカソンに参加する。
イベントの種類によっては業務扱いで参加することもある。
社内発表会などで得た知見をフィードバックするが
「それ業務で使える?」「作ったらいくらで売れる?」
など、当然だが現実的な反応が返ってくる。
このままでは業務扱いが認められなくなる。
理解ある管理者を増やすため、業務で使えそうなシステムを作ることにした。
作ったもの
システム概要
システムのコア
- kintoneによるDB管理
- DifyによるAI分析
- makeによる各種処理の結合
付加価値
- Vonageによる電話通知
- Mackerelによる監視連携
動作例
緊急度が高い場合(電話あり)
エラーログ(Apacheの異常終了)
[Thu Apr 25 11:52:03.207133 2024] [core:error] [pid 31274] AH00046: child process 32436 still did not exit, sending a SIGKILL
緊急度
high
要約
このエラーログは、Apache HTTPサーバーのプロセスが正常に終了しないことを示しています。
子プロセスが指定された時間内に終了せず、強制終了信号(SIGKILL)が送信されているため、サーバーの安定性やパフォーマンスに悪影響を及ぼす可能性があります。
対処方法
この問題を解決するためには、まずApacheの設定を見直し、子プロセスの最大数やタイムアウト設定を調整します。
また、エラーログやアクセスログを確認して、特定のリクエストや処理が原因であるかを調査し、必要に応じてアプリケーションのコードを修正することが重要です。
最終的には、Apacheを再起動して変更を適用し、問題が解消されたか確認します。
緊急度が低い場合(電話なし)
エラーログ(Apacheの正常起動)
[Sun Aug 11 03:11:02.255000 2024] [mpm_prefork:notice] [pid 951] AH00163: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/8.2.9 configured -- resuming normal operations
緊急度
low
要約
このエラーログは、Apacheサーバーが正常に動作を再開したことを示しています。特に、mpm_preforkモジュールの通知メッセージであり、エラーではなく通常の運用状態に戻ったことを示しています。
対処方法
特に復旧作業は必要ありませんが、サーバーの運用状況を監視し、異常が発生した際には適宜対応してください。
実装詳細
kintoneレコード登録されたエラーログをAI分析
まずはデータを格納する箱を作るため、kintoneで適当なアプリを作成する
makeで新しいシナリオを作成してwebhookの受け口を用意
Difyでログ分析用のワークフローとプロンプトを用意
json形式での応答を指示することで実装を時短
makeでつなぐ
エラーログにはダブルクォートや改行も含まれる
これはDifyへのリクエストjson作成に都合が悪いので変換・除去する
これでコア機能が完成
緊急度が高ければVonageで電話をかける
JWT作成用のGASを準備
// JWTを生成して返すGoogle Apps Script関数
function doGet(e) {
// アルゴリズム、アプリケーションID、秘密鍵を指定
const privateKey = `-----BEGIN PRIVATE KEY-----
your private key
-----END PRIVATE KEY-----`;
const header = {
alg: "RS256",
typ: "JWT"
};
const jti = generateUUID();
// JWTのペイロード
const payload = {
iat: Math.floor(Date.now() / 1000), // 現在の発行時間
jti: jti, // 一意識別子(UUIDなどを生成可能)
sub: "alice", // サブジェクト(通常はユーザーIDなど)
exp: Math.floor(Date.now() / 1000) + (60 * 60), // 有効期限(ここでは1時間後に設定)
acl: {
paths: {
"/*/rtc/**": {},
"/*/users/**": {},
"/*/conversations/**": {},
"/*/sessions/**": {},
"/*/devices/**": {},
"/*/image/**": {},
"/*/media/**": {},
"/*/knocking/**": {},
"/*/legs/**": {},
"/v1/calls/**": {} //これが重要
}
},
application_id: "vonage app id(uuid)" // VonageアプリケーションID
};
// Base64URLエンコード
const base64UrlEncode = (obj) => {
return Utilities.base64EncodeWebSafe(JSON.stringify(obj)).replace(/=+$/, '');
};
const headerEncoded = base64UrlEncode(header);
const payloadEncoded = base64UrlEncode(payload);
const signatureInput = `${headerEncoded}.${payloadEncoded}`;
// RS256で署名を生成
const signature = Utilities.computeRsaSha256Signature(signatureInput, privateKey);
const signatureEncoded = Utilities.base64EncodeWebSafe(signature).replace(/=+$/, '');
// 完成したJWT
const jwt = `${headerEncoded}.${payloadEncoded}.${signatureEncoded}`;
// JWTをレスポンスとして返す
return ContentService.createTextOutput(jwt).setMimeType(ContentService.MimeType.TEXT);
}
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
JWTを使ってVonageAPIをコール
エラー要約の読み上げとアクション要求
ここでは簡易化のため発信先をハードコードしている
実運用ではkintoneアプリに連絡先を登録しmakeで取得すればよい
(make内の日付変数などを活用してローテーション可能)
{
"to":[
{"type":"phone","number":"81XXXXXXXXXX"}
],
"from":{
"type":"phone","number":"Vonageで購入した電話番号"
},
"ncco": [
{
"action":"talk",
"text":"{{18.explanation}}",
"language":"ja-JP",
"bargeIn": false
},
{
"action": "talk",
"text": "1を押すともう一度メッセージを再生します。2を押すと通話を終了し、対応開始を登録します。3を押すとマネージャーに電話をかけます。4を押す、または通話終了で次のエンジニアに電話をかけます。",
"language": "ja-JP",
"bargeIn": true
},
{
"action": "input",
"submitOnHash": true,
"eventUrl": ["振り分け用webhook
?record={{6.record.`レコード番号`.value}}"],
"dtfm": {
"timeOut": 10,
"maxDigits": 1
}
}
]}
電話口での番号入力に応じて処理を分岐するmakeシナリオを用意
上記のeventUrlに指定
(この辺りJWT含めてmakeでは処理しづらい)
Mackerel監視との連携
GASでwebhookの受け口を用意
kintoneにレコード登録
(makeでも良いが、無料は2シナリオなのでGASで代用)
function doGet(e) {
// 正常なレスポンスとして200 OKを返す
return ContentService.createTextOutput('OK').setMimeType(ContentService.MimeType.TEXT);
}
// Kintoneへのレコード登録
function postToKintone(mackerel) {
if (mackerel.alert.status == "ok") {
return;
}
// Kintoneの設定
var kintoneDomain = 'your kintone domain'; // Kintoneのサブドメイン
var appId = 'your app id'; // KintoneアプリのID
var apiToken = 'your api token'; // KintoneのAPIトークン
// APIエンドポイントのURL
var url = 'https://' + kintoneDomain + '.cybozu.com/k/v1/record.json';
// レコードデータを作成(フィールドコードを指定)
var payload = {
"app": appId,
"record": {
"error_log": {
"value": mackerel.alert.status + " " + mackerel.message
},
"hostname": {
"value": mackerel.host.name
},
"source": {
"value": "mackerel"
}
}
};
// リクエストヘッダー
var options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'X-Cybozu-API-Token': apiToken
},
'payload': JSON.stringify(payload)
};
// リクエスト送信
var response = UrlFetchApp.fetch(url, options);
Logger.log(response.getContentText());
}
// Mackerel Webhook受信処理
function doPost(e) {
try {
// Mackerelからのデータをパース
var json = JSON.parse(e.postData.contents);
// 受け取ったデータをKintoneに送信
postToKintone(json);
// 正常終了のレスポンス
return ContentService.createTextOutput('Success').setMimeType(ContentService.MimeType.TEXT);
} catch (error) {
Logger.log('Error: ' + error);
return ContentService.createTextOutput('Error: ' + error).setMimeType(ContentService.MimeType.TEXT);
}
}
適当なサーバにエージェントをインストール
監視ルールを設定
(動作確認ため閾値を低く設定)
kintoneにレコード登録されると同様に処理される
(監視エラーメッセージが簡素なので、AI分析は一般論になりがち)
作ってみた感想
社内報告会をしたところ
「いいじゃん」
「こういうのを待ってた」
とかなり好評だった
気を良くして実運用に向けた検討を始めた
ハードルになるのは以下の3点
- AI分析のトークン使用量の抑制
- 分析精度向上ため構成情報を用いた高度な推論(RAG)
- 機密性の高い情報の取り扱い
回答精度の高いローカルLLMが理想だ
しかしオンプレGPUサーバは高額。
無料商用利用可能なモデルも限られる。
そして現状のローカルLLMではGPT4o並の回答は期待できない。
また、エンジニアが本当に求めているのは自動復旧。
そのためには簡易な切り分けや復旧コマンドはAIに実施してほしい。
自律型AIエージェントの機能が必要だ。
誤った判断で被害を拡大させないため、コマンド権限の制限や監視が必要になる。
そんな展望を語っているといつの間にか「生成AIを用いた業務改善の推進者」のレッテルがつき、仕事が増えた。
のびのびハッカソンライフを満喫するために作ったプロダクトのせいで、ハッカソンに参加する時間が減りそうだ。