こんにちは。バナナデスクです。
問い合わせの返信文をAIで作れたら、次は受信から記録まで自動化したくなりますよね。そのとき気になるのが、同じ問い合わせが二度届いた場合です。同じ下書きが二つできるだけなのか、二通の返信まで送られるのかで、必要な設計が変わります。
AIによる業務自動化は、生成処理をつなぐだけでは完成しません。どこまで進んだかを残し、空欄や重複で止め、失敗した受付を特定して再開できるようにします。今回は架空講座の問い合わせを題材に、外部送信を含めない小さな制御フローを作って試しました。
AIに任せる判断と、決まった条件を分ける
自由文の問い合わせを「料金」「持ち物」へ分け、自然な返信の下書きを作る部分はAIを使う候補です。一方、本文が空欄か、受付IDが同じか、確認待ちかという判定は、決まったルールで扱えます。
たとえば「料金についての質問だろう」という分類ができても、料金の承認や申込受付を自動的に確定する必要はありません。AIが作る内容と、システムが進めてよい状態を分けると、何を確認してから次へ進めるかが明確になります。

今回作った検証例の範囲
今回の実行環境はPython 3.12.14、SQLite 3.53.1です。1つのプロセスの中で、受け取った問い合わせ、状態、下書きをメモリー内のデータベースへ保持しました。外部サービスやAIモデルのAPIは接続していません。
分類と下書きには、原稿制作AIが作った固定応答を使っています。外部処理の失敗と、承認を入力する操作も模擬です。これにより、文章生成の品質とは別に、どの条件でどこへ進むかを確認できます。実際の連携ツールとモデルを通した自動化の成功例ではありません。
受け付ける情報は、架空の受付IDと問い合わせ本文だけです。実在のメールアドレスや顧客情報を使わず、処理の中にもメール送信機能を入れていません。記録済みとは、ローカルの例で状態を更新したことを指します。
このフローの完了条件を、一文で決める
今回の完了は「同じ受付IDに対して、確認した下書きと承認入力をローカルに記録した」です。問い合わせへの返信や、講座の申し込み確定は完了条件に含めません。先にこの一文を決めると、下書きができた段階と、記録まで進めた段階を区別できます。
| 設計する項目 | 今回の記入内容 |
|---|---|
| 入力 | 受付IDと問い合わせ本文。前後の空白を除いた本文で比較する |
| 生成の代わりに使うもの | R001とR003に用意した固定の分類・下書き。新しい質問をAIへ送る機能はない |
| 確認前に止める場所 | 下書きを保存したREVIEW。ここからメール送信などへ進めない |
| 完了とする状態 | 模擬の承認入力を受けたRECORDED。外部サービスへの記録完了とは呼ばない |
| 保存の寿命 | メモリー内DBは実行中だけ。結果JSONはファイルへ残るが、再起動時に読み込んで再開する処理はない |
正常・空欄・重複・外部失敗の4入力を通す
| 入力 | 実行した処理 | 観測した状態 |
|---|---|---|
| R001:参加費と録画の質問 | 固定応答を読み、下書き保存 | REVIEW:確認待ち |
| R002:空白だけ | 空欄として生成前に停止 | INPUT_REQUIRED:追加入力が必要 |
| R001:同じ本文を再度入力 | 既存の受付IDと本文を照合 | DUPLICATE:下書きを追加しない |
| R003:持ち物の質問+模擬失敗 | 固定のエラー条件で停止 | FAILED:再実行待ち |
正常なR001の下書きは、「参加費は税込9,800円、録画配布なし」という架空の条件を説明します。これを作っただけでは完了にせず、確認待ちに止めました。R002の空欄から、AIが質問内容を補って返信を作る動きにはしていません。
R001が同じ本文で再び届いても、既存の状態を参照して終わります。この例では、問い合わせ本文が同じかどうかだけでなく、受付IDも使っています。別の人が偶然同じ質問をした場合まで、同じ受付と扱わないためです。
失敗したR003だけを、同じ受付IDで再開する
R003は、再実行を明示したうえで模擬エラーを外すと、同じ受付IDのまま確認待ちへ進みました。別のR004を作ってやり直す方法にはしていないので、どの問い合わせを再開したかが分かります。
追加で、R001のIDを使いながら本文を「別の日程へ変更してください」に変えるケースも試しました。この場合はID_CONFLICTとして停止します。同じ番号だからといって、別内容へ置き換えてよいわけではありません。
Amazon Builders’ Libraryの解説も、再試行で処理が重複しないよう要求を識別すること、同じ識別子で内容が違う場合を区別することを説明しています。今回のローカル例はその考え方を小さく示したもので、AWSの仕組みを実装したものではありません。
実際の外部通信では、タイムアウトしても相手側では処理が終わっている場合があります。この教材の固定エラーは、そうした不確実な結果を再現していません。本番では、相手側の処理状況を確認できるか、同じIDで再試行してよい仕様かを先に調べます。失敗の表示だけを見て何度も書き込み直さないことが大切です。
承認を通す操作を、下書き生成から分ける
今回の例ではR001へ教材用の承認入力を与え、状態をRECORDEDへ進めました。これは人が確認した時間や判断を計測した結果ではなく、承認入力を模した操作です。R003は確認待ちのまま残しています。
| 最後の受付 | 最後の状態 | 今回行っていないこと |
|---|---|---|
| R001 | RECORDED:ローカル記録済み | 外部への返信送信、実際の顧客受付 |
| R003 | REVIEW:確認待ち | 承認入力、外部送信 |
4入力と再開、内容変更、承認の模擬操作で、履歴は7件、受付の記録は2件となりました。空欄や重複をそれぞれ新しい受付として増やしていないことを照合しています。ただしメモリー内の保存なので、プログラム終了後にも復旧できる記録ではありません。
配布教材を実行し、結果のどこを読むか
手元にPython 3の実行環境がある場合は、ZIPを作業用のフォルダへ展開し、N050-workflow-demo.pyのあるフォルダで次のコマンドを実行します。追加ライブラリやAPIキーは使いません。環境がない場合も、同梱した結果JSONと下の読み方で、停止・再開の関係を確認できます。
python N050-workflow-demo.py
Pythonのコマンド名がpython3の環境では、その名前に読み替えます。コマンドが見つからない場合はフローの失敗ではなく、Pythonの起動方法を確認する段階です。ファイルが見つからない場合は、ZIPを展開したか、スクリプトのあるフォルダで実行しているかを確かめてください。
配布したコードの入力を変えずに実行したときの、標準出力は次の形です。
{"events": 7, "records": 2, "states": [["R001", "RECORDED"], ["R003", "REVIEW"]]}
同じフォルダ内のN050/control-flow-results.jsonに詳細が保存されます。fixed-response-examples.jsonは使用した固定応答の控えで、編集して次回の入力を変える設定ファイルではありません。コードはR001・R003の決められた例を通す教材です。
| 読む場所 | 今回見える内容 | そこから判断すること |
|---|---|---|
| events | 7回の処理結果。3件目はR001のDUPLICATE | 重複入力をどう扱ったか。これは「新規の問い合わせが7件あった」という件数ではない |
| states_before_retry | R001はREVIEW、R003はFAILED | 再開前に止まっていたのがR003だと確認できる |
| final_records | R001はRECORDED、R003はREVIEWの2件 | 受付ごとの最後の状態。DUPLICATEをR001の最終状態へ上書きしていない |
| R002の扱い | eventsにINPUT_REQUIREDがあり、final_recordsにはない | 空欄を生成へ進めず、受付の保存件数を増やしていない |
実行が終わるとメモリー内DBは消えます。再びコマンドを実行すると同じ例を最初から作り、結果ファイルを上書きします。前の実行で止まった仕事を、JSONから復旧した結果ではありません。条件を変えて比べる場合は、元の結果を別名で残してから実行してください。
実業務へつなぐときに、追加で確認するもの
次は、実際に使う受信元、連携ツール、モデルと版、保存先を固定し、同じ4条件を通します。AIへ渡す業務資料が正しいか、返された分類が許可した種類に入るか、下書きに条件の創作がないかを確認します。問い合わせ本文にシステム操作の指示が書かれていても、問い合わせの内容として扱い、実行する指示へ昇格させません。
再開には、状態を残す永続的な保存先と、複数の処理が同時に届いても重複しない仕組みが必要です。今回の1プロセスの教材から、同時実行やサーバー障害時の復旧まで成功したとは判断できません。承認後の文面を変えた場合には、何を承認したのかも更新して追えるようにします。
FAILEDを見た後の判断を、接続先の状態で変える
この教材のR003は、固定応答を読む前に模擬エラーを起こしています。そのため、エラー条件を外して同じIDを再実行する流れを確認できました。実通信では同じFAILEDという表示でも、相手側の処理がどこまで進んだかで次の作業が変わります。
| 確認できた条件 | 次の判断 | 残す記録 |
|---|---|---|
| 今回のR003:固定応答を読む前に停止 | 同じID・本文で、FAILEDに対してだけ再開を許す | 元ID、元の本文、停止位置、再開後のREVIEW |
| 実通信:相手の応答がなく、処理済みか不明 | 自動で新IDを作らず、相手側の状態照会と再試行の仕様を確認する | 送った要求ID、送信内容、照会で分かった状態。不明なら不明のまま残す |
| 同じIDなのに本文が変わった | 再実行ではなく内容変更として止め、どちらを処理するか確認する | 以前の本文と新しい本文の差。元データを消して置き換えない |
中央の行は本番へ向けた設計上の確認事項です。この教材で実通信を試した結果ではありません。連携ツールの「再試行」ボタンを使う場合も、既存の受付IDを引き継ぐのか、別の処理として増えるのかを、その製品の仕様と検証用データで確かめます。
処理単価と、人の確認時間を測ってから判断する
費用はモデルの入力・出力、連携ツールの実行回数、保存や通知などの利用条件で変わります。今回の検証はAPI未接続なので、本番の処理単価は測定していません。外部APIを呼んでいないことを、業務の運用費がゼロという意味にはしません。
件数の分母も先に決めます。この教材なら、履歴は7件、保存した受付は2件、RECORDEDまで進んだ受付は1件です。7件を新規問い合わせ数、2件を返信完了数と見なすと、一件当たりの費用や時間を誤って計算してしまいます。
| 評価欄 | 今回の例へ記入した結果 |
|---|---|
| 完了の単位 | 確認後の下書きと承認入力をローカルへ記録した受付1件 |
| 完了した受付 | R001の1件。R003は確認待ちなので数えない |
| AI・連携サービスの費用 | 未測定。API未接続という条件だけでは、本番費用を出せない |
| 人の確認と復旧の時間 | 未測定。承認入力は模擬なので、人が確認した時間にはできない |
| 今判断できること | 固定応答の範囲では、空欄・重複・内容不一致を分け、失敗した受付だけを再開する流れが記録と一致 |
| まだ判断できないこと | 実業務の削減時間、運用費、外部障害からの復旧、返信品質 |
実際の試験では、同じ期間の再試行分も含めた処理費用を合計し、同じ完了条件を満たした受付件数で割ります。完了が0件なら一件当たりの費用は出せません。確認時間は下書きの読み始めから修正・採否決定まで、復旧時間は失敗の調査と再開に使った作業時間として別に残します。最初の構築時間も運用時間と分ければ、少量の業務でも導入する価値があるかを判断しやすくなります。
生成にかかった秒数だけで効率化を判断せず、確認と復旧まで含めて仕事が減ったかを見ます。まず同じ入力条件で一件を通し、どこで止まり、誰が何を確かめて再開するかを記録するところから始めてください。