1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI エージェント間の JSON 受け渡しで情報欠落を検出する

1
Posted at

配送照会の結果を別の AI エージェントへ渡す場合、後続の返品判定には「配達済み」という結論に加えて、配送完了日が必要です。配送完了日が 2026-10-10、返品申請日が 2026-10-25 なら、経過日数は 15 日と計算できます。一方、「配達済み」という文字列だけでは、期間条件を評価する根拠が揃いません。この情報依存関係は、注文照会・配送照会・返品判定を分ける際の設計上の論点です[1]。

この記事では、後続処理が必要とする値を入力契約として固定し、検証に通った入力だけで期間判定を実行する方法を Python で確かめます。情報欠落と期間超過を区別し、後者に限って False を返します。

エージェント間で配送完了日が欠落すると返品期間を判定できない

ここでは、配送完了日から 30 日以内(0 日目と 30 日目を含む)に返品申請があれば、期間条件を満たすとします。これは検証用の架空の業務規則です。商品の状態、返品対象外の商品、承認などは別の条件として扱います。

配送照会が返す内容 後続処理で可能な判定
"配達済み" 配送状態は分かるが、経過日数の算出には配送完了日が必要
注文番号・配送状態・配送完了日 注文の一致を確かめ、経過日数を算出できる
注文番号・配送記録への参照 参照先から配送完了日を取得できる場合に、期間条件を判定できる

以下では 2 行目の構造化された受け渡しを実装します。配送記録への参照を渡す方式には、参照先の取得可能性や記録の版を管理する別の条件があります。

Python の実行環境と検証対象を固定する

使用するのは Python 3.11 以上と標準ライブラリです。json、datetime、re、unittest を使用します。外部 API、LLM、追加パッケージは使用しません。

python3 --version

同一ディレクトリに delivery_handoff.py と test_delivery_handoff.py を配置します。入力となる JSON 文字列はテスト側で生成するため、外部データファイルは不要です。

実行時の返品申請日は 2026-10-25 に固定します。現在日時に依存させず、境界値の検証を繰り返せるようにします。

配送照会の JSON に必要な項目を定義する

返品期間判定に必要な項目

正常な受け渡しデータは次の形式とします。

{
  "order_id": "ORD-001",
  "delivery_status": "delivered",
  "delivered_at": "2026-10-10"
}
項目 受理条件 不正な場合
order_id 空白だけではない文字列で、期待する注文番号 ORD-001 と一致 ValueError
delivery_status 文字列 delivered と一致 ValueError
delivered_at YYYY-MM-DD 形式の文字列で、実在する日付 ValueError

json.loads() は JSON の文字列を Python の値へ変換します。この処理が成功しても、結果がオブジェクトであることや必要な項目を含むことは別途確認する必要があります[2]。

日付形式と実在する日付を区別する

date.fromisoformat() は日付へ変換できますが、Python 3.11 では YYYYMMDD のような基本形式も受け付けます。今回の入力契約は YYYY-MM-DD に限定するため、正規表現で形式を確認してから日付へ変換します[3]。

例えば 2026-02-30 は指定形式に一致しても、実在しない日付なので変換時に拒否されます。

Python で入力検証と返品期間判定を実装する

delivery_handoff.py は JSON の解析、項目の検証、日付差分の計算を担当します。

delivery_handoff.py
import json
import re
from datetime import date


def validate_delivery(message: str, expected_order_id: str) -> date:
    try:
        delivery = json.loads(message)
    except json.JSONDecodeError as exc:
        raise ValueError("invalid JSON") from exc

    if not isinstance(delivery, dict):
        raise ValueError("expected JSON object")

    for field in ("order_id", "delivery_status", "delivered_at"):
        if field not in delivery:
            raise ValueError(f"missing {field}")

    order_id = delivery["order_id"]
    if not isinstance(order_id, str) or not order_id.strip():
        raise ValueError("invalid order_id")
    if order_id != expected_order_id:
        raise ValueError("order_id mismatch")
    if delivery["delivery_status"] != "delivered":
        raise ValueError("delivery is not completed")

    delivered_at = delivery["delivered_at"]
    if not isinstance(delivered_at, str) or re.fullmatch(
        r"[0-9]{4}-[0-9]{2}-[0-9]{2}", delivered_at
    ) is None:
        raise ValueError("invalid delivered_at format")
    try:
        return date.fromisoformat(delivered_at)
    except ValueError as exc:
        raise ValueError("invalid delivered_at date") from exc


def within_return_window(
    message: str, expected_order_id: str, requested_at: date
) -> bool:
    delivered_at = validate_delivery(message, expected_order_id)
    days = (requested_at - delivered_at).days
    if days < 0:
        raise ValueError("delivery date is after request date")
    return days <= 30

validate_delivery() は、解析後の値が辞書であること、必要な 3 項目が存在すること、注文番号と配送状態が期待値に一致すること、配送完了日が指定形式の実在する日付であることを検証します。検証済みの配送完了日だけを返します。

within_return_window() は、配送完了日と固定の申請日から経過日数を求めます。受理条件は 0 <= days <= 30 です。配送完了日が申請日より未来なら、入力の不整合として ValueError を送出します。経過日数が 31 日以上なら、有効な入力に対する期間条件の判定結果として False を返します。

