dai365のお部屋

酒と業務効率化をこよなく愛する50代オヤジ。北海道の一次産業系組織でDX推進をやりつつ、Power Platform × 生成AIの実戦記録を書いてます。Microsoft MVP(Business Applications)。最近はAIと組んでアプリを作る「バイブコーディング」にハマり中。好きなものは競馬、プロレス。嫌いなものは手書き。奇想天外ビリビリ☆Power Apps同好会やってます。https://biribiri.connpass.com/

手書きPDFを業務データ化。Copilot Studio新UI × Claude Opus 4.8で検算・確認・SharePoint登録まで

 

AI Builderなしで手書きPDFを業務データ化。Copilot Studio新UI × Claude Opus 4.8で検算・確認・SharePoint登録まで

手書き出荷帳票PDFを「読む→構造化→検算→人に確認→親子リスト登録」までEnd-to-Endで通した検証ログ

手書きと印字が混在した紙帳票の取り込みでは、これまで人手入力や専用OCR、AI Builderのような帳票処理基盤の作り込みを検討するのが一般的でした。私も当初はAI Builder前提で設計を始めていました。

今回確かめたのは、Copilot Studioの新エクスペリエンス(プレビュー)でモデルにClaude Opus 4.8を選んだとき、専用OCRなしで「読み取り→構造化→検算→不明点を人に確認→承認後にSharePoint親子リストへ登録」まで一本の線で通せるか、です。

結論:通りました。手書きPDFをOCRしたかったのではない。紙に閉じ込められた非構造情報を、AIが意味理解し、検算し、人に確認し、業務データへ変換できるか——これがAI Builderなしで成立しました。ただし途中、WorkflowとSharePointの「型の境界」で5連続エラーという学びの多い回り道があります。

1. 検証環境と構成

Copilot Studioの新エクスペリエンスは、Build画面右側にMicrosoft IQ / Skills / Tools / Knowledge / Connected agents / Memoryが並び、Modelドロップダウンで推論モデルを選べます。今回の検証環境ではこのドロップダウンに「Claude Opus 4.8」が表示され、選択できたため使用しました。執筆時点の公式モデル一覧に掲載されているのはClaude Sonnet 4.6やClaude Opus 4.6/4.7などで、モデルの提供状況は環境・地域・公開段階によって変わり得ます。公式ドキュメント上、新エージェント体験は「production-ready preview」、新Workflows体験は「public preview」で、Anthropicなど外部モデルの利用には管理者側の有効化設定(Power Platform管理センターでの外部モデル有効化と、Microsoft 365管理センターでの各プロバイダー接続許可の2段階)が前提です(文末リンク参照)。

新エクスペリエンスのBuild画面。右パネルのModelでClaude Opus 4.8を選択。

最終構成はこれだけです。帳票解析専用のAI Builderモデルは使っていません。なお本記事の「専用OCRなし」は、作り手がAI Builderの帳票モデルや専用OCRフローを構築していないという意味で、Copilot Studio内部のPDF前処理やモデル側の画像理解処理まで未使用だと主張するものではありません。

Copilot Studio Agent(Claude Opus 4.8)
  └─ Tool: RegisterShipmentDocument(Workflow)
      When an agent calls the flow → Parse JSON → DocumentId生成
      → ShipmentHeaders登録(親1件) → Loop → ShipmentDetails登録(子N件)
      → Respond to the agent

SharePoint: ShipmentHeaders(ヘッダー) / ShipmentDetails(明細) ※DocumentIdで親子紐づけ

2. 検証対象:手書きの出荷帳票PDF

読み込ませたのは、印字された項目名の枠に手書きで値が書き込まれた、固定レイアウトの出荷帳票PDF(検証用のダミーデータ)です。ヘッダー部にはコード・農協名・精算区分・年産・発送日・取引先・輸送情報・荷姿・合計数量・通信欄など約20項目、明細部には品種×サイズ(3L〜2S・混)の数量マトリクスが3行。日付は「4/4」のように年の記載がなく、一部の欄は未記入——という、実務でよくある「きれいではない」帳票です。実際に読み取られた主要な値はこうでした。

