📄

DialogflowのログをJSONにパースするためのPythonパッケージをPYPIにあげてみた

2023/01/05に公開

Dialogflowの会話データログをJSON形式に変換するPythonパッケージを作成しPYPIにあげたので紹介します。

Dialogflowとは?

DialogflowとはGoogleが提供しているチャットボットの自然言語理解プラットフォームです。
簡単にNLPが搭載されたチャットボットを作成することができ、WEBやSNSに連携しやすいよう設計されている環境です。

作ったもの

Dialogflowから出力されるResponsのログをJSONにパースするdialogflow-log-parserというPythonパッケージを作成しました。
https://pypi.org/project/dialogflow-log-parser/
https://github.com/PeterYusuke/dialogflow-log-parser

なぜ作ったのか?

結論、JSON形式に変換する処理がないため自分で作成しました。
DialgoflowからGoogle Cloudに会話ログHOCON形式で出力されます。
HOCONとはHuman-Optimized Config Object Notationの略で、コンフィギュレーションの文字列を定義したものです。
JSON形式を含み、=(イコール)や.(ピリオド)も形式として扱われます。

Dialogflowのドキュメントには「HOCONパーサーを使用してJSONに変換できます」と記載があります。
PythonではpyhoconというライブラリでHOCONをパースできますが、
欠落する情報がありましたので、すべての情報をJSONに変換できるようにライブラリを作成しました。

どのように作ったのか?

ライブラリ作成の進めかたは以下の様にしました。

  1. pyhoconで欠落する情報の確認
  2. 欠落する情報を保持できるように変換

pyhoconで欠落する情報の確認

まず初めに、想定するDialogflownのログを収集しました。すでに動作しているDialogflowからログを収集しました。
最終的にここで収集したログをテストに使用しました。

ログファイル

各ログファイルをpyhoconでパースしたところ以下のキーが重複しているため、値が欠落することが確認できました。

  • messages
  • parameters
  • contexts
欠落するログファイル

欠落する情報を保持できるように変換

欠落する情報を保持するためには、重複している情報をリスト形式に変換できれば可能であることを思いつきました。
改行コードで分割すれば、各行がそれぞれキーとかっこの終わりなどを表すようになることに気づきました。
HOCONは配列を許可しているので、重複している個所を以下の様に変換すれば配列としてHOCONパースができ、必要な情報を保持できました。

  • messages
- messages {
+ messages [
+ {
  ...
}
- messages {
+ {
  ...
}
+ ]
  • fields
- parameters {
+ parameters [
-   fields {
+ {
    ...
  }
-   fields {
+ {
    ...
  }
+ ]
  • contexts
- contexts {
+ contexts [
+ {
  ...
}
- contexts {
+ {
  ...
}
+ ]

挑戦したこと

  • PyTestを行った
    初めてPythonのテストコードを作成しテストを行いました。
    用意したファイルが想定するJSONに変換されている確認のみをしています。
    テストの設計に指摘があればいただきたいです。

  • CIとcoverageを使用
    PyTestに加えて、CIツールを追加。Circle CIをGitHubプロジェクトに追加しました。
    また、ソースコードのカバー率を確認するため、Converallsを使用。
    カバー率はpyhoconのプロジェクトを参考にしました。

こだわったこと

欠落する情報を保持するだけでもJSONにパースできますが、より簡潔なデータを取得できるように処理を加えました。

  • {}のペア探し
    データをリストに変換する際に、変換すべき{}のペアを探す処理にキューを使用しました。
    改行コードで分割した各行が、始まりor値or終わり、のいづれかを表しています。
    キューを作成し、始まり("{"が存在している場合)はキューに"{"を追加し、終わり("}"が存在している場合)はキューから値を1つ削除しました。
    キューが空になった時は{}のペアであると判定し、行のインデックスを取得しました。
    キューを使ったアルゴリズムは初挑戦で面白く、きれいに動いたときは嬉しかったです。

https://github.com/PeterYusuke/dialogflow-log-parser/blob/c60b51b3710dbdf23338890fc478197363e813cf/dialogflow_log_parser/es/logic/conf_logic.py#L61-L84

  • 複数のfields
    fieldsはparametersだけではなく、contextsにも存在しています。
    fieldsのキーだけではどのfieldsかを判断できませんでした。
    そのため、リストにするfieldsは連続している時のみとの判定を入れました。

例えば、以下の場合はリストにまとめられると判定します。

parameters {
  fields {
    ...
  }
  fields {
    ...
  }
}

以下の場合は、別のリストと判定し、別の処理を行います。

parameters {
  fields {
    ...
  }
  fields {
    ...
  }
}
contexts {
  paramters {
    fields {
      ...
    }
    fields {
      ...
    }
  }
}

上記のような判定をすることで同じ名前の別のデータを保持することができました。

  • payloadを元の文字列に変換
    Dialogflowは文字列だけではなくJSON形式のレスポンスを返すことができ、UIに反映できます。
    もとのpayloadは以下であり、
もとのpayload
{
  "richContent": [
    [
      {
        "type": "chips",
        "options": [
          {
            "text": "Chip 1"
          },
          {
            "text": "Chip 2"
          }
        ]
      }
    ]
  ]
}

これがログファイルにはこのように出力されます。

ログのpayload
payload {
  fields {
    key: "richContent"
    value {
      list_value {
        values {
          list_value {
            values {
              struct_value {
                fields {
                  key: "options"
                  value {
                    list_value {
                      values {
                        struct_value {
                          fields {
                            key: "text"
                            value {
                              string_value: "chip 1"
                            }
                          }
                        }
                      }
                      values {
                        struct_value {
                          fields {
                            key: "text"
                            value {
                              string_value: "chip 2"
                            }
                          }
                        }
                      }
                    }
                  }
                }
                fields {
                  key: "type"
                  value {
                    string_value: "chips"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

かなり扱いにくくなります。
JSONに変換するときは元のpayloadのJSON文字列に変換する処理を加えました。

次の課題

パッケージをあげることで、達成感が出ました。
一方で課題が見えてきました。
エラー時に例外を出すか、それとも、そのまま処理を進めるのかの処理が分かりませんでした。
また、sphinxを使ってドキュメントを作成した時に、関数の説明がない箇所が多かったので、ドキュメント作成をより多くしたいと思いました。

Discussion