これにより、判定に必要な入力の不備と、入力を受理したうえでの期間条件不成立を区別できます。

unittest で正常系と情報欠落を検証する

Python 標準ライブラリの unittest で、正常系、境界値、不正入力を確認します。assertRaises() は指定した例外の発生を検証するために使用します[4]。

以下の test_delivery_handoff.py を同じディレクトリに配置します。

test_delivery_handoff.py
import json
import unittest
from datetime import date

from delivery_handoff import within_return_window


ORDER_ID = "ORD-001"
REQUESTED_AT = date(2026, 10, 25)
DELIVERY = {
    "order_id": ORDER_ID,
    "delivery_status": "delivered",
    "delivered_at": "2026-10-10",
}


class DeliveryHandoffTests(unittest.TestCase):
    def check(self, delivery: dict) -> bool:
        return within_return_window(
            json.dumps(delivery), ORDER_ID, REQUESTED_AT
        )

    def test_t01_fifteen_days(self):
        self.assertTrue(self.check(DELIVERY))

    def test_t02_same_day(self):
        self.assertTrue(self.check({**DELIVERY, "delivered_at": "2026-10-25"}))

    def test_t03_thirty_days(self):
        self.assertTrue(self.check({**DELIVERY, "delivered_at": "2026-09-25"}))

    def test_t04_thirty_one_days(self):
        self.assertFalse(self.check({**DELIVERY, "delivered_at": "2026-09-24"}))

    def test_t05_missing_date(self):
        without_date = {key: value for key, value in DELIVERY.items()
                        if key != "delivered_at"}
        with self.assertRaises(ValueError):
            self.check(without_date)

    def test_t06_null_date(self):
        with self.assertRaises(ValueError):
            self.check({**DELIVERY, "delivered_at": None})

    def test_t07_invalid_date(self):
        with self.assertRaises(ValueError):
            self.check({**DELIVERY, "delivered_at": "2026-02-30"})

    def test_t08_wrong_order_id(self):
        with self.assertRaises(ValueError):
            self.check({**DELIVERY, "order_id": "ORD-002"})

    def test_t09_incomplete_delivery(self):
        with self.assertRaises(ValueError):
            self.check({**DELIVERY, "delivery_status": "in_transit"})

    def test_t10_invalid_json(self):
        with self.assertRaises(ValueError):
            within_return_window("{broken", ORDER_ID, REQUESTED_AT)

    def test_t11_future_delivery_date(self):
        with self.assertRaises(ValueError):
            self.check({**DELIVERY, "delivered_at": "2026-10-26"})


if __name__ == "__main__":
    unittest.main()

実行コマンドは次のとおりです。

python3 -B -m unittest -v test_delivery_handoff.py

今回の実行環境は Python 3.13.5 です。この環境で 11 件を実行し、すべて成功しました。Python 3.11 は実装対象として想定していますが、この実測は 3.13.5 での結果です。

Ran 11 tests in 0.001s

OK
ケース 検証内容 期待結果 実測結果
T01 配送完了から 15 日 True PASS
T02 配送完了と申請が同日 True PASS
T03 配送完了から 30 日 True PASS
T04 配送完了から 31 日 False PASS
T05 delivered_at の欠落 ValueError PASS
T06 delivered_at が null ValueError PASS
T07 実在しない日付 2026-02-30 ValueError PASS
T08 注文番号 ORD-002 ValueError PASS
T09 配送状態 in_transit ValueError PASS
T10 JSON の構文不正 ValueError PASS
T11 配送完了日が申請日の翌日 ValueError PASS

T04 と T05 の違いが受理条件の要点です。T04 は入力が成立しているため False が適切です。T05 は経過日数を計算するための日付が欠落しているため、判定を始める前に例外となります。T07 と T11 でも、日付を任意に補って計算を続行する挙動はありません。

入力検証で保証できる範囲を整理する

今回のテストで確認したのは、固定した注文番号、配送状態、日付形式、30 日の期間条件に対する入出力です。必須項目が正しい形式で揃っていても、配送完了日が実際の配送記録と一致するかどうかは別の確認が必要です。

実運用では、配送情報の取得元、記録の更新時点、注文との対応付け、入力サイズなどを入力契約や取得処理の側で定める必要があります。特に、外部から受け取った JSON は解析処理の資源消費にも影響するため、入力サイズの制限を検討します[2]。

ここで実装した返品期間判定は、承認や返金処理とは切り離しています。エージェントを分割する場合でも、後続処理が使用する入力契約を先に定義すれば、配送完了日の欠落、注文番号の不一致、未来日付を期間判定の前に検出できます。期間を超えた注文だけが False となり、入力不備は ValueError として呼び出し元へ返るため、両者に異なる対応を適用できます。

参考文献

  1. id774, 仕事を分けるべき境界はどこにあるのか(2026-10-28). https://blog.id774.net/entry/2026/10/28/5818/
  2. Python Software Foundation, json — JSON encoder and decoder. https://docs.python.org/3.11/library/json.html
  3. Python Software Foundation, datetime — Basic date and time types. https://docs.python.org/3.11/library/datetime.html
  4. Python Software Foundation, unittest — Unit testing framework. https://docs.python.org/3.11/library/unittest.html
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?