ヘッダー(抜粋)
 コード: 00306-001 / 農協名: JA北海 / 共・個選区分: 共 / 精算区分: 買取 / 年産: 5
 発送日: 4/4(年なし) / 到着月日: 4/8(年なし) / 取引先: 北海野菜
 品目: 種ばれいしょ / 輸送: トラック(北海トラック) / 荷姿: C/8(手書きで判読不確実)
 合計数量: 85 / 通信欄: ユーザー引取 / 販売区分・TEL・量目など: 未記入

明細(品種×サイズ別数量)
 メークイン: 2L=20, L=10, S=5(計35)
 とうや:  L=5, M=5(計10)
 男しゃく: L=30, M=10(計40) ※帳票の原文ママ。一般名称は「男爵」

ポイントは、この帳票が「年のない日付」「判読の怪しい手書き(C/8)」「正規名称と揺れる品種名(男しゃく)」「未記入欄」を含んでいることです。読めるかどうかだけでなく、読めない・確定できない情報をAIがどう扱うかを試すための素材になっています。

3. 素のOpus 4.8が、読んで検算まで始めた

設計前にまず、新規作成直後のほぼ素のエージェント(モデルだけ変更)にPDFを添付し、「読み取って構造化して。記載のない値は推測せずnull。合計を検算して」とだけ依頼しました。これが素直に驚いた点で、Preview上の応答から観測できた流れは「テキスト抽出 → 手書きが十分読めないと判断 → ページを画像化して視覚確認 → 行列構造を保って構造化」。さらに行合計(35/10/40)、列合計(2L20+L45+M15+S5=85)、帳票記載の合計85を突き合わせるところまで、この検証では自走しました。

この結果を受けて「PDF→専用OCR→AI Builder→JSON化」をやめ、読む・理解する・検算するはエージェント、ID生成・型変換・登録はWorkflowという責務分離に設計変更しました。

4. 設計:固定JSON契約とHuman confirmation

SharePointはヘッダーと明細の親子2リスト構成です(サンプルデータ入りCSVをExcelでテーブル化し、SharePointへエクスポートして作成)。列はテキストと数値の標準列だけで、特別な仕掛けはありません。

ShipmentHeaders(親・1帳票=1件)
 Title / DocumentId / SourceFileName / Code / JAName / SelectionType / SalesType /
 SettlementType / CropYear / SequenceNo / ShippingDate / ContractNo / CustomerCode /
 CustomerName / Address / Telephone / DriverName / InvoiceNo / TransportType / ArrivalDate /
 UserCode / UserName / ItemCategory / ItemCode / ItemName / CarrierName / VehicleNo /
 PackageType / Weight(数値) / TotalQuantity(数値) / CommunicationNote / ReviewStatus /
 RawJson(複数行テキスト) / RegisteredAt

ShipmentDetails(子・1明細=1件)
 Title / DocumentId / LineNo(数値) / Variety /
 Size3L・Size2L・SizeL・SizeM・SizeS・Size2S・SizeMixed・BoxCount(いずれも数値) /
 Confidence / ReviewStatus

エージェント側の作り込みは、実質Instructionsがすべてです。「役割と登録条件」「PDF読み取りルール」「固定データ構造」「検算と確認」の4パートで構成しました。実際に使った内容の骨子をそのまま載せるので、列名・項目名を自分の帳票に合わせて書き換えれば再現できるはずです。

Instructions(1)役割と登録条件

あなたは、手書き・印字された出荷帳票PDFを読み取り、内容を構造化し、
ユーザー確認後にSharePointへ登録する「出荷帳票登録エージェント」です。

## 役割
ユーザーからPDF帳票を受け取った場合、帳票内の印字文字、手書き文字、
表形式データを読み取り、固定された構造に整理してください。
読み取り後は内容を検算し、ユーザーへ確認を求めてください。
ユーザーが明示的に登録を承認した場合のみ、SharePoint登録ツールを使用してください。

