Zenn
💨

ChatGPTの会話記録解析と集計

2025/02/12に公開

ChatGPTの会話記録解析と集計

本記事では、ChatGPTから出力された会話記録のJSONデータをGo言語を用いて解析し、日付ごとおよびモデルごとの利用回数を集計する方法について解説します。実際に作成したプログラムと、解析結果の一例を交えて説明します。

はじめに

ChatGPTの会話記録は、各会話が複数のメッセージノード(JSON形式)として出力されます。今回の解析では、特にアシスタントの返答に着目し、以下の処理を行います。

  • 日時情報の抽出
    各メッセージのcreate_time(Unixタイムスタンプ)からyyyy-mm-dd形式の日付を生成します。

  • モデル名の判別
    メッセージ内のmetadataにあるmodel_slugを確認し、なければ会話オブジェクト直下のdefault_model_slugを利用、さらに情報がない場合は"unknown"とします。

  • 集計処理
    日付ごとに各モデルの出現回数をマップに格納し、最終的にJSON形式で結果を出力します。

データのダウンロード方法

まず最初に、ChatGPTの会話記録(conversation.json)のデータダウンロード手順について説明します。

  1. ChatGPTの設定画面に移動し、データコントロールを選択します。
  2. データをエクスポートするをクリックすると、登録済みのメールアドレスにダウンロード用のURLが送信されます。
  3. メール内のURLからZIPファイルをダウンロードしてください。
  4. ダウンロードしたZIPファイルを解凍すると、出力されたフォルダ内にconversation.jsonファイルが含まれています。

プログラムの概要

以下のGoプログラムは、conversations.jsonというファイルに格納された会話記録を読み込み、上記の処理を実施した後、集計結果をsummary.jsonとして書き出します。

主な処理の流れ

  1. ファイルの読み込みとJSONパース
    json.Decoderを用いて、トップレベルが配列形式であることを確認後、各会話オブジェクトを順次読み込みます。

  2. 日付とモデルの抽出

    • create_timeからtime.Unixを使って日時を生成し、Format("2006-01-02")で日付文字列を作成します。
    • メッセージ内のmetadataからmodel_slugを取得、なければdefault_model_slugを使用します。
  3. 集計処理
    日付ごとに、該当モデルの出現回数をカウントし、マップに記録します。

  4. 結果の出力
    集計結果を整形してsummary.jsonに書き出します。

コード詳細解説

以下に、解析プログラムの全コードを示します。

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"time"
)

// Conversation は会話全体を表す構造体です
type Conversation struct {
	Title            string                 `json:"title"`
	CreateTime       float64                `json:"create_time"`
	UpdateTime       float64                `json:"update_time"`
	Mapping          map[string]MappingNode `json:"mapping"`
	DefaultModelSlug string                 `json:"default_model_slug"`
}

// MappingNode は会話内の各メッセージノードを表現します
type MappingNode struct {
	Id       string   `json:"id"`
	Message  *Message `json:"message"`
	Parent   string   `json:"parent"`
	Children []string `json:"children"`
}

// Message は各メッセージの詳細を格納します
type Message struct {
	Id         string                 `json:"id"`
	Author     Author                 `json:"author"`
	CreateTime *float64               `json:"create_time"`
	UpdateTime *float64               `json:"update_time"`
	Content    json.RawMessage        `json:"content"`
	Status     string                 `json:"status"`
	EndTurn    *bool                  `json:"end_turn"`
	Weight     float64                `json:"weight"`
	Metadata   map[string]interface{} `json:"metadata"`
	Recipient  string                 `json:"recipient"`
	Channel    interface{}            `json:"channel"`
}

// Author はメッセージの作者情報を表します
type Author struct {
	Role     string                 `json:"role"`
	Name     *string                `json:"name"`
	Metadata map[string]interface{} `json:"metadata"`
}