Instructions(2)PDF読み取りルール(8か条)

1. PDF内のテキストを確認してください。
2. テキスト抽出だけでは手書き内容を十分に取得できない場合、
  PDFページを画像として視覚的に確認してください。
3. 表形式部分は、行と列の位置関係を保持して読み取ってください。
4. 必要に応じて判読が難しい領域を詳細に確認してください。
5. 帳票に記載されていない値を推測で追加しないでください。
6. 読み取れない値、未記入値、空欄は null としてください。
7. 原文と正規化値を勝手に混同しないでください。
8. 手書き文字を一般名称へ補正した場合は、ユーザー確認前に確定値として扱わないでください。

5〜8か条目が今回の肝です。「男しゃく」を勝手に「男爵」へ確定しない、「4/4」の年を勝手に補完しない、という挙動はここから来ています。

Instructions(3)固定データ構造(JSON契約)

読み取り結果は、内部的に必ず次のJSON構造で保持させます。キー名は完全一致。この形がそのままTool入力とParse JSONの検証(後述)に効いてきます。

{
  "header": {
    "sourceFileName": null, "code": null, "jaName": null, "selectionType": null,
    "salesType": null, "settlementType": null, "cropYear": null, "sequenceNo": null,
    "shippingDate": null, "contractNo": null, "customerCode": null, "customerName": null,
    "address": null, "telephone": null, "driverName": null, "invoiceNo": null,
    "transportType": null, "arrivalDate": null, "userCode": null, "userName": null,
    "itemCategory": null, "itemCode": null, "itemName": null, "carrierName": null,
    "vehicleNo": null, "packageType": null, "weight": null, "totalQuantity": null,
    "communicationNote": null
  },
  "details": [
    { "lineNo": 1, "variety": null, "size3L": null, "size2L": null, "sizeL": null,
      "sizeM": null, "sizeS": null, "size2S": null, "sizeMixed": null, "boxCount": null }
  ],
  "validation": {
    "detailRowTotal": null, "columnTotal": null, "declaredTotal": null, "isConsistent": null
  }
}

Instructions(4)検算と確認

読み取り後、次を検算してください。
1. 各明細行のサイズ別数量の合計と boxCount の一致
2. 全明細の boxCount 合計(detailRowTotal)
3. サイズ列ごとの数量総合計(columnTotal)
4. 帳票記載の合計数量(declaredTotal)との一致(isConsistent)

判読が難しい項目・不足している情報(年など)はユーザーへ提示し、
その時点では登録しないでください。

この状態でPDFを渡すと、検算結果(isConsistent: true)と確認事項——年の欠落、荷姿「C/8」の判読不確実——を提示したうえで、「この時点では登録は行いません」と自分で止まりました。Instructionsで意図したHuman confirmationが、この検証では素直に成立しています。

5. Workflow:入口で形を強制し、IDはAIに作らせない

登録側は新Workflows(public preview)。「When an agent calls the flow」トリガーを持つワークフローはエージェントにToolとして追加できます。入力はあえて shipmentJson(string)1本にして、エージェントが実際に何を渡したかを観測しやすくしました。受け取ったJSONはParse JSONで header / details / validation を必須化し、additionalProperties: false でキー揺れを入口で弾きます。

Instructions「この形で作れ」→ Tool Input Description「この形で渡せ」
→ Parse JSON(additionalProperties: false)「違う形なら通さない」

DocumentIdはAIに作らせず、Workflow側で生成して親子両方に書き込みます。ここまでは順調でした。

6. 型境界との戦い:5連続エラー

Workflow単体テストの最初の結果は、外側だけ見ると HttpError status: 502。しかしActivityタブで実行履歴を開くと、真因は全部別の場所にありました。

エラー1:nullがNumber列で空文字になる(Weight)

Activityの実行詳細。「item/Weight は Number/double が必要。実際の値は ""」。JSONのnullがコネクタ境界で空文字になっている。Run ID等はマスク済み。

エラー2:RawJsonが255文字制限に激突

生JSONは1,622文字。ところが保存先の RawJson 列が、CSVからの型推論で「1行テキスト」になっていました。SharePointの1行テキスト列は最大255文字です(公式の列タイプ解説に記載。複数行テキストなら63,999文字まで格納可能)。

「item/RawJson requires maximum length '255' but is of length '1622'」。

列を複数行テキストへ変更——で終わらないのがこの回の学びで、この検証では既存Workflowアクションが古い列スキーマを保持したままで同じエラーが再発し、アクションの再読み込み・再マッピングまでやって初めて通りました。SharePoint列の型変更は、参照しているWorkflowアクションのスキーマとセットで考える

エラー3〜5:nullable numberが全滅

子明細のサイズ別数量は、空欄が契約どおりnullで来ます。これをNumber列へ渡す方法を3つ試して、3つとも失敗しました。

試行1: if(empty(item()?['sizeL']), null, ...) → empty(10) が Integer 非対応で失敗
試行2: coalesce(item()?['size3L'], null) → コネクタ境界で "" になり失敗
試行3: json('null') を返す分岐 → これも最終的に "" になり失敗
何を試してもCreate item境界でitem/Size3Lに""が渡り、型変換エラー。エージェント→Parse JSON→Loopまではnullが正しく保持されていた。

観測できた事実を整理すると、エージェントのJSONでもParse JSONでもLoopでもnullはnullのまま保持され、SharePointの「項目の作成」のNumber列パラメータに渡る段階で空文字化して失敗する。AIの「わからない=null」と業務DBの「値がない」の間には、コネクタという第三の当事者がいるわけです。

7. 解決:nullの意味は層で分ける

最後は発想を変えました。数量欄の空欄は、業務的には「その規格の出荷が0箱」。ユーザー確認を経て未記入と確定した数量のnullなら、登録層で0に正規化していい。coalesce(item()?['size3L'], 0) を数量列すべてに適用したところ、単体テストが全ノードグリーンで通りました。ちなみにこの決定打は高度な技術ではなく、「いや、普通に0を入れていいのでは」という業務側の一言でした。技術を複雑化せず、業務の意味に戻った瞬間に解けた——今回の本質のひとつです。

AI構造化層

未記入・未確定 = null

推測で埋めない。判読不能な値は単なる空欄と同一視せず、ユーザー確認の対象として明示的に残す。

Workflow登録層

確認済みの数量空欄のみ 0 へ正規化

今回はユーザー確認後にのみToolが呼ばれる設計のため、登録時点で残る数量欄のnullは「未記入と確認済み」。それをNumber列へ渡す直前にcoalesceで0へ変換した。判読不能値を無条件に0へ潰す設計ではない。

単体テスト成功。左のActivity一覧に、そこへ至るまでの5連敗の履歴が残っている。

8. End-to-End:読む→検算→止まる→人が確認→登録

成功したWorkflowを RegisterShipmentDocument としてToolsへ追加。入力 shipmentJson の「How is this filled?」はAIを選択し、Opus 4.8が会話中の最新の確認済みJSONを生成して渡す形にします。入力のDescriptionにも形の契約を書きます。ここも再現の要点なので要旨を載せておきます。

shipmentJson の Description(要旨)
 ユーザー確認済みの出荷帳票データを表す有効なJSON文字列。
 必ず header / details / validation のトップレベル構造を持つこと。
 Markdownコードフェンス禁止。JSON前後の説明文禁止。
 キーは固定の英語キー。未記入値は null。数量は number または null。
Tool入力の設定。「How is this filled?」でAIを選択。説明には固定構造に加え「コードフェンス禁止」「未記入値はnull」まで指定。

通しテスト。PDFを添付して「この帳票を読み取ってください」とだけ依頼すると、検算結果と確認事項を提示し、またも「この時点では登録は行いません」と停止。Toolを接続した後でも、読解直後に勝手にToolを呼ばない——ここが今回いちばん確認したかった挙動です。

E2Eテスト。検算結果(isConsistent: true)と確認が必要な項目を提示し、「この時点では登録は行いません」と明言して停止。