func main() {
	inputFile := "conversations.json"
	f, err := os.Open(inputFile)
	if err != nil {
		fmt.Printf("入力ファイルのオープンエラー: %v\n", err)
		os.Exit(1)
	}
	defer f.Close()

	dec := json.NewDecoder(f)

	// JSONが配列形式であることを確認
	t, err := dec.Token()
	if err != nil {
		fmt.Printf("JSONトークン読み取りエラー: %v\n", err)
		os.Exit(1)
	}
	delim, ok := t.(json.Delim)
	if !ok || delim != '[' {
		fmt.Println("入力JSONは配列形式である必要があります")
		os.Exit(1)
	}

	// 集計用マップ: summary[日付][モデル名] = 出現回数
	summary := make(map[string]map[string]int)

	// 各会話オブジェクトを処理
	for dec.More() {
		var conv Conversation
		if err := dec.Decode(&conv); err != nil {
			fmt.Printf("会話オブジェクトのデコードエラー: %v\n", err)
			continue
		}

		defaultModel := conv.DefaultModelSlug

		// 各メッセージノードを処理
		for _, node := range conv.Mapping {
			if node.Message == nil {
				continue
			}
			// アシスタントの返答のみ対象
			if node.Message.Author.Role != "assistant" {
				continue
			}
			if node.Message.CreateTime == nil {
				continue
			}

			// create_timeから日付を生成
			ct := *node.Message.CreateTime
			sec := int64(ct)
			nsec := int64((ct - float64(sec)) * 1e9)
			tm := time.Unix(sec, nsec)
			date := tm.Format("2006-01-02")

			// model_slugを取得
			model := ""
			if node.Message.Metadata != nil {
				if ms, exists := node.Message.Metadata["model_slug"]; exists {
					if mstr, ok := ms.(string); ok && mstr != "" {
						model = mstr
					}
				}
			}
			if model == "" {
				model = defaultModel
			}
			if model == "" {
				model = "unknown"
			}

			// 日付ごとのマップがなければ作成し、カウントをインクリメント
			if _, exists := summary[date]; !exists {
				summary[date] = make(map[string]int)
			}
			summary[date][model]++
		}
	}

	// 配列の終了トークンを読み込み
	if _, err = dec.Token(); err != nil && err != io.EOF {
		fmt.Printf("JSON終了トークンの読み込みエラー: %v\n", err)
	}

	// 集計結果をJSON形式に整形
	out, err := json.MarshalIndent(summary, "", "  ")
	if err != nil {
		fmt.Printf("サマリのJSON変換エラー: %v\n", err)
		os.Exit(1)
	}

	// 結果をsummary.jsonに書き出す
	outFileName := "summary.json"
	outFile, err := os.Create(outFileName)
	if err != nil {
		fmt.Printf("出力ファイル %s の作成エラー: %v\n", outFileName, err)
		os.Exit(1)
	}
	defer outFile.Close()

	if _, err := outFile.Write(out); err != nil {
		fmt.Printf("出力ファイル %s への書き込みエラー: %v\n", outFileName, err)
		os.Exit(1)
	}

	fmt.Printf("結果を %s に出力しました。\n", outFileName)
}

解析結果の例

上記プログラムを実行すると、以下のようなJSON形式の集計結果が得られます(一部抜粋):

{
  "2024-08-22": {
    "gpt-4o": 152
  },
  "2024-08-23": {
    "gpt-4o": 181
  },
  "2024-09-01": {
    "gpt-4o": 21,
    "gpt-4o-mini": 2
  },
  ...
}

各日付ごとに、どのモデルが何回使用されたかが把握でき、今後の利用傾向分析やシステム改善に活用できます。

まとめ

本記事では、ChatGPTの会話記録をGo言語で解析し、日付およびモデルごとの利用回数を集計する手法を解説しました。この解析手法を応用することで、チャットボットの利用状況の可視化やパフォーマンス改善への示唆が得られるでしょう。

Discussion

ログインするとコメントできます