なお、この年確認メッセージの中では「年産5=令和5年=2023年でしょうか?推測はいたしません」と、年産から年の候補を示唆する一幕もありました。値として確定していないので契約の範囲内ではあるものの、完璧にルールへ従った美談でもありません。厳密にやるなら「年産や契約番号など他項目から発送日・到着日の年を推定しない」までInstructionsに書く余地があります。

人間が「発送日と到着日は2026年。荷姿はC/8で正しい。この内容で登録して」と応えると、エージェントは日付を2026-04-04 / 2026-04-08へ更新してからToolを呼び、登録成功。DocumentId(DOC-417a39b6-...)と明細件数3件が返ってきました。

ユーザー確認をJSONへ反映後、RegisterShipmentDocumentを呼び出して登録成功。DocumentIdと明細3件が返る。

9. SharePointに親1件・子3件が入った

ShipmentHeadersに親レコード登録(最上段ID 9が今回のE2E分。DocumentId=DOC-417a39b6-...)。
ShipmentDetailsに子3件(ID 9〜11)が同じDocumentIdで登録。メークイン 2L=20/L=10/S=5、とうや L=5/M=5、男しゃく L=30/M=10。空欄セルには正規化どおり0が入っている。

PDFから読み取った帳票項目を、SharePoint登録のために人が再転記した箇所はゼロ。人が行ったのは、AIが確定できなかった年と判読項目の確認だけです。「読めなかった/書かれていない」という情報はエージェントの応答と確認プロセスに残り、確認を経た数量の空欄だけが業務データとして0で確定する——二層の使い分けがそのままデータに現れています。

10. 今回わかったこと

  • 専用OCRなしでここまで読めた:この検証では、Opus 4.8が手書き帳票の項目理解・表構造復元・算術検算・曖昧性検出までこなし、「読めないときは画像として読み直す」切り替えも観測できた
  • Human confirmationは設計できる:「承認前に登録しない」を契約化すると、Tool接続後も確認→承認→実行の順序が保たれた
  • 固定JSON契約が安定の要:Instructions/Tool入力説明/Parse JSONの三重で形を強制すると挙動が安定した
  • AIのnullと業務DBの型契約は別問題:今回のSharePoint「項目の作成」のNumber列経路では、nullが空文字として渡され型変換エラーになった。意味理解層はnull、登録層は0という層分離で解決
  • 実務の注意:SharePoint列の型変更後はWorkflowアクションの再読み込みが必要になり得る。またWorkflowsは本番実行時、アクションごとにCopilot Studioのキャパシティを消費する。公式ドキュメントでは、フローデザイナーやエージェントのテストチャットからのテスト実行は消費対象外とされている。運用に乗せるなら容量監視か従量課金を含めた設計が要る
最終的な役割分担——意味理解(読む・構造化・検算・人への確認)はAI。確定処理(JSON検証・ID生成・null→0正規化・親子登録)はWorkflow。永続化はSharePoint。AIに全部やらせないことが、AIを実務に入れる近道になる。

AIは帳票を「読めた」ではなく「検算して、わからない所を聞いてきた」。

そこまで来て初めて、紙は業務データになる。

やればわかるさ。

関連リンク・参考

注意書き
本記事は2026年7月8日・10日時点の検証結果です。Copilot Studioの新エージェント体験はproduction-ready preview、新Workflows体験はpublic previewであり、UI・仕様・モデルの提供状況は今後変わる可能性があります。「登録しない」「画像化して読む」等の挙動はこの検証環境で観測した結果であり、常に同じ動作を保証するものではありません。Claude等の外部モデルの利用には管理者による有効化設定が必要です。スクリーンショットのテナント名・接続情報・各種IDはマスク処理しています。帳票データはすべて検証用のダミーです。導入の際は公式ドキュメントと所属組織のルールを確認してください。

保存した事故報告をAIに読ませたら、Word報告書と「事故経緯」まで作れた

 

事故報告を"上書きしない"、その先へ ―― 保存したV1〜V4をAIに読ませたら、Word報告書と「事故経緯」まで作れた

前回のAppend-only Versioned Snapshotを土台に、Word報告書生成と、全履歴からの時系列説明(タイムライン)を実証

前回、Copilot Studioで「社内向け 事故報告受付・初動整理エージェント」を作り、事故報告を同一行UPDATEではなくバージョン付きの完全スナップショットとして追記し続ける(Append-only Versioned Snapshot)設計を実証しました。追加報告・訂正・バージョン競合・No-op判定・矛盾確認まで、継続報告の"貯める側"を通したところまでが前回の話です。

前回のあらすじ(詳細は前回記事へ)

事故報告は一度で完成しない。だから最新値を上書きするのではなく、報ごとにV1・V2・V3…と完全スナップショットを追記する。同じSharePoint行をUPDATEし続ける方式でも、SharePoint標準のバージョン履歴を有効にすれば変更履歴自体は残せる。ただし通常の最新行参照だけでは「最初は"けが人なし"と報告され、後から訂正された」という業務上の経緯を直接は扱いにくい。そこで標準のバージョン履歴に依存せず、各報告時点の状態をAIが取得・比較・説明しやすい独立した業務レコードとして残すために、完全Snapshotの追記方式を選びました。前回はこの設計思想と、追加・訂正・バージョン競合・矛盾確認の実装までを扱いました。→Copilot Studio新UIで事故報告エージェントを「第1報・第2報・第3報」方式へ進化させてみた

今回はその続きで、"貯めた履歴を使う側"に踏み込みます。確かめたかったのは2つ。①保存済みスナップショットからWordの事故報告書を作れるか。②V1〜V4の全履歴を読ませたとき、AIが最新状態だけでなく「第一報から現在までの変化」を語れるか。

結論:保存済みSnapshotだけを正本にしてWord報告書を生成でき(内部コード漏れは表示層の分離で解消)、さらに全Versionを読ませると、AIは「最初は"けが人なし"だった → 後から訂正された → いつ確認済みになったか」という変化の経緯まで時系列で説明できた。上書きせず履歴を残したことが、ここで効いた。

1. 今回のエージェントと、積み上がった履歴

前回からエージェントにはSkillとToolを足しています。今回新しく加えたのは、Word生成Tool・履歴取得Tool(GetAccidentSnapshotHistory)と、文書化・時系列整理の各Skillです。なお本記事でいうSkillは、検証時点のCopilot Studio編集体験上で作成・追加したSkillを指します。モデルは検証時点で選べた Claude Sonnet 4.6 を指定しています。

エージェントの構成画面。Instructionsに「Append-only Versioned Snapshotで管理する/既存SnapshotをUPDATE・DELETEしない」と明記。右ペインにSkill群・Tool群、上部にモデル(Claude Sonnet 4.6)。

今回のシナリオでは、同一CaseIdが次のように積み上がっています。V3の「訂正」は前回くわしく検証したポイントで、今回はそこにV4(病院受診の結果など)を追加しました。

V1 第一報  「けが人なし」/警察連絡・社内連絡は未確認
V2 追加報告 警察へ連絡完了(連絡日時を追加)
V3 訂正    「けが人なし」は誤り。同乗者1名が腕の痛みを訴えていた ← 前回の主眼
V4 追加報告 病院受診/検査結果は骨折なし/上司への社内連絡完了 ← 今回追加
永続化先のSharePointリスト「AccidentReportSnapshots」。同一CaseIdのV4行が VersionNo=4/PreviousVersion=3 として追記され、過去バージョンは上書きされずに残っている。※Actor列(氏名)はマスク処理済み。

2. V4追加と、保存後に読み直す

既存事故の更新では、まず GetLatestAccidentSnapshot で最新V3を取得して現在値として扱い、そこへ今回の追加情報を統合して更新後の完全な状態を作ります(差分だけを保存しません)。V4追加では、AIが「未確認→確認済み」への変化を表で提示し、人間の承認を挟んでから保存しました。

V4追加報告時の統合結果。「医療機関受診/検査結果/社内連絡(上司)」がいずれも"未確認"→"確認済み"へ更新され、変更種別は information_added(情報追加)と判定。保存前に「この内容でV4として登録してよろしいですか?」と確認している。

今回あらためて良かったのは、保存を「保存しました」で終わらせなかった点です。AppendAccidentSnapshot のレスポンスが snapshot_generated になったあと、もう一度 GetLatestAccidentSnapshot を実行し、V4が最新として取得できることを確認しました。書き込み後の読み取り確認(write → read-after-write)です。

保存レスポンス(snapshot_generated)を受けて最新Snapshotを再取得し、「V4が正常に保存されていることを確認しました」と応答。保存の成否をレスポンスだけで判断せず、実データを読み直している。

3. 保存済み状態からWord報告書を作る

ここからが今回の新しい話です。「この事故の報告書をWordで作って」に応えるため、Word Online (Business) コネクタの、Copilot Studio 向けに用意された「指定されたコンテンツで Microsoft Word 文書を作成する」Toolを足しました。設計上こだわったのは、報告書の正本データ源を最新の保存済みSnapshotだけにし、会話中の未保存情報は使わないこと。そしてWord生成は状態変更ではないので、V5は作らずVersionは据え置きにしました。

前提として、検証時のTool設定画面では説明欄に文字数の上限があり、また生成ファイルの本体を会話応答へそのまま載せる構成は採りませんでした。そこで、ファイル本体ではなく生成済みWord文書のURLを返す設計にしています。この「指定されたコンテンツで Microsoft Word 文書を作成する」アクションは、公式仕様でもCopilot Studioからの利用を前提に、戻り値としてWord文書のURLを返すものです(Word Online (Business) コネクタ リファレンス)。

最初のWordは生成できたものの、問題が出ました。confirmed_yes / vehicle_accident / open といった内部の正規化コードや、reporter.contact のようなJSONパスが、そのまま本文に出てしまったのです。

最初に生成したWord(3ページ)。「負傷あり(confirmed_yes)」「車両事故(vehicle_accident)」のように内部コードが露出し、未確認事項も「reporter.contact」「vehicleAccident.otherDamage」のまま。※氏名はマスク処理済み。

4. 内部コードを消す ―― 表示層を分離する

原因は、業務状態の内部正規値をそのまま人間向け文書へ流していたこと。そこで「内部の正規値」と「人間向け表示」を分け、変換を挟みました。

vehicle_accident → 車両事故  /  open → 対応中
confirmed_yes → 項目に応じて「あり」「済み」「確認済み」
confirmed_no  → 項目に応じて「なし」「未発生」「該当なし」
reporter.contact → 報告者の連絡先  /  incident.address → 事故発生場所の詳細住所

あわせて文書化専用のSkill accident-report-document-draft を追加し、「保存済み最新Snapshotを、人間が読む社内提出用報告書へ変換する」役割を切り出しました。再生成の結果、内部コードは消え、初回3ページだった文書は2ページに整理されました。

改善後のWord(2ページ)。「案件状態: 対応中」「事故種別: 車両事故」「負傷状況: あり」のように人間向け表記へ変換され、未確認事項も「報告者の連絡先」などの日本語になった。※氏名はマスク処理済み。

5. 「第一報から現在まで」をAIに語らせる(今回の主眼)

Word生成までできたので、いよいよ「第一報から現在まで何が変わったのか」をAIに説明させます。ここで前回のAppend-only設計が効いてきます。全Versionが残っているからこそ、変化を語れるはずだからです。

新しいTool GetAccidentSnapshotHistory を作りました。入力はCaseIdだけ。中身はエージェントフローで、SharePointの「複数の項目の取得」で同一CaseIdをVersionNo昇順に取り、見つかったかどうかで分岐 → Select で整形 → エージェントへ応答、という構成です。

GetAccidentSnapshotHistory のエージェントフロー。「When an agent calls the flow」→「複数の項目の取得」→ If/Else → Select / Respond to the agent。CaseIdで絞り、Version順に履歴を返す。
実行結果(Activity)。フロー全体がSucceededし、入力スキーマ(CaseIdを受けるtext)と出力(body)が確認できる。この案件では4件(V1〜V4)が返った。

6. 事故経緯を時系列で語らせる

最後に、履歴を人間向けに読み解くSkill accident-history-timeline を追加しました。役割は「同一CaseIdの複数Version履歴を時系列で整理し、第一報・追加報告・訂正・状態変更・現在状態を人間向けに説明する」こと。過去の誤報を現在の事実として扱わない、内部コードを出さない、といった確認事項も指示に書き込んでいます。

accident-history-timeline Skillの定義。「Version順序は昇順か」「訂正を明確に説明しているか」「過去の誤報を現在事実として扱っていないか」などをチェック項目として指示に含めている。

そして「CaseId ○○ の事故経緯を、第一報から現在まで時系列でまとめてください」と依頼した結果がこちらです。

事故経緯の出力。V2「警察連絡状況の未確認が解消された」、V3「初回報告の"けが人なし"が訂正された/救急搬送はなし/この時点では受診状況は未確認」、V4「病院受診・骨折なしを確認/上司連絡完了」を、それぞれ意味づけて時系列で説明している。

注目したいのは、AIが単なる差分の羅列をしていない点です。V2は「追加した」ではなく「未確認が解消された」と説明し、V3は状態遷移ではなく「初回報告の内容が誤っていたため訂正された」と意味づけして説明しています。さらに「救急搬送なし ≠ けが人なし」「骨折なし ≠ 負傷なし」を取り違えず、「V3時点ではまだ受診未確認」という時間軸まで保っていました。前回"貯めた"履歴が、ここで初めて"語られる"側に回ったわけです。

7. 見えてきたレイヤーと役割分担

Agent  → 依頼種別の判定・全体オーケストレーション
Skill  → 事故種別ごとの構造化 / 更新意味の解釈 / 文書表現 / 履歴の時系列化
Tool   → Snapshot保存 / 最新取得 / Version追記 / Word生成 / 全履歴取得
SharePoint → Append-only Snapshotの永続化
今回いちばん効いたのは、3つの動作を混ぜなかったこと。「事故状態の変更(→V+1を追記)」「Word報告書作成(→最新Snapshotを参照するだけ、Versionは据え置き)」「事故経緯整理(→全Version履歴を参照するだけ、Versionは据え置き)」。状態管理層と成果物生成層を分けたから、履歴が汚れず、AIの説明も安定しました。

ひとつ補足すると、本検証のAppend-onlyはエージェント/Tool/Flowの設計規約として実装したもので、SharePointストレージ自体に物理的な不変性を持たせたものではありません。人間による直接編集や別経路からの更新・削除まで防ぐには、本番適用で権限設計や更新経路の制限を別途検討する必要があります。

8. この設計が向く業務

前回から一貫して試したかったのは、事故報告チャットボットそのものではなく、事実が時間とともに変化する業務で、AIと履歴管理をどう組み合わせるかでした。貯めた履歴からWord報告書と経緯説明まで通ったことで、同じ発想は、苦情対応・品質事故・システム障害・インシデント管理・監査指摘・契約変更・問い合わせ履歴など「一度で情報が完成せず、後から追加・訂正・状態変更が起きる業務」に広く応用できそうです。

前回は履歴を"貯めた"。今回はその履歴を"使った"。

上書きせず残したから、AIは「現在」だけでなく「変化」を語り、報告書まで書けた。

やればわかるさ。

関連リンク・参考

注意書き
本記事は2026年7月7日時点の検証記録で、前回記事(2026-07-05)の続編です。Copilot Studioの新しいエージェント編集体験(Skill等)は、検証時点でプレビューとして案内されている機能を含み、UIや仕様は今後変わる可能性があります。掲載画像はテナント名・URL・環境ID・氏名などをマスク処理済みで、事故内容は検証用の架空データです。実運用へ適用する際は、必ず最新の公式ドキュメントと、所属組織の情報管理・記録保持ルールをご確認ください。