Linebot の24時間経過したメッセージの削除

24時間経過したメッセージの削除

import warnings
from google.cloud import firestore
import datetime
import absl.logging

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# gRPCの警告を抑制
absl.logging.set_verbosity("info")

def get_user_messages(user_id):
    messages_ref = db.collection("messages")
    
    # 複数条件のクエリを設定
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
    
    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = ""
messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

では24時間以内のメッセージのみ取得して表示している

ストレージの確保のため
24時間経過したメッセージは削除する

import warnings
from google.cloud import firestore
import datetime
import absl.logging

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# gRPCの警告を抑制
absl.logging.set_verbosity("info")

def get_user_messages(user_id):
    messages_ref = db.collection("messages")
    now = datetime.datetime.now()
    
    # 24時間以内のメッセージを取得するクエリ
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", now - datetime.timedelta(days=1))
    # 24時間以上前のメッセージを削除するクエリ
    old_messages_query = messages_ref.where("user_id", "==", user_id).where("timestamp", "<", now - datetime.timedelta(days=1))

    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())
    
    # 古いメッセージを削除
    for doc in old_messages_query.stream():
        doc.reference.delete()

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = ""
messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

として実行

/Users/snowpool/aw10s/linebot/get_message.py:20: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", now - datetime.timedelta(days=1))
/Users/snowpool/aw10s/linebot/get_message.py:22: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  old_messages_query = messages_ref.where("user_id", "==", user_id).where("timestamp", "<", now - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1732049170.984219 4556639 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
メッセージ: 本日は厳しい冷え込みだ, タイムスタンプ: 2024-11-19 20:43:21+00:00

となって24時間経過したメッセージは消えた

次にこのコードに既読フラグをつけたいが
その前になぜ前回のコードが機能しなかったのかまとめておく

from google.cloud import firestore
from datetime import datetime, timedelta, timezone
import requests
import warnings

# JSONファイルで認証情報を指定
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# 特定のユーザーID
target_user_id = ""

# VoiceVox APIの設定
VOICEVOX_API_URL = "http://192.168.1.69:50021"
VOICEVOX_SPEAKER_ID = 1

def read_unread_messages():
    # UTCの現在時刻を取得
    current_time = datetime.now(timezone.utc)
    messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
    unread_messages = messages_ref.stream()

    for message in unread_messages:
        message_data = message.to_dict()
        message_text = message_data["text"]
        # FirestoreのタイムスタンプをUTCに変換
        message_time = message_data["timestamp"].replace(tzinfo=timezone.utc)

        print(f"Checking message: {message_text} at {message_time}, Time difference: {current_time - message_time}")
        if current_time - message_time > timedelta(hours=24):
            print(f"Deleting message: {message_text} at {message_time}")
            message.reference.delete()
        else:
            print(f"Not deleting message: {message_text} at {message_time}")

        read_message_aloud(message_text)
        message.reference.update({"read": True})

def read_message_aloud(text):
    query_response = requests.post(f"{VOICEVOX_API_URL}/audio_query", params={"text": text, "speaker": VOICEVOX_SPEAKER_ID})
    if query_response.status_code == 200:
        synthesis_response = requests.post(f"{VOICEVOX_API_URL}/synthesis", params={"speaker": VOICEVOX_SPEAKER_ID}, json=query_response.json())
        if synthesis_response.status_code == 200:
            print(f"Message read aloud: {text}")
        else:
            print("Failed to read aloud:", synthesis_response.text)
    else:
        print("Failed to generate audio query:", query_response.text)

read_unread_messages()

の場合

from datetime import datetime, timedelta, timezone

def read_unread_messages():
    current_time = datetime.now(timezone.utc)
    messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
    unread_messages = messages_ref.stream()

    for message in unread_messages:
        message_data = message.to_dict()
        message_time = message_data["timestamp"].replace(tzinfo=timezone.utc)

        if current_time - message_time > timedelta(hours=24):
            print(f"Deleting message: {message_text} at {message_time}")
            message.reference.delete()
        else:
            print(f"Not deleting message: {message_text} at {message_time}")

の場合
タイムスタンプをUTC時刻に変換してから、現在時刻との差を計算して24時間を超えていればメッセージを削除

ここでのタイムスタンプはtimestampフィールドから取得され、その値がすでにUTC時刻である必要がある

今回の場合は

import datetime

def get_user_messages(user_id):
    now = datetime.datetime.now()
    old_messages_query = messages_ref.where("user_id", "==", user_id).where("timestamp", "<", now - datetime.timedelta(days=1))

    for doc in old_messages_query.stream():
        doc.reference.delete()


現在時刻から1日前の時刻を計算し、それよりも前のタイムスタンプを持つメッセージをクエリで直接検索

この場合、Firestoreのtimestampは自動的にUTCで扱われるため、時差を考慮する必要がない

GPTによれば

違い
1. タイムゾーンの扱い:
* 最初のコードでは、メッセージのタイムスタンプをUTCに明示的に変換して比較しています。
* 二番目のコードでは、タイムスタンプがUTCであると仮定して、クエリで直接比較しています。
2. クエリの利用:
* 最初のコードでは、すべての未読メッセージをストリームで取得してから条件分岐を行い、必要に応じて削除しています。
* 二番目のコードでは、24時間以上古いメッセージに対して直接クエリを発行し、その結果に基づいて削除しています。

最初のコードが削除できなかった可能性として、タイムスタンプの変換や時差の取り扱いが適切でなかったか、Firestoreの設定やデータの整合性に問題がある可能性が考えられます。また、read属性がFalseに設定されたままのメッセージが削除の条件に含まれていなかったことも影響しているかもしれません

とあるが
最後の read属性についてはハズレで
これがFalseでも削除されている

Google Cloud Firestore APIの料金

Google Cloud Firestore APIの料金

Google Cloud Firestore の料金は、以下の要素に基づいて計算されます。

1. **ドキュメントの読み取り、書き込み、削除の回数**: 各操作ごとに課金されます。
2. **インデックス エントリの読み取り数**: クエリを満たすために読み取られるインデックス エントリの数に応じて課金されます。
3. **データベースのストレージ使用量**: 保存されているデータの容量に基づいて課金されます。
4. **ネットワーク帯域幅の使用量**: データの送受信量に応じて課金されます。

これらの料金は日単位で計算されます。

**無料の使用枠**も提供されており、以下の通りです。

– **保存データ**: 1 GiB
– **ドキュメントの読み取り**: 1 日あたり 50,000 回
– **ドキュメントの書き込み**: 1 日あたり 20,000 回
– **ドキュメントの削除**: 1 日あたり 20,000 回
– **送信データ転送**: 1 か月あたり 10 GiB

無料枠を超える使用量については、課金が発生します。

詳細な料金情報や具体的な料金例については、公式ドキュメントをご参照ください。

とのこと

料金はserviceKeyでのアクセスと合算されるか調べる

Google Cloud Firestore の料金は、
アクセス方法や認証手段に関係なく、プロジェクト全体の使用量に基づいて計算されます。

したがって、サービス アカウントのキー(serviceKey)を使用してアクセスした場合でも、
その操作による使用量は他のアクセス方法と合算され、プロジェクト全体の料金として請求されます。

具体的には、Firestore の課金は以下の要素に基づいています。

– **ドキュメントの読み取り、書き込み、削除の回数**
– **インデックス エントリの読み取り数**
– **データベースのストレージ使用量**
– **ネットワーク帯域幅の使用量**

これらの使用量は、アクセス方法や認証手段に関係なく、プロジェクト全体で集計されます。

したがって、サービス アカウントを使用して行われた操作も、
他のアクセス方法で行われた操作と同様に、プロジェクト全体の使用量として合算され、
料金が計算されます。

APIアクセスもserviceKeyアクセスと同様に扱われます。

Google Cloud Firestoreの料金は、
アクセス手段(APIアクセス、serviceKey、その他)に関係なく、
プロジェクト全体の使用量に基づいて計算されます。

したがって、APIを通じたアクセスも、serviceKeyでのアクセスも、
同一プロジェクト内での使用量として合算されて課金されます。

プロジェクトごとの料金体系に基づくため、
どのアクセス手段を用いても料金計算においては一貫しています

とのこと

つまりAPIでも serviceKey でのアクセスも合算されるので
新しく課金される項目が増えることはない

無料の使用枠も提供されており、以下の通りです。
* 保存データ: 1 GiB
* ドキュメントの読み取り: 1 日あたり 50,000 回
* ドキュメントの書き込み: 1 日あたり 20,000 回
* ドキュメントの削除: 1 日あたり 20,000 回
* 送信データ転送: 1 か月あたり 10 GiB

のように無料枠さえ超えないければ問題ない

まずはAPIを有効化する

Google Cloud Consoleの「Firestore API」のページにアクセス

「APIを有効にする」ボタンをクリックしてFirestore APIを有効化

Cloud Filestore API

Google Cloud Firestore API

の違いは?

調べたら
FileStore と FireStoreの違いだった

あと原因が判明
GOOGLE_APPLICATION_CREDENTIALS
を使う前提のコードになっている

これは以前設定した時に Google Cloud Vision API でうまくいかなくなったため
GOOGLE_APPLICATION_CREDENTIALS
を使わず
Jsonファイルを指定する方法にしているため

なので今回もjsonファイルを指定する

認証情報の指定: f
irestore.Client.from_service_account_json(“serviceAccountKey.json”) を使用して、
serviceAccountKey.json ファイルで認証情報を指定しました。

警告の抑制:
warnings.filterwarnings() を使って、
Firestoreに関するUserWarningを無視する設定を追加しています。

コードを

from google.cloud import firestore
from datetime import datetime, timedelta
import requests
import warnings

# JSONファイルで認証情報を指定
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# 特定のユーザーID
target_user_id = ""

# VoiceVox APIの設定
VOICEVOX_API_URL = "http://192.168.1.69:50021/speak"  # VoiceVoxのURL
VOICEVOX_SPEAKER_ID = 1  # 読み上げに使用する話者のID

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

def read_unread_messages():
    # 現在の時刻
    current_time = datetime.now()
    # Firestoreのコレクションから未読メッセージを取得
    messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
    unread_messages = messages_ref.stream()

    for message in unread_messages:
        message_data = message.to_dict()
        message_text = message_data["text"]
        message_time = message_data["timestamp"].to_datetime()
        
        # VoiceVoxでメッセージを読み上げ
        read_message_aloud(message_text)
        
        # メッセージを既読に更新
        message.reference.update({"read": True})
        
        # 24時間経過したメッセージの削除
        if current_time - message_time > timedelta(hours=24):
            message.reference.delete()

def read_message_aloud(text):
    # VoiceVoxのAPIにリクエストを送信
    response = requests.post(
        VOICEVOX_API_URL,
        json={"text": text, "speaker": VOICEVOX_SPEAKER_ID}
    )
    if response.status_code == 200:
        print("メッセージを読み上げました:", text)
    else:
        print("読み上げに失敗しました:", response.text)

# コードを実行して未読メッセージを取得して読み上げ
read_unread_messages()

にする

しかし

python get_firestore_message.py
/Users/snowpool/aw10s/linebot/get_firestore_message.py:23: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731443353.181065 4135517 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_firestore_message.py", line 53, in <module>
    read_unread_messages()
  File "/Users/snowpool/aw10s/linebot/get_firestore_message.py", line 29, in read_unread_messages
    message_time = message_data["timestamp"].to_datetime()
AttributeError: 'DatetimeWithNanoseconds' object has no attribute 'to_datetime'

となる

DatetimeWithNanoseconds オブジェクトに to_datetime() メソッドがないため、
エラーが発生しています。

このオブジェクトはそのまま datetime として利用できるので、 to_datetime() メソッドは不要です。
コードを修正して、to_datetime() の呼び出しを削除

from google.cloud import firestore
from datetime import datetime, timedelta
import requests
import warnings

# JSONファイルで認証情報を指定
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# 特定のユーザーID
target_user_id = ""

# VoiceVox APIの設定
VOICEVOX_API_URL = "http://192.168.1.69:50021/speak"  # VoiceVoxのURL
VOICEVOX_SPEAKER_ID = 1  # 読み上げに使用する話者のID

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

def read_unread_messages():
    # 現在の時刻
    current_time = datetime.now()
    # Firestoreのコレクションから未読メッセージを取得
    messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
    unread_messages = messages_ref.stream()

    for message in unread_messages:
        message_data = message.to_dict()
        message_text = message_data["text"]
        message_time = message_data["timestamp"]  # to_datetime()を削除

        # VoiceVoxでメッセージを読み上げ
        read_message_aloud(message_text)
        
        # メッセージを既読に更新
        message.reference.update({"read": True})
        
        # 24時間経過したメッセージの削除
        if current_time - message_time > timedelta(hours=24):
            message.reference.delete()

def read_message_aloud(text):
    # VoiceVoxのAPIにリクエストを送信
    response = requests.post(
        VOICEVOX_API_URL,
        json={"text": text, "speaker": VOICEVOX_SPEAKER_ID}
    )
    if response.status_code == 200:
        print("メッセージを読み上げました:", text)
    else:
        print("読み上げに失敗しました:", response.text)

# コードを実行して未読メッセージを取得して読み上げ
read_unread_messages()

今度は

/Users/snowpool/aw10s/linebot/get_firestore_message.py:23: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731443604.856422 4139038 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
読み上げに失敗しました: {"detail":"Not Found"}
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_firestore_message.py", line 53, in <module>
    read_unread_messages()
  File "/Users/snowpool/aw10s/linebot/get_firestore_message.py", line 38, in read_unread_messages
    if current_time - message_time > timedelta(hours=24):
TypeError: can't subtract offset-naive and offset-aware datetimes

となる

このエラーは、current_timeが「オフセットのない(ナイーブな)datetimeオブジェクト」で、
message_timeが「オフセットのある(タイムゾーン付きの)datetimeオブジェクト」であるため、
直接引き算できないことが原因です。

この問題を解決するために、両方を同じ形式のdatetimeオブジェクトに統一します。
コードを修正して、current_timeをUTCタイムゾーン付きに変換

しかし

/Users/snowpool/aw10s/linebot/get_firestore_message.py:23: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731443712.845077 4140502 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
読み上げに失敗しました: {"detail":"Not Found"}
読み上げに失敗しました: {"detail":"Not Found"}
読み上げに失敗しました: {"detail":"Not Found"}

となる

“読み上げに失敗しました:
{“detail”:”Not Found”}” エラーは、VoiceVox APIに送信したリクエストが見つからない
(404 Not Found)ためです。

これは、VoiceVox APIのエンドポイントや
リクエストの構造が間違っている可能性があることを示しています。

VoiceVox APIの仕様に沿ってエンドポイントとリクエストのフォーマットを確認し、
修正を加えてみましょう。

例えば、VoiceVoxでのリクエストには、
/audio_query エンドポイントを使用して音声生成クエリを作成し、
その後 /synthesis エンドポイントで音声ファイルを生成する必要がある場合があります。

from google.cloud import firestore
from datetime import datetime, timedelta, timezone
import requests
import warnings

# JSONファイルで認証情報を指定
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

# 特定のユーザーID
target_user_id = ""

# VoiceVox APIの設定
VOICEVOX_API_URL = "http://192.168.1.69:50021"  # VoiceVoxのベースURL
VOICEVOX_SPEAKER_ID = 1  # 読み上げに使用する話者のID

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

def read_unread_messages():
    # 現在の時刻をUTCのオフセット付きで取得
    current_time = datetime.now(timezone.utc)
    # Firestoreのコレクションから未読メッセージを取得
    messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False)
    unread_messages = messages_ref.stream()

    for message in unread_messages:
        message_data = message.to_dict()
        message_text = message_data["text"]
        message_time = message_data["timestamp"]

        # VoiceVoxでメッセージを読み上げ
        read_message_aloud(message_text)
        
        # メッセージを既読に更新
        message.reference.update({"read": True})
        
        # 24時間経過したメッセージの削除
        if current_time - message_time > timedelta(hours=24):
            message.reference.delete()

def read_message_aloud(text):
    # audio_queryを生成
    query_response = requests.post(
        f"{VOICEVOX_API_URL}/audio_query",
        params={"text": text, "speaker": VOICEVOX_SPEAKER_ID}
    )
    if query_response.status_code != 200:
        print("audio_queryの生成に失敗しました:", query_response.text)
        return

    # 音声合成リクエストを送信
    synthesis_response = requests.post(
        f"{VOICEVOX_API_URL}/synthesis",
        params={"speaker": VOICEVOX_SPEAKER_ID},
        json=query_response.json()
    )
    if synthesis_response.status_code == 200:
        print("メッセージを読み上げました:", text)
    else:
        print("読み上げに失敗しました:", synthesis_response.text)

# コードを実行して未読メッセージを取得して読み上げ
read_unread_messages()

これを実行すると

/Users/snowpool/aw10s/linebot/get_firestore_message.py:23: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead. messages_ref = db.collection("messages").where("user_id", "==", target_user_id).where("read", "==", False) WARNING: All log messages before absl::InitializeLog() is called are written to STDERR I0000 00:00:1731444164.973858 4145605 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache メッセージを読み上げました: ゴルゴ13だ。

となる

コードが正常に動作し、メッセージが読み上げられたようで良かったです!
“メッセージを読み上げました: ゴルゴ13だ。” と表示されたので、
VoiceVoxを使った音声合成が成功しています
となるが

ちょっと意味が違う
Firestoreに保存されたメッセージの取得はできているが
これをvoicevox で音声を作成し
それを再生する
が本来の目的

以前作成したモジュールから使うことにする

音声作成と作成した音声を使用後削除するモジュール
音声を作成するためのモジュールがあるので
それを使うことにする

これらのモジュールはカレンダー読み上げのプロジェクトにあるので
それを使う

firestoreDBからメッセージ取得

firestoreDBからメッセージ取得

 python main3.py      

で実行

[2024-11-10 06:45:16.017] [ctranslate2] [thread 3848107] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
認識されたテキスト: 今度はiCloud Driveに保存しますか
Firestoreに保存を開始します: 今度はiCloud Driveに保存しますか
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731188739.927080 3848107 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Firestoreに保存されました: 今度はiCloud Driveに保存しますか
Firestoreへの保存処理が完了しました
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
今度はiCloud Driveに保存しますか

でDB保存された後に

import warnings
from google.cloud import firestore
import datetime

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

def get_user_messages(user_id):
    messages_ref = db.collection("messages")
    
    # 複数条件のクエリを設定
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
    
    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = ""
messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

を実行したけど

/Users/snowpool/aw10s/linebot/get_message.py:15: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731188828.849859 3850189 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache

となる

原因を調べるため
Firestoreへログインし
user_idの値を調べたら
user_id_example
となっていて
linebotのIDではなかった

このため

import warnings
from google.cloud import firestore
import datetime

# 警告を無視する設定
warnings.filterwarnings("ignore", category=UserWarning, module="google.cloud.firestore_v1.base_collection")

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

def get_user_messages(user_id):
    messages_ref = db.collection("messages")
    
    # 複数条件のクエリを設定
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
    
    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = "user_id_example"


messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

と変更し

python get_message.py

を実行

/Users/snowpool/aw10s/linebot/get_message.py:15: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731190387.989119 3864599 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
メッセージ: 動作していますか, タイムスタンプ: 2024-11-09 21:39:35.038000+00:00
メッセージ: 今度はiCloud Driveに保存しますか, タイムスタンプ: 2024-11-09 21:45:40.490000+00:00

となった

つまり送信した時のuser_id がlinebotのものになるようにしないとダメ

user_idの値を実際のLINE botユーザーのIDに設定する

user_id の値を user_id_example ではなく linebotのユーザの値にしたい

FireStore の
user_idの値を実際のLINE botユーザーのIDに設定するには、
main2.pyのコードで、ユーザーIDを動的に取得し、Firestoreに保存する際に正しいIDを設定する必要がある

GPTによれば

修正方法
1. main2.pyのuser_idを設定する部分で、LINE botからユーザーIDを取得できるようにします。
2. 例えば、index.jsでLINEから送られてきたユーザーIDを取得し、Pythonスクリプトに渡す仕組みを構築するか、必要に応じて固定のIDを用いる方法も考えられます。

とのこと

INE botユーザーのIDを正確に反映するために、user_idをmain2.pyに渡すか、
スクリプトで直接設定する

import sounddevice as sd
from module.module_whisper import FasterWhisperModel
from module.module_recorder import Recorder
import time
from line_notify import LineNotify  # 作成したLineNotifyモジュールをインポート
from ollama_text_correction import OllamaTextCorrector  # Ollamaによる修正モジュールをインポート
from line_bot_sender import LineBotSender  # LINE bot送信用のモジュールをインポート
import firebase_admin
from firebase_admin import credentials, firestore

# Firebase Admin SDKの初期化
if not firebase_admin._apps:
    cred = credentials.Certificate("path/to/serviceAccountKey.json")  # 認証ファイルのパスに置き換え
    firebase_admin.initialize_app(cred)

# Firestoreのクライアントを取得
db = firestore.client()

# LINE botユーザーIDを設定(実際のLINEユーザーIDに置き換え)
LINE_USER_ID = "実際のLINEユーザーのID"  # この部分を実際のユーザーIDに変更

def save_to_firestore(user_id, text):
    # Firestoreにメッセージを保存する関数
    try:
        message_data = {
            "user_id": user_id,
            "text": text,
            "timestamp": firestore.SERVER_TIMESTAMP,
            "read": False
        }
        db.collection("messages").add(message_data)
        print("Firestoreに保存されました:", text)
    except Exception as e:
        print(f"Firestoreに保存中にエラーが発生しました: {e}")

def main():
    recorder = Recorder()
    fasterWhispermodel = FasterWhisperModel()

    # 入力された音声テキストを格納するリスト
    recognized_texts = []

    # LINE Notifyのモジュールを初期化(config.jsonからトークンを読み込む)
    line_notify = LineNotify("config.json")
    
    # Ollamaのテキスト修正モジュールを初期化
    text_corrector = OllamaTextCorrector("config.json")
    
    # LINE bot送信用のモジュールを初期化
    line_bot_sender = LineBotSender("config.json")

    while True:
        start_time = time.time()  # 処理開始時刻を記録
        audio_data = recorder.speech2audio()

        # 処理が10秒間行われなかった場合はループを抜ける
        if time.time() - start_time >= 10:
            print("10秒間音声が入力されなかったため、ループを終了します。")
            break

        if audio_data is None:
            print("無音状態が続いたため、ループを終了します。")
            break  # 無音でループを抜ける
        
        # 音声をテキストに変換
        text = fasterWhispermodel.audio2text(audio_data)
        
        # Ollamaでテキストを構成
        corrected_text = text_corrector.correct_text(text)
        
        if corrected_text:  # Noneが返された場合はスキップ
            recognized_texts.append(corrected_text)
            print("認識されたテキスト:", corrected_text)

            # Firestoreに保存を開始
            print("Firestoreに保存を開始します:", corrected_text)
            save_to_firestore(LINE_USER_ID, corrected_text)  # LINEのユーザーIDを指定
            print("Firestoreへの保存処理が完了しました")

    # ループ終了後に、入力した音声テキストを改行付きで一覧表示
    if recognized_texts:
        message = "\n".join(recognized_texts)
        print("\n入力された音声テキスト一覧:")
        print(message)

        # LINE Notifyでメッセージを送信
        line_notify.send(f"入力された音声テキスト一覧:\n{message}")
        
        # LINE botで修正されたテキストを送信
        line_bot_sender.send_message(f"修正された音声テキスト:\n{message}")
    else:
        print("入力メッセージはありませんでした")

if __name__ == "__main__":
    main()

となるようにコード変更する

コードを新しいmain.4.pyを作成して実行する

touch main4.py

でコードを貼り付け

なおjsonファイルパスとユーザIDは修正しておく

import sounddevice as sd
from module.module_whisper import FasterWhisperModel
from module.module_recorder import Recorder
import time
from line_notify import LineNotify  # 作成したLineNotifyモジュールをインポート
from ollama_text_correction import OllamaTextCorrector  # Ollamaによる修正モジュールをインポート
from line_bot_sender import LineBotSender  # LINE bot送信用のモジュールをインポート
import firebase_admin
from firebase_admin import credentials, firestore

# Firebase Admin SDKの初期化
if not firebase_admin._apps:
    cred = credentials.Certificate("serviceAccountKey.json")  # 認証ファイルのパスに置き換え
    firebase_admin.initialize_app(cred)

# Firestoreのクライアントを取得
db = firestore.client()

# LINE botユーザーIDを設定(実際のLINEユーザーIDに置き換え)
LINE_USER_ID = ""  # この部分を実際のユーザーIDに変更

def save_to_firestore(user_id, text):
    # Firestoreにメッセージを保存する関数
    try:
        message_data = {
            "user_id": user_id,
            "text": text,
            "timestamp": firestore.SERVER_TIMESTAMP,
            "read": False
        }
        db.collection("messages").add(message_data)
        print("Firestoreに保存されました:", text)
    except Exception as e:
        print(f"Firestoreに保存中にエラーが発生しました: {e}")

def main():
    recorder = Recorder()
    fasterWhispermodel = FasterWhisperModel()

    # 入力された音声テキストを格納するリスト
    recognized_texts = []

    # LINE Notifyのモジュールを初期化(config.jsonからトークンを読み込む)
    line_notify = LineNotify("config.json")
    
    # Ollamaのテキスト修正モジュールを初期化
    text_corrector = OllamaTextCorrector("config.json")
    
    # LINE bot送信用のモジュールを初期化
    line_bot_sender = LineBotSender("config.json")

    while True:
        start_time = time.time()  # 処理開始時刻を記録
        audio_data = recorder.speech2audio()

        # 処理が10秒間行われなかった場合はループを抜ける
        if time.time() - start_time >= 10:
            print("10秒間音声が入力されなかったため、ループを終了します。")
            break

        if audio_data is None:
            print("無音状態が続いたため、ループを終了します。")
            break  # 無音でループを抜ける
        
        # 音声をテキストに変換
        text = fasterWhispermodel.audio2text(audio_data)
        
        # Ollamaでテキストを構成
        corrected_text = text_corrector.correct_text(text)
        
        if corrected_text:  # Noneが返された場合はスキップ
            recognized_texts.append(corrected_text)
            print("認識されたテキスト:", corrected_text)

            # Firestoreに保存を開始
            print("Firestoreに保存を開始します:", corrected_text)
            save_to_firestore(LINE_USER_ID, corrected_text)  # LINEのユーザーIDを指定
            print("Firestoreへの保存処理が完了しました")

    # ループ終了後に、入力した音声テキストを改行付きで一覧表示
    if recognized_texts:
        message = "\n".join(recognized_texts)
        print("\n入力された音声テキスト一覧:")
        print(message)

        # LINE Notifyでメッセージを送信
        line_notify.send(f"入力された音声テキスト一覧:\n{message}")
        
        # LINE botで修正されたテキストを送信
        line_bot_sender.send_message(f"修正された音声テキスト:\n{message}")
    else:
        print("入力メッセージはありませんでした")

if __name__ == "__main__":
    main()

これで

python main4.py

を実行

[2024-11-11 05:19:46.714] [ctranslate2] [thread 3933364] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
認識されたテキスト: 少々重量過剰かもしれない
Firestoreに保存を開始します: 少々重量過剰かもしれない
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731270012.069859 3933364 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Firestoreに保存されました: 少々重量過剰かもしれない
Firestoreへの保存処理が完了しました
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
少々重量過剰かもしれない
[・shell]
となってユーザIDが反映されて保存される

python get_message.py

を実行すると

/Users/snowpool/aw10s/linebot/get_message.py:15: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731270127.577985 3935695 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
メッセージ: 少々重量過剰かもしれない, タイムスタンプ: 2024-11-10 20:20:12.657000+00:00

でメッセージ取得も問題ない

pythonでfirestoreデータ保存

pythonでfirestoreデータ保存

import sounddevice as sd
from module.module_whisper import FasterWhisperModel
from module.module_recorder import Recorder
import time
from line_notify import LineNotify
from ollama_text_correction import OllamaTextCorrector
from line_bot_sender import LineBotSender
import firebase_admin
from firebase_admin import credentials, firestore

# Firebase Admin SDKの初期化
if not firebase_admin._apps:
    cred = credentials.Certificate("serviceAccountKey.json")  # 認証ファイルのパスに置き換え
    firebase_admin.initialize_app(cred)

# Firestoreのクライアントを取得
db = firestore.client()

def save_to_firestore(user_id, text):
    # Firestoreにメッセージを保存する関数
    message_data = {
        "user_id": user_id,
        "text": text,
        "timestamp": firestore.SERVER_TIMESTAMP,
        "read": False
    }
    db.collection("messages").add(message_data)

def main():
    recorder = Recorder()
    fasterWhispermodel = FasterWhisperModel()
    recognized_texts = []

    line_notify = LineNotify("config.json")
    text_corrector = OllamaTextCorrector("config.json")
    line_bot_sender = LineBotSender("config.json")

    while True:
        start_time = time.time()
        audio_data = recorder.speech2audio()

        if time.time() - start_time >= 10:
            print("10秒間音声が入力されなかったため、ループを終了します。")
            break

        if audio_data is None:
            print("無音状態が続いたため、ループを終了します。")
            break

        # 音声をテキストに変換
        text = fasterWhispermodel.audio2text(audio_data)
        
        # Ollamaでテキストを構成
        corrected_text = text_corrector.correct_text(text)
        
        if corrected_text:
            recognized_texts.append(corrected_text)
            print(corrected_text)

            # Firestoreにメッセージを保存
            save_to_firestore("user_id_example", corrected_text)  # user_idを適切に設定

    if recognized_texts:
        message = "\n".join(recognized_texts)
        print("\n入力された音声テキスト一覧:")
        print(message)

        # LINE Notifyでメッセージを送信
        line_notify.send(f"入力された音声テキスト一覧:\n{message}")
        
        # LINE botで修正されたテキストを送信
        line_bot_sender.send_message(f"修正された音声テキスト:\n{message}")
    else:
        print("入力メッセージはありませんでした")

if __name__ == "__main__":
    main()

として
Firestoreでデータ保存するようにしたけど

[2024-11-10 06:39:11.240] [ctranslate2] [thread 3843030] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
動作していますか
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731188374.394713 3843030 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
動作していますか

となるが保存されているかわからない

対応策をGPTで調べる

このメッセージは、音声テキストが「動作していますか」と認識され、表示されていることを示しています。
ただし、Firestoreに保存されたかどうかについての情報が表示されていません。
Firestoreに保存されない原因と確認すべきポイントについて以下に示します。

### 確認ポイント

1. **Firestoreの保存関数の確認**
Firestoreへの保存処理が正常に機能しているか確認するため、
以下のようにFirestore保存関数に`try-except`ブロックを追加して、エラーメッセージが出力されるようにします。

“`python
def save_to_firestore(user_id, text):
try:
message_data = {
“user_id”: user_id,
“text”: text,
“timestamp”: firestore.SERVER_TIMESTAMP,
“read”: False
}
db.collection(“messages”).add(message_data)
print(“Firestoreに保存されました:”, text)
except Exception as e:
print(f”Firestoreに保存中にエラーが発生しました: {e}”)
“`

`main2.py`の実行中にエラーメッセージが出力されるか確認してください。

2. **Firestoreへの接続確認**
– `firebase_admin.initialize_app()`の認証情報が正しいか確認します。
    特に、`serviceAccountKey.json`のファイルパスが正しいかを再度確認してください。
– Firestore Consoleで「messages」コレクションが生成されているか確認し、
データが追加されていない場合は、アクセス権限やネットワーク接続を再確認します。

3. **Firestoreルールの設定確認**
Firebase ConsoleのFirestoreルールで読み書きが許可されているかを確認します。ルールの設定はテスト目的で以下のように設定し、保存されるか確認してみてください。

“`plaintext
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
“`

4. **デバッグ出力の追加**
Firestoreにデータが保存される直前と保存後に、以下のようなデバッグメッセージを出力すると、保存処理のどの段階で問題が発生しているか確認しやすくなります。

“`python
print(“Firestoreに保存を開始します:”, text)
save_to_firestore(“user_id_example”, corrected_text)
print(“Firestoreへの保存処理が完了しました”)
“`

これらの手順を試してみて、Firestoreにデータが保存されるかどうかを確認してください。

とのこと

import sounddevice as sd
from module.module_whisper import FasterWhisperModel
from module.module_recorder import Recorder
import time
from line_notify import LineNotify  # 作成したLineNotifyモジュールをインポート
from ollama_text_correction import OllamaTextCorrector  # Ollamaによる修正モジュールをインポート
from line_bot_sender import LineBotSender  # LINE bot送信用のモジュールをインポート
import firebase_admin
from firebase_admin import credentials, firestore

# Firebase Admin SDKの初期化
if not firebase_admin._apps:
    cred = credentials.Certificate("serviceAccountKey.json")  # 認証ファイルのパスに置き換え
    firebase_admin.initialize_app(cred)

# Firestoreのクライアントを取得
db = firestore.client()

def save_to_firestore(user_id, text):
    # Firestoreにメッセージを保存する関数
    try:
        message_data = {
            "user_id": user_id,
            "text": text,
            "timestamp": firestore.SERVER_TIMESTAMP,
            "read": False
        }
        db.collection("messages").add(message_data)
        print("Firestoreに保存されました:", text)
    except Exception as e:
        print(f"Firestoreに保存中にエラーが発生しました: {e}")

def main():
    recorder = Recorder()
    fasterWhispermodel = FasterWhisperModel()

    # 入力された音声テキストを格納するリスト
    recognized_texts = []

    # LINE Notifyのモジュールを初期化(config.jsonからトークンを読み込む)
    line_notify = LineNotify("config.json")
    
    # Ollamaのテキスト修正モジュールを初期化
    text_corrector = OllamaTextCorrector("config.json")
    
    # LINE bot送信用のモジュールを初期化
    line_bot_sender = LineBotSender("config.json")

    while True:
        start_time = time.time()  # 処理開始時刻を記録
        audio_data = recorder.speech2audio()

        # 処理が10秒間行われなかった場合はループを抜ける
        if time.time() - start_time >= 10:
            print("10秒間音声が入力されなかったため、ループを終了します。")
            break

        if audio_data is None:
            print("無音状態が続いたため、ループを終了します。")
            break  # 無音でループを抜ける
        
        # 音声をテキストに変換
        text = fasterWhispermodel.audio2text(audio_data)
        
        # Ollamaでテキストを構成
        corrected_text = text_corrector.correct_text(text)
        
        if corrected_text:  # Noneが返された場合はスキップ
            recognized_texts.append(corrected_text)
            print("認識されたテキスト:", corrected_text)

            # Firestoreに保存を開始
            print("Firestoreに保存を開始します:", corrected_text)
            save_to_firestore("user_id_example", corrected_text)  # user_idを適切に設定
            print("Firestoreへの保存処理が完了しました")

    # ループ終了後に、入力した音声テキストを改行付きで一覧表示
    if recognized_texts:
        message = "\n".join(recognized_texts)
        print("\n入力された音声テキスト一覧:")
        print(message)

        # LINE Notifyでメッセージを送信
        line_notify.send(f"入力された音声テキスト一覧:\n{message}")
        
        # LINE botで修正されたテキストを送信
        line_bot_sender.send_message(f"修正された音声テキスト:\n{message}")
    else:
        print("入力メッセージはありませんでした")

if __name__ == "__main__":
    main()

へ修正する

実行すると

[2024-11-10 06:45:16.017] [ctranslate2] [thread 3848107] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
認識されたテキスト: 今度はiCloud Driveに保存しますか
Firestoreに保存を開始します: 今度はiCloud Driveに保存しますか
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731188739.927080 3848107 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Firestoreに保存されました: 今度はiCloud Driveに保存しますか
Firestoreへの保存処理が完了しました
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
今度はiCloud Driveに保存しますか

となる

これで firestoreへの保存はOK

次はこのメッセージを取得する

LINEbot 特定のメッセージの取得

LINEbot 特定のメッセージの取得

from google.cloud import firestore
import datetime

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

def get_user_messages(user_id):
    # Firestore のコレクション名が "messages" であると仮定
    messages_ref = db.collection("messages")
    
    # user_id に一致するドキュメントをクエリで取得
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
    
    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = ""
messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

を実行したら

/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/base_collection.py:304: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  return query.where(field_path, op_string, value)
/Users/snowpool/aw10s/linebot/get_message.py:12: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731082506.541246 3718460 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 162, in error_remapped_callable
    return _StreamingResponseIterator(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 88, in __init__
    self._stored_first_result = next(self._wrapped)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/grpc/_channel.py", line 543, in __next__
    return self._next()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/grpc/_channel.py", line 969, in _next
    raise self
grpc._channel._MultiThreadedRendezvous: <_MultiThreadedRendezvous of RPC that terminated with:
	status = StatusCode.FAILED_PRECONDITION
	details = "The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/voicelinebot/firestore/indexes?create_composite=

	debug_error_string = "UNKNOWN:Error received from peer ipv4:142.251.42.170:443 {grpc_message:"The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/voicelinebot/firestore/indexes?create_composite=, grpc_status:9, created_time:"2024-11-09T01:15:06.942376+09:00"}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 23, in <module>
    messages = get_user_messages(user_id)
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 16, in get_user_messages
    for doc in query.stream():
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/stream_generator.py", line 58, in __next__
    return self._generator.__next__()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/query.py", line 400, in _make_stream
    response_iterator, expected_prefix = self._get_stream_iterator(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/query.py", line 250, in _get_stream_iterator
    response_iterator = self._client._firestore_api.run_query(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/services/firestore/client.py", line 1558, in run_query
    response = rpc(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/gapic_v1/method.py", line 113, in __call__
    return wrapped_func(*args, **kwargs)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/retry.py", line 349, in retry_wrapped_func
    return retry_target(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/retry.py", line 191, in retry_target
    return target()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/timeout.py", line 120, in func_with_timeout
    return func(*args, **kwargs)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 166, in error_remapped_callable
    raise exceptions.from_grpc_error(exc) from exc
google.api_core.exceptions.FailedPrecondition: 400 The query requires an index. You can create it here: 

となった

このエラーログをGPTへ貼り付け
解説と対処を日本語で表示

結果は

このエラーは、Firestoreで複合インデックスが必要なクエリを実行しようとしたために発生しています。
where(“user_id”, “==”, user_id).where(“timestamp”, “>=”, datetime.datetime.now() – datetime.timedelta(days=1))という複数条件のクエリを実行した際、
Firestoreは自動で複合インデックスを作成しません。そのため、手動でインデックスを作成する必要があります。

とのこと

Firestoreインデックス作成ページにアクセス

インデックス作成画面で、user_idとtimestampフィールドを基にしたインデックスを作成する

インデックスを追加をクリック

コレクションIDには「messages」と入力
(クエリで指定したコレクション名と一致させる必要があるため)

フィールドのパスに
user_id を入力し
Ascending のままでOK
意味は照準

同様に
time_stampも設定する

クエリのスコープで
コレクション
特定のコレクション パス内のクエリの場合

コレクション グループ
同じコレクション ID を持つすべてのコレクションを対象とするクエリ用

とあるのでGPTで調べる

「コレクション」スコープと「コレクション グループ」スコープのどちらが適しているかは、
アプリケーションの構造や必要なデータ取得の範囲によって異なります。

### 1. **コレクション スコープ**
– **対象**:特定のコレクション内のドキュメントを対象とします。
– **メリット**:クエリ範囲が狭いため、効率的で検索速度が速くなります。
– **適用例**:ユーザーごとに分けた特定のコレクションからメッセージを取得する場合などに適しています。
– **おすすめの利用場面**:
特定のコレクションのみからデータを取得する場合や、データ構造が単一階層で構成されている場合。

### 2. **コレクション グループ スコープ**
– **対象**:同じコレクションIDを持つすべてのサブコレクションに対してクエリを行います。
– **メリット**:複数の階層やドキュメントにまたがって一括検索ができるため、
   データの階層が深くても一度のクエリで対象をカバーできます。
– **適用例**:例えば、複数のユーザーごとに階層を作ってメッセージサブコレクションがある場合、
   すべてのユーザーの「messages」サブコレクションからまとめてメッセージを検索する場合に便利です。
– **おすすめの利用場面**:
データが階層構造で保存されている場合や、複数のコレクション階層にまたがって同じデータ構造を持つ場合。

### 選択のポイント
– **単一のコレクション内で特定ユーザーや特定の条件のデータを取得したい場合**は「コレクション」スコープが最適です。
– **すべてのユーザーのデータに対して同時に検索をかけたい場合**や、
  データが階層化されている場合は「コレクション グループ」スコープが便利です。

今回は特定のユーザーのメッセージにアクセスするので、**コレクション スコープ**で十分だと思われます。

とのこと

なので
コレクションを選択

これでインデックスを作成する

再度実行すると

The above exception was the direct cause of the following exception:

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731083489.912237 3734254 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache

Firestoreの警告メッセージで指摘されているように、
whereメソッドの代わりに filter キーワードを使用して条件を指定すると警告が解消されます
とのこと

query = messages_ref \
    .where("user_id", "==", user_id) \
    .where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
の部分を
query = messages_ref.filter("user_id", "==", user_id).filter("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))

に変更

しかし

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 25, in <module>
    messages = get_user_messages(user_id)
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 13, in get_user_messages
    query = messages_ref.filter("user_id", "==", user_id).filter("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
AttributeError: 'CollectionReference' object has no attribute 'filter'

となる

AttributeError: ‘CollectionReference’ object has no attribute ‘filter’ というエラーは、
FirestoreのPython SDKには filter メソッドが存在しないために発生

where メソッドを使用して、複数の条件をチェーンで繋げる

しかし結果は変わらない

一度
main2.pyを実行し

音声メッセージを linebot へ送信

本来ならこれを取得できるはずだがDBに保存されていない

このため保存するコードを追加することにする

npm install firebase-admin

ってなっているけど
そもそもインストールがあってるか怪しいので調べる

npm install -g firebase-tools   

でインストールしてるので多分OK

npm install firebase-admin

を実行したら

added 158 packages in 20s 13 packages are looking for funding run npm fund for details npm notice npm notice New minor version of npm available! 10.7.0 -> 10.9.0 npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.9.0 npm notice To update run: npm install -g npm@10.9.0 npm notice

となったのでOK

次にindex.js を書き換える

const functions = require("firebase-functions");
const express = require("express");
const line = require("@line/bot-sdk");

const config = {
  channelAccessToken: functions.config().line.channel_access_token,
  channelSecret: functions.config().line.channel_secret,
};

const app = express();

app.post("/webhook", line.middleware(config), (req, res) => {
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  return client.replyMessage(event.replyToken, {
    type: "text",
    text: event.message.text,
  });
}

exports.webhook = functions.https.onRequest(app);

const functions = require("firebase-functions");
const express = require("express");
const line = require("@line/bot-sdk");
const admin = require("firebase-admin");

// Firebase Admin SDKの初期化
admin.initializeApp();
const db = admin.firestore();

const config = {
  channelAccessToken: functions.config().line.channel_access_token,
  channelSecret: functions.config().line.channel_secret,
};

const app = express();
const client = new line.Client(config);

// 特定のユーザーID
const targetUserId = "";  // 実際のLINEユーザーIDに置き換えてください

// Firestoreにメッセージを保存する関数
async function saveMessageToFirestore(userId, text) {
  const currentTimestamp = admin.firestore.FieldValue.serverTimestamp();

  // Firestoreにメッセージを保存
  await db.collection("messages").add({
    user_id: userId,        // LINEのユーザーID
    text: text,              // メッセージ本文
    timestamp: currentTimestamp,
    read: false,             // 既読状態
  });
}

// LINEメッセージのイベント処理
async function handleEvent(event) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  const userId = event.source.userId;
  const messageText = event.message.text;

  // 特定のユーザーからのメッセージのみFirestoreに保存
  if (userId === targetUserId) {
    await saveMessageToFirestore(userId, messageText);
    console.log("メッセージがFirestoreに保存されました:", messageText);
  }

  // メッセージ返信
  return client.replyMessage(event.replyToken, {
    type: "text",
    text: messageText,
  });
}

app.post("/webhook", line.middleware(config), (req, res) => {
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

// Firebase Functionsのエントリーポイント
exports.webhook = functions.https.onRequest(app);

へ変更

これで

firebase deploy --only functions --gen 1

とすると

error: unknown option '--gen'

   ╭────────────────────────────────────────────────────────────────────╮
   │                                                                    │
   │                 Update available 13.18.0 → 13.24.2                 │
   │           To update to the latest version using npm, run           │
   │                   npm install -g firebase-tools                    │
   │   For other CLI management options, visit the CLI documentation    │
   │         (https://firebase.google.com/docs/cli#update-cli)          │
   │                                                                    │
   │                                                                    │
   │                                                                    │
   ╰────────────────────────────────────────────────────────────────────╯

となる

npm install --save firebase-functions@latest

を実行

added 70 packages, and audited 229 packages in 3s

25 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

再度デプロイするので

firebase deploy --only functions     

を実行

✔  Deploy complete!

実行はできたけど

python main2.py                

を実行し

[2024-11-09 02:38:39.425] [ctranslate2] [thread 3780473] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
アロエベラはれ
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
アロエベラはれ

この結果を取得しようとしたけど
DBに格納されていない

index.jsのコード変更だけでは保存機能が動作しない?
ということでGPTで調べる

GPTによれば

はい、`index.js`のコード変更だけでは、Firestoreに保存機能が動作しない可能性があります。
`index.js`では主にLINEbotからのメッセージをFirestoreに保存する役割を果たしますが、
**`main2.py`の実行中に音声から認識されたテキストをFirestoreに保存するには、
`main2.py`内にFirestoreへの保存処理を追加する必要があります**。

以下が、`index.js`と`main2.py`の役割の違いです:

1. **`index.js`の役割**
– `index.js`は、LINEbotのWebhookとして機能し、LINEのユーザーからメッセージを受信した際に、そのメッセージをFirestoreに保存します。
– したがって、`index.js`はLINEからの入力メッセージに対して保存処理を行うものです。

2. **`main2.py`の役割**
– `main2.py`は、音声認識を行い、認識されたテキストをLINE NotifyやLINE Botに送信する役割を持っています。
– Firestoreに認識されたテキストを保存したい場合、`main2.py`内にFirestoreへの保存処理(`save_to_firestore`関数)を追加する必要があります。

### `main2.py`にFirestore保存機能を追加する理由

– `main2.py`が音声認識で生成したテキストは、LINEのメッセージとは異なるソースからのデータです。そのため、このテキストデータをFirestoreに保存するためには、`main2.py`内でFirestore保存処理を直接行う必要があります。

– `index.js`の変更は、LINEメッセージのみをFirestoreに保存する機能を提供するものなので、音声から生成されたテキストには影響を与えません。

したがって、**音声認識されたテキストをFirestoreに保存するには、`main2.py`内にFirestore保存機能を追加することが必須**です。

ということ
なのでwebhookの設定だけじゃだめ

次にmain2.pyを改造してmain3.pyとして
これに firestore保存機能をつける

Firestore DBの作成

Firestore DBの作成

import firebase_admin
from firebase_admin import credentials, firestore

# サービスアカウント JSON ファイルのパスに置き換える
cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

# Firestore クライアントの取得
db = firestore.client()

# Firestore の 'messages' コレクションからデータを取得
def get_messages():
    messages_ref = db.collection("messages")
    docs = messages_ref.stream()
    
    for doc in docs:
        print(f"{doc.id} => {doc.to_dict()}")

# 動作確認のためにメッセージを取得
get_messages()

print("Firestore に接続しました。")

を実行し

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1730742741.766654 3504035 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Firestore に接続しました。

となった

warning のためGPTで調べる

このメッセージは、**正常に Firestore に接続され、`get_messages()` 関数が実行されている**ことを示しています。
具体的には、次の点が確認できます。

1. **「Firestore に接続しました。」の表示**:
– このメッセージが表示されているため、Firestore への接続と初期化が正常に行われたことが確認できます。

2. **警告メッセージについて**:
– `WARNING: All log messages before absl::InitializeLog() is called are written to STDERR` といった警告は、Firestore API を使用する際に出力される一般的なメッセージです。接続や操作には影響がないため、無視して構いません。

3. **データの取得**:
– 実行結果で `get_messages()` 関数が特定のデータを表示していない場合、これは `messages` コレクションが空であることを示しています。データがあれば、`doc.id => doc.to_dict()` の形式で出力されます。
– Firestore にデータが入っているか確認したい場合は、Firestore Console から `messages` コレクションにサンプルデータを追加して、再度コードを実行してください。

### 結論
この表示結果から判断すると、**問題なく Firestore に接続できている**ため、設定や接続には問題がありません。

ということなので問題はない

次にDBの作成

これをchat GPTで調べる
以下は回答

Firestore を利用するためには、**DB(データベース)の作成**が必要です。
以下の手順で、Firestore データベースを作成し、利用できる状態に設定してください。

### Firestore データベースの作成手順

1. **Firebase Console にアクセス**:
– [Firebase Console](https://console.firebase.google.com/) でプロジェクトに移動します。

2. **Firestore のセットアップ**:
– 左側メニューで「Firestore Database」を選択し、「データベースの作成」をクリックします。

3. **データベースモードの選択**:
– 「Firestore ネイティブモード」を選択することをおすすめします。

4. **リージョンの選択**:
– データの保存場所(リージョン)を選択します。日本の場合は、`asia-northeast1`(東京)を選択するのが一般的です。

5. **Firestore セキュリティルールの設定**:
– **本番環境ルール**(`allow read, write: if false;`)を選択してアクセスを制限し、必要に応じてルールをカスタマイズしてください。
– ルールは Firebase Console の「ルール」タブから後で編集できます。

### `messages` コレクションの準備

コードが正しく動作するようにするため、次の準備が必要です。

1. **`messages` コレクションの作成**:
– Firestore Console で「コレクションを開始」ボタンをクリックし、`messages` という名前のコレクションを作成します。

2. **ドキュメントの追加**:
– `messages` コレクション内にドキュメントを追加し、次のフィールドを含めます。
– `user_id`: メッセージを送信したユーザーの ID
– `text`: メッセージの内容
– `timestamp`: メッセージのタイムスタンプ(Firestore のタイムスタンプ形式を利用)

### 注意点

– Firestore はスキーマレスのデータベースなので、フィールド名とデータ型が一致していれば自由にデータを追加できますが、コードとデータ構造が一致するように注意してください。
– セキュリティルールの設定を適切に行い、本番環境でのアクセス制限を確保してください。

これで、Firestore データベースの準備が整い、コードが正しく動作するはずです。

とのこと

次に
FireStoreの管理画面から
DBの default を選択し
コレクションを開始
をクリック

messages という名前のコレクションを作成するため
コレクションIDを
messages
にする

最初のドキュメントの追加で
messages コレクション内にドキュメントを追加し、次のフィールドを含めます。
* user_id: メッセージを送信したユーザーの ID
* text: メッセージの内容
* timestamp: メッセージのタイムスタンプ(Firestore のタイムスタンプ形式を利用)

なおドキュメントIDが自動で振られるけど問題ないらしい

GPTで調べたら

Firestore でドキュメント ID が自動で振られるのは、通常問題ありません。
Firestore はドキュメントごとにユニークな ID を自動生成する機能を持っており、
ほとんどのケースでこの自動生成された ID をそのまま使用して問題ありません。

### 自動生成されたドキュメント ID が問題ない理由

1. **ユニーク性の保証**: Firestore の自動生成された ID は、
各ドキュメントがユニークになるように設計されています。
同じコレクション内で ID が重複することはありません。

2. **スケーラビリティとパフォーマンス**:
自動生成された ID は Firestore に最適化されており、大量のデータ操作にもスケーラブルです。

3. **データ構造の柔軟性**:
自動生成された ID を使用しても、クエリやアクセス制御に影響はありません。
フィールド `user_id` などを使ったフィルタリングも可能です。

### ただし、カスタム ID を使用したほうが良い場合

もし、特定のルールに基づいてドキュメントを一意に識別したい場合
(例: ユーザーごとにユニークな ID が必要な場合)には、
カスタム ID を設定することも可能です。
しかし、自動生成された ID で問題がない限り、そのままで問題ありません。

### 結論

自動生成されたドキュメント ID は Firestore の一般的な設計に適しているため、そのまま利用して問題ありません。

次にフィールドの設定をする
これは後で変更可能なので
1. user_id
* フィールド名: user_id
* フィールドタイプ: String(文字列)
* フィールド値: メッセージを送信したユーザーの ID(例: “user123″)
* 例: ユーザーごとのユニークな ID。LINEbot の場合、LINE ユーザー ID などが適しています。
2. text
* フィールド名: text
* フィールドタイプ: String(文字列)
* フィールド値: メッセージの内容(例: “こんにちは”)
* 例: メッセージのテキスト内容を保存します。
3. timestamp
* フィールド名: timestamp
* フィールドタイプ: Timestamp(タイムスタンプ)
* フィールド値: メッセージの送信日時(Firestore のタイムスタンプ形式)
*
とした

なお本番環境では
ユーザIDを変える

次は特定ユーザのメッセージを取得する

Firestore データベースの作成

Firestore データベースの作成

Google Cloud Console
へアクセス
「Firestore の開始」または「データベースの作成」ボタンをクリック

Firestore モード」を選択
この時
ネイティブ モード
Datastore モード
の2つがある

APIを使うということで
ネイティブモードを選択

このデータベースには無料枠の割り当てがあります。
各プロジェクトには「(default)」という名前の単一のデータベースがあり、
無料枠割り当ての対象になります。
この割り当てを使い切ると、オペレーション、ストレージ、ネットワークの使用量に基づいて課金されます¹。
ロケーションは料金に影響します

とのこと

(default)
がデータベースIDになっている

次にロケーション

ロケーション タイプには

リージョン
99.99% の可用性 SLA、単一リージョン内で比較的低いレイテンシ

マルチリージョン
99.999% の可用性 SLA、最大の領域にわたり最も高い可用性

コストの違いをGPTで調べたらマルチにすると倍になるらしいので
リージョンを選択する

次にリージョンの設定をする

Firebase のセットアップ画面でリージョンを選ぶ際に、
「asia-northeast1(東京)」を選択すると、日本国内向けに最適なパフォーマンスが得られるらしい

選択したリージョンがユーザーに近いほど、レイテンシが低くなります

とあるので調べた

レイテンシ(Latency) は、ユーザーがデータのリクエストを送ってから、
そのデータが返ってくるまでの遅延時間のことを指します。

具体的には、アプリケーションやサービスに対してユーザーが操作した際に、
その応答が表示されるまでの時間差です。

レイテンシは「応答時間」とも呼ばれることがあり、通常ミリ秒(ms)単位で測定されます。

レイテンシの例
たとえば、LINEbot が Firestore に保存されたメッセージを取得してユーザーに表示する場合、
レイテンシが低いと、メッセージの取得がスムーズで、
ユーザーは素早く応答を受け取ることができます。
しかし、レイテンシが高いと、データの取得や表示が遅れてしまい、ユーザーにとって待ち時間が長く感じられます。

レイテンシが重要な理由
* ユーザー体験の向上: レイテンシが低いほど、アプリの操作がスムーズで、ユーザーは快適に利用できます。
* リアルタイムアプリに重要: リアルタイムでの更新が必要なアプリ
(例: チャットアプリやオンラインゲーム)では、レイテンシが低いことが特に重要です。

どのようにレイテンシを低くするか?
* 近くのリージョンを選択することで、データのリクエストが物理的に近い場所で処理されるため、
レイテンシが低くなります。
* ネットワーク最適化やキャッシュの活用などもレイテンシの低減に役立ちます。
例えば、日本のユーザーが多い場合は、データベースのリージョンを asia-northeast1(東京)にすると、
データの取得時間が短くなり、レイテンシが低くなります。

次に暗号化

「Google が管理する暗号鍵」を選ぶ理由
1. 管理がシンプル: 鍵の生成・ローテーション・バックアップなどがすべて
Google によって自動的に管理されるため、暗号鍵の管理負担が軽減されます。

2. 追加コストなし: Google 管理の暗号鍵はデフォルトで提供されており、追加料金がかかりません。

3. 標準的なセキュリティ: 多くのプロジェクトでは、Google が管理する暗号化で十分なセキュリティが提供されます。

なので
デフォルトのままでOK

次に
セキュリティ ルール
セキュリティ ルールはアクセス制御とデータ検証を提供します。
2 つのプリセット ルールのいずれかから始めて、後でカスタマイズしてください。

本番環境ルール
データはデフォルトで限定公開になります。
モバイルおよびウェブ クライアント ライブラリ アクセスは、
セキュリティ ルールで指定された内容に従ってのみ許可されます。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}


rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

サードパーティによる読み取りと書き取りはすべて拒否されます

テストルール
データはデフォルトでオープン状態になります。
ただし、長期にわたりモバイルおよびウェブ クライアント ライブラリ アクセスを有効にするには、
30 日以内にセキュリティ ルールを更新する必要があります。

これについては本番環境を選択

なお
firebase第1世代だけど rules_version = ‘2’; でOKか気になったので調べる

chatGPTの回答は
**Firebase 第1世代でも `rules_version = ‘2’;` を使用して問題ありません**。

`rules_version = ‘2’` は、最新のセキュリティルール構文で、
Firestore の第1世代と第2世代の両方で利用可能です。
このバージョンでは、より細かいアクセス制御やデータ検証の機能が提供されており、セキュリティルールの記述がシンプルかつ強力になります。

そのため、第1世代の Firebase プロジェクトでも `rules_version = ‘2’;` を指定してルールを作成することを推奨します。

って答えだけどGPTなのでとりあえず試すことにする
全面的な信頼はせず自分で検証も重要
たまに存在しないバージョン指定とかもするので

とりあえずこのルールでDBを作成する

Ollama index

Ollama index チュートリアルを参考に

https://llamahub.ai/

https://docs.llamaindex.ai/en/stable/getting_started/starter_example_local/
のチュートリアルを参考に
MacBook Air で実験

Custom install の
https://docs.llamaindex.ai/en/stable/getting_started/installation/
を参考に

pip install llama-index

If you aren’t using OpenAI, or want a more selective installation, you can install individual packages as needed.
For example, for a local setup with Ollama and HuggingFace embeddings, the installation might look like:

もしOpenAIを使用しない場合や、特定のパッケージのみを選んでインストールしたい場合、必要なパッケージだけを個別にインストールすることができます。
例えば、ローカル環境でOllamaとHuggingFaceの埋め込み機能を使用したセットアップを行う場合、以下のコマンドでインストール

pip install llama-index-core llama-index-readers-file llama-index-llms-ollama llama-index-embeddings-huggingface

これでインストールはOK

次にollama index

pip install llama-index-llms-ollama

でインストール

Ollamaを使ってLlamaIndexでRAGを構築する【Windows 11 + WSL 2 + JupyterLab】
を参考に
Ollama index を使えるようにする

ELYZA-japanese-Llama-2-7b で LlamaIndex を 試す
も参考に

PDFからの情報抽出とクエリ応答を試す

厚生労働省が公開しているモデル就業規則を使う

mkdir -p data/10k/ 
wget https://www.mhlw.go.jp/content/001018385.pdf -O data/10k/001018385.pdf 

これで
ーOオプションをつけて保存先を指定

保存したら
これを
Data/10/フォルダに移動

 touch test.py

でファイルを作成

from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from transformers import AutoTokenizer

# 使用モデルを選択します。
llm = Ollama(model="elyza:jp8b", request_timeout=40.0)

# HuggingFace Embedding モデルの初期化
embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")

# Transformers tokenizerの設定
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2", clean_up_tokenization_spaces=False)

# PDFファイルの読み込み
reader = SimpleDirectoryReader(input_files=["./data/001018385.pdf"])
data = reader.load_data()
index = VectorStoreIndex.from_documents(data, embed_model=embed_model)

# クエリエンジンの初期化
query_engine = index.as_query_engine(llm=llm, streaming=True, similarity_top_k=3)

# クエリの実行
response = query_engine.query("就業規則では休みは何日もらえますか?日本語で答えて。")
response.print_response_stream()

# ソースノードの確認
for node in response.source_nodes:
    print(f"Text: {node.node.text[:100]}...")
    print(f"Similarity score: {node.score}")  # 'score'を使用

これを実行すると

/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/transformers/tokenization_utils_base.py:1601: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted in transformers v4.45, and will be then set to `False` by default. For more details check this issue: https://github.com/huggingface/transformers/issues/31884
  warnings.warn(

記載されている就業規則より、休息時間の情報が見つかりました。そこには「いかなる場合も、従業員ごとに1日の勤務終了後、次の勤務の開始までに少なくとも、○時間の継続した休息時間を与える」とあります。具体的な日数は記載されていませんが、休息時間が一定の時間保証されていることがわかります。Text: - 65 - 
 ません。しかし、賞与を支給する場合、就業規則に支給対象時期、賞与の算定基準、査
定期間、支払方法等を明確にしておくことが必要です。 
2 就業規則に、賞与の支給対象者を一定の日(例え...
Similarity score: 0.26476020388558674
Text: [例1] インターバル時間と翌日の所定労働時間が重複する部分を働いたものとみなす
場合 
 
(勤務間インターバル) 
第22条  いかなる場合も、従業員ごとに1日の勤務終了後、次の勤務の開始までに少...
Similarity score: 0.2292672424458453
Text: なお、1か月60時間の算定には、法定休日に労働した時間数は含まれませんが、法
定外の休日に行った労働における時間外労働の時間数は含まれます。...
Similarity score: 0.2239520151706951

となる

参考サイトでは

Based on the provided context information, the answer to the query is:

就業規則では休みは3日もらえます。(65)

Just like how the law specifies that an employee is entitled to a maximum of 3 days of rest in a row, it also states that an employee must be given at least 24 hours of continuous rest time after each day of work. However, this rule does not apply in cases of natural disasters or other unavoidable circumstances. (36)-----
Text:	 なお、1か月60時間の算定には、法定休日に労働した時間数は含まれませんが、法 定外の休日に行った労働における時間外労働の時間数は含まれます。 ...
Metadata:	 {'page_label': '55', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.321
-----
Text:	 - 65 -   ません。しかし、賞与を支給する場合、就業規則に支給対象時期、賞与の算定基準、査 定期間、支払方法等を明確にしておくことが必要です。  2 就業規則に、賞与の支給対象者を一定の日(例えば、6月1日や12月1日、又は賞 与支給日)に在籍した者とする規定を設けることで、期間の途中で退職等し、その日に 在職しない者には支給しないこととすることも可能です。 ...
Metadata:	 {'page_label': '65', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.306
-----
Text:	 [例1] インターバル時間と翌日の所定労働時間が重複する部分を働いたものとみなす 場合    (勤務間インターバル)  第22条  いかなる場合も、従業員ごとに1日の勤務終了後、次の勤務の開始までに少 なくとも、○時間の継続した休息時間を与える。ただし、災害その他避けることがで きない場合は、この限りではない。 ...
Metadata:	 {'page_label': '36', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.306

となっているが
PDFを見ても就労規則で休みの項目が見当たらないので
ファイルが更新されているのかもしれない

次に
テキストファイルからの情報抽出とクエリ応答

あらすじ
結束バンド
後藤ひとりは友達を作れない陰キャでいつも一人で過ごしていたが、中学時代にテレビのインタビューを見て、陰キャでもバンドを組んでいれば人気者になれると聞き、ギターの練習を始める。ひとりは毎日コツコツ練習を重ねることでギターの腕前はかなりのものとなっていたが、気がつけば中学は卒業。結局、友達を作るという当初の目標は達成することはできずじまいだった。そして高校に入学したひとりはギター演奏を動画配信し、「ギターヒーロー」としてネットでそこそこの人気を集めていたが、現実では変わらず友達を作れずにいた。ひとりはギターをこれみよがしに持ち、ギタリストだということをアピールするものの効果はなし。黄昏(たそがれ)ていたところ、ギタリストを探していた伊地知虹夏に誘われ、バンド活動をすることとなる。ひとりは虹夏、山田リョウらと共に「結束バンド」として活動を始めるが、友達は欲しいけどコミュ症独りぼっちのひとりは問題行動ばかり起こしていた。結束バンドなのに、結束力ゼロな凸凹バンド活動は幕開けするのだった。そしてそんなひとりに、クラスメイトの喜多郁代が話しかけてきて、彼女が結束バンドの元メンバーだったことを知る。紆余(うよ)曲折の末、郁代はひとりとの交流で結束バンドに戻ることになり、ひとりたちはライブに向けて準備をすることとなる。ひとりはライブチケットのノルマをこなすため、方々を回ってチケットを売ることになるが、人見知りのひとりには難題ですぐに壁にぶち当たってしまう。絶望するひとりは、そこで行き倒れた廣井きくりと遭遇。彼女の計らいで突発的な路上ライブを行なって、観客を魅了する。きくりのお陰でライブチケットのノルマもこなし、結束バンドはライブに臨むのだった。

文化祭ライブ
夏休みに入り、後藤ひとりは知り合いも増えていたが、自分から遊びに誘うことができずに夏休みも終わり、新学期を迎えていた。相も変わらずコミュ症なひとりは文化祭で盛り上がるクラスメイトをよそに、一人黄昏ていた。そんな中、みんなの前で結束バンドのライブをすればクラスで人気者になれるかもと考えたひとりは、喜多郁代の後押しもあり、ライブをすることを決める。文化祭ライブにひとりは大きな不安を抱くが、周囲の励ましと協力によって、ついに文化祭当日を迎える。しかし、ライブ中に機材トラブルが発生し、ひとりはとっさにボトルネック奏法のアドリブを行い、仲間たちはそのフォローに回る。その演奏をみんなから拍手喝采されるひとりだったが、コミュ症のひとりはその晴れ舞台に混乱し、観客席にダイブするという暴挙に出てしまう。ひとまずライブは成功を収めるものの、ひとりの奇行により、結束バンドの名はフリーライターの佐藤愛子の耳に入ることとなる。そして愛子が結束バンドを取材した際に、ひとりが「ギターヒーロー」であることがバレてしまう。愛子はひとりに強い関心を抱くが、結束バンドの演奏に対しては辛らつで、彼女たちを酷評する。そしてひとりたちは、愛子を見返すため、10代限定のロックフェス「未確認ライオット」に参加し、グランプリを目指すことを決める。

デモ審査
未確認ライオットに参加するためには、デモ審査、ウェブ投票、ライブ審査を通過して、最終審査に合格する必要があった。そして結束バンドは、デモ審査のためのMV(ミュージックビデオ)作成を始める。そんな中、結束バンドの面々は、フェスではライバルとなる人気メタルバンド「SIDEROS」に所属する大槻ヨヨコと出会う。ひねくれ者なヨヨコは、結束バンドをライバル視しながらも、彼女たちにアドバイスを送り、ファンたちの助けもあってMVを完成させる。デモ審査の結果が出るのを待つばかりとなり、新たな春を迎える。ひとりたちは無事進級するものの、相も変わらずコミュ症のひとりは奇行が目立っていた。それでも喜多郁代の助けもあり、ひとりは新たにクラスメイトとなった佐々木次子と友人となる。今までのライブ活動が実りはじめ、徐々に活動範囲が広がっていくひとりたちだったが、そこにライブ出演の依頼が舞い込む。初の依頼にワクワクする結束バンドの面々だったが、イベント会場は音楽ジャンルがバラバラのカオス状態。運営サイドがスケジュールを埋めるためだけに、適当にバンドをブッキングされたことが判明する。アウェーな会場に気圧される結束バンドだったが、それぞれが最善を尽くすことでライブは大盛り上がりのうちに終了し、大成功を収める。そして着実に実力をつけた結束バンドのもとに、デモ審査通過の知らせが届くのだった。




メディアミックス
テレビアニメ
2022年テレビアニメ化。2022年10月8日よりTOKYO MX、BS11ほかにて放送。制作はCloverWorks。キャストは、後藤ひとりを青山吉能、伊地知虹夏を鈴代紗弓、山田リョウを水野朔、喜多郁代を長谷川育美が担当している。

劇場版アニメ
テレビアニメの総集編が劇場アニメ化。2024年6月7日に前編『劇場総集編ぼっち・ざ・ろっく! Re:』、8月9日に後編『劇場総集編ぼっち・ざ・ろっく! Re:Re:』が公開。



登場人物・キャラクター
 後藤 ひとり (ごとう ひとり)
秀華高校に通う女子。桃色の髪を無造作に伸ばし、いつもジャージを身につけている。自他共に認める引きこもり一歩手前の「陰キャ」で、承認欲求が人一倍強いにもかかわらず、臆病な性格で人と接するのを極度に苦手としている。そのため、すぐに自分の世界に入って落ち込むという情緒不安定さを見せる。押し入れやダンボールに潜り込む癖があり、「完熟マンゴー仮面」を持ち歩いている。運動も勉強も苦手で、特に勉強はまじめに授業を受けているにもかかわらず、生来の要領の悪さから赤点ギリギリ。自分には何の取り柄もないのを痛感していたため、中学の頃に暗い性格の人間がバンドをやって人気者になったインタビューを読んで、ギターを始める。毎日練習したお陰でギターの腕前はプロ級になったが、結局、その腕前を披露する場に恵まれず、友達を作れないまま中学を卒業。現在は「ギターヒーロー」の名で動画配信している。ギタリストを探していた伊地知虹夏に誘われ、「結束バンド」に加入し、バンド活動を始める。バンド内ではギタリスト兼作詞を担当している。山田リョウに「ぼっち」の愛称を付けられて以降は、その愛称で呼ばれる。ギターの腕前はかなりのものだが、他人と合わせるセッションの経験が皆無なため、バンドではまともに演奏できないでいる。しかし徐々にバンドとして演奏できるようになり、文化祭ライブでは機材トラブルというアクシデントに見舞われながらも、アドリブでボトルネック奏法を行い、ライブを成功に導いた。なお、父親から借りたギターは文化祭で壊れたため、動画配信の広告収入で得たお金を使って、新たなギターを手に入れた。人見知りなため、美容院にもいけず、髪の毛を伸ばし放題で前髪で目をつねに隠している。それに加えて野暮ったいジャージ姿でいるため気づかれていないが、実は同性すら見とれるほどの美少女。黙って着飾っていれば「アイドル事務所に入れる」「ビジュアル担当」と言われるほどのポテンシャルを秘めているが、キメ顔は10秒しか持たず、ふだんの奇行がすべてを台無しにしている。

 伊地知 虹夏 (いじち にじか)
下北沢高校に通う女子。後藤ひとりより1学年上。ライトイエロー色の髪をサイドテールにセットし、派手目のファッションを好んで着ている。幼い頃に母親と死別しており、父親も多忙なためさびしい日々を送っていた。姉の伊地知星歌の影響で、バンド活動にあこがれるようになり、「結束バンド」を結成する。バンドではドラムを担当している。姉がバンドをやめてライブハウスを始めたのも、自分の応援をするためであることに薄々気づいており、バンドで大成功を収めて姉のライブハウスを有名にするのを夢見ている。明るく面倒見のよい性格をしており、初対面の人とも仲よくなれる人当りのよさを持つ。また、生活能力が皆無な山田リョウの面倒を見たり、コミュ障の後藤ひとりにも根気強く付き合ったりしている。ギターヒーローの大ファンで、当初はひとりがギターヒーローであることに気づかなかったが、8月のライブでひとりの演奏する姿を見て、その正体に気づく。のちに佐藤愛子にひとりの正体を暴露されるまでは、ひとりの正体を知る唯一のメンバーだった。ライブでは緊張からライブ後に毎回倒れるひとりの運搬役も担当しているため、知らず間に体力と筋肉が付いてきており、最近は手慣れた手付きでトランクにひとりを詰めて運んでいるが、周囲からは死体を運んでいるように見えるため怖がられている。また、ほかのメンバーに比べて自分が「地味」なことにコンプレックスを抱き、ひそかに筋肉キャラで目立とうと考えている。

 山田 リョウ (やまだ りょう)
下北沢高校に通う女子。後藤ひとりより1学年上。中性的な雰囲気を漂わせた麗人で、青色の髪をボブにセットしている。「結束バンド」のメンバーの一人で、ベースと作曲を担当している。飄々(ひょうひょう)としたマイペースな性格で、「変人」と呼ばれると喜ぶ。趣味は廃墟探索と古着屋巡り。変わり者で一人でいるのが好きだが、後藤ひとりと違って特にコミュ障というわけではない。音楽に関しては独自の価値観を持っており、流行(はや)りに流されるのを嫌い、バンドの個性を重視している。そのため過去に別のバンドに所属していたが、人気が出るにつれて売れ筋の曲ばかりを作るようになったバンドに嫌気が差して脱退し、伊地知虹夏に誘われて結束バンドに加入した経緯がある。バンド経験があるため、演奏技術などは結束バンドの中でも群を抜いて高い。楽器類に小遣いを注ぎ込んでいるためいつも金欠で、食費にすら事欠くことも多い。また計画性も皆無で、新しい楽器を買うため、今の楽器を売り飛ばしたりもしている。ミステリアスな雰囲気を漂わせているため、周囲は勉強ができると思っていたが、実は相当なバカ。高校生にもかかわらず学力は中学生レベルにも及ばず、成績はつねに赤点。ただ天才肌で要領がいいため、高校受験の際は一夜漬けで乗り切った。しかし勉強にのめり込み過ぎると、今度は音楽関係が疎(おろそ)かになるという欠点がある。実家は病院を経営しており、かなりの資産家。両親から溺愛されているが、そんな両親の過保護に反発して、ロックを始めた経緯がある。

 喜多 郁代 (きた いくよ)
秀華高校に通う女子。後藤ひとりとは同学年。ウェーブの入った赤い髪をセミロングに伸ばした美少女で、社交的な性格をしている。いわゆる「陽キャ」で、ひとりとは正反対で要領がよく、友達も多い。一方でなんでも無難にこなせるゆえに特技といったものもなく、自分を平凡な人間と思っているのがコンプレックスとなっている。「郁代」という名前が気に入らず、周囲には名字で呼ぶようにお願いしている。山田リョウを慕っており、彼女に近づくためギターが弾けるとウソをついて、結束バンドに加入した。しかし、土壇場で怖くなって逃げ出しており、そのまま音信不通になっていた。ひとりがギターを弾いている姿を見て、彼女と交流を持ち、ひとりの取り成しで結束バンドに出戻った。バンドではギター兼ボーカルを担当する。実はギターと間違えてベースを買ってしまい、そのせいでいくら練習しても上達しなかったほどの音楽オンチ。歌も上手(うま)いが、カラオケ的な上手さのため歌唱技術そのものはない。しかしひたむきな努力家でもあるため、自らの欠点に気づいてからは、練習を重ねていた。人と接するのが大好きで、自分のSNSはフォロワーが1万5000人以上いる。またその社交性を買われて結束バンドの公式SNSも担当する。喜多郁代の手腕もあって公式SNSは着実に人気を集めているが、美容の話ばっかりしていたせいで、フォロワーからは音楽系アカウント思われず、ライブの告知をした際にはフォロワーから化粧の実演販売をするとカンちがいされていた。2年生ではひとりと違うクラスだったが、3年生ではクラスメイトとなる。ひとりの奇行に頭を悩ませながらも、彼女のフォロー役を担っている。

 廣井 きくり (ひろい きくり)
実力派サイケデリックロックバンド「SICK HACK(シックハック)」に所属する女性。ベース兼ボーカルを担当している。長く伸ばしたあずき色の髪を三つ編みにしている。いつも酔っぱらっている変人で、行き倒れていたところを後藤ひとりに介抱され、彼女と知り合う。新宿ではカリスマ的な人気を誇り、ひとりと路上ライブを行なって、彼女の行く道を指し示した。ひとりの持つポテンシャルに気づいており、彼女が上のレベルに上がってくるのを期待している。実は高校時代はかなり根暗な性格をしており、自分を変えるためバンドを始めたため、ひとりにシンパシーを抱いている。飲酒を始めたのも緊張から逃れるためだったが、現在はそれが行き過ぎて四六時中酔っぱらっている。バンドも人気があり、チケットノルマも余裕で達成し、グッズ販売も好調なためバンド活動だけで生活している。しかしライブ中も酔っぱらって大暴れするため、儲(もう)けは壊した機材の修理費で消えてしまい、いつも生活は困窮している。伊地知星歌は高校時代の先輩で、結束バンドのライブを見て以降はSTARRYに入り浸っている。風呂なしアパートに住んでいるため、STARRYのシャワーを使ったり、タダ飯にありつこうとしているため、星歌からは迷惑がられている。愛用のベースに「スーパーウルトラ酒呑童子EX」と名前を付けて大事にしているが、しょっちゅうどこかに置き忘れている。

 後藤 ふたり (ごとう ふたり)
後藤ひとりの妹。年齢は5歳。姉とは正反対の天真爛漫(らんまん)で無邪気な性格をしている。愛犬のジミヘンと仲がよく、いつもいっしょに遊んでいる。まだ幼いためよくも悪くも素直で、その言動で姉を傷つけたりしている。ナチュラルに姉のことを面倒くさいと思っているが、ギターの腕前は尊敬しており、結束バンドのライブを見た際には姉のことをかっこいいと感じていた。その後、ライブの影響でひとりに教わりながらギターを始めるが、すぐに飽きてしまう。ひとりと後藤ふたりの父によれば、ふたりは家族の中にヒエラルキーを作っており、父親が最下位である。

 清水 イライザ (しみず いらいざ)
バンド「SICK HACK」に所属する女性。ギターを担当している。金髪を長く伸ばした髪型が特徴。イギリス出身で、18歳までイギリスで暮らしていたため、片言の日本語でしゃべる。日本のアニメや漫画が大好きで、「コミマ」に参加するために3年前に来日し、音楽活動を始めた。ギターのテクニックは確かで、その腕前は感情的でありながらロジカルと評されている。ただし、清水イライザとしてはロックバンドよりも、アニソンのコピーバンドをやりたがっている。また、同人作家としても活動している。

 後藤 美智代 (ごとう みちよ)
後藤ひとりの母親。二児の母だが若々しく、言動が明るくノリも軽い。ひとりのことを何かと心配しており、ジャージばかり着ている彼女の服などを買ってきている。ひとりが喜多郁代を家に連れてきた際には大喜びし、謎の行動力を発揮。娘のサポートをするために制服を着て、女子高生になり切って会話に入ろうとした。

 内田 幽々 (うちだ ゆゆ)
メタルバンド「SIDEROS(シデロス)」に所属する少女。ベースを担当している。黒い髪をストレートに伸ばし、口元にホクロがあるのが特徴。

 岩下 志麻 (いわした しま)
バンド「SICK HACK」に所属する女性。ドラムを担当している。髪をボブにした中性的な風貌をしており、ファンからは「志麻さま」と呼ばれている。バンドの中ではまじめな性格の常識人で、酔っぱらって大暴れする廣井きくりのフォローのために謝罪行脚している。ライブでも変拍子に対応したテクニックの高さを見せつけ、バンドをうまくまとめている。喜多郁代はSICK HACKの中では、岩下志麻が一番まともだと思っているが、実は怒ったらめちゃくちゃ怖いらしく、きくりが暴れた後始末のためにライブ後は毎回怒り狂っている。ドラムも、感情のまま叩いていたら向上したようで、きくりは自分のおかげと胸を張っているが、まったく関係ない。

 長谷川 あくび (はせがわ あくび)
メタルバンド「SIDEROS(シデロス)」に所属する少女。ドラムを担当している。いつも口元を黒いマスクで隠している。一人称が「自分」で、語尾に「っす」を付けるしゃべり方をするのが特徴。社交的な性格の常識人で、バンド内では大槻ヨヨコへのツッコミ兼フォローも担っている。ヨヨコを「先輩」と呼び慕っており、ヨヨコとは以心伝心の関係で、彼女のわずかな表情の変化から状態を見極めて的確なツッコミを入れている。結束バンドに対しても、同年代のバンドで親近感を感じており、彼女たちの曲を気に入っている。毎回ライブをめちゃくちゃする廣井きくりに対しては憤りを感じており、彼女が一時期FOLTから姿を消した際には平和を満喫していた。

 佐々木 次子 (ささき つぐこ)
秀華高校に通う3年生の女子。進級した後藤ひとりとクラスメートとなる。愛称は「さっつー」。喜多郁代とは中学時代からの友人関係で、彼女を通してひとりとも知り合う。ヒップホップが大好きで、基本的にヒップホップ以外の曲には興味がない。そのため、郁代のバンドの話にもあまり興味を示さないが、文化祭ライブでひとりの演奏のすごさは目の当たりにしており、ひとりのことは印象に残っていた。

 大槻 ヨヨコ (おおつき よよこ)
人気メタルバンド「SIDEROS(シデロス)」のリーダーを務める少女。ギター兼ボーカルを担当している。ライブハウス「FOLT」で活動しており、廣井きくりを純粋に慕っている数少ない人物の一人。小心者なのに意地っ張りという難儀な性格をしており、弱い自分を見せまいとつい強がりをしてしまう。コミュニケーションを取るのも下手で敵をつくりがちであるため、友達もおらず、バンドメンバーも入れ替わりが激しい。きくりはそんな自分にも構ってくれるために慕っているが、飯をたかられたり、お金を貸したり、便利に使われていることに気づいていない。向上心は旺盛であるため、努力して現在の地位にまで上り詰めたが、未だにライブ前は緊張して眠れなくなるほどあがり症。そのせいで三日寝ずに過ごすこともあるほどで、寝不足のせいでコミュニケーションが疎かになるという悪循環に陥っている。ただし、その緊張さがいい意味でバンドメンバーの緊張を解している副次的効果がある。結束バンドの曲を聞いて、彼女たちのことを気に掛けるようになる。頑なに認めようとはしないが、そのさまはファンそのもので、改善点をまとめて彼女たちに教えるなどマネージャー的な役割を担っている。

 佐藤 愛子 (さとう あいこ)
フリーライターとして活動する女性。ペンネームは「ぽいずん♥やみ」。年齢は23歳だが、童顔で小柄な体型をしているため、中学生でも通る幼い容姿をしている。佐藤愛子自身も幼い容姿は自覚しており、「14歳」と年齢を詐称したこともある。黒い髪をツインアップテールにしている。痛ロリ系のファッションに身を包んでおり、自分でも自分の格好を「痛い奴」と認識している。いいバンドをもっと多くの人に知ってもらいたいとの思いから、音楽系の記事を扱うサイト「ばんらぼ」の運営会社に所属し、記事を書いている。しかし大手のバンドに取材するツテも、音楽雑誌の編集者に就職するツテもないため、最近はアクセス数稼ぎのための記事しか書いておらず辟易している。後藤ひとりの文化祭の奇行が話題となり、その取材のために結束バンドを取材する。その際に、彼女たちの演奏する姿を見て、ひとりがギターヒーローであることに気づき、それをバンドメンバーに暴露した。ギターヒーローのファンだが、結束バンドのバンド演奏は「ガチじゃない」と酷評し、ひとりにバンドを抜けて一人で活動するように勧めた。この行動が結束バンドのメンバーを奮起させ、フェスに参加させるきっかけをつくった。言葉は厳しいが、結束バンドへの評価は的を得ており、ふだんの言動は痛いが音楽に関することには真摯に向き合っている。その後も結束バンドのことは何かと気に掛けており、彼女たちが着々と実力を付けていっているのも認めている。

 ひとりの父 (ひとりのちち)
後藤ひとりの父親。本名は不明で、素顔もなぜか毎回明かされていないために不明。若い頃、ギターを弾いていたらしく、ひとりの中学時代、ギターに興味を覚えた娘に自分のギターを貸した。動画配信のアカウントを家族で共有しているため、ひとりが「ギターヒーロー」として人気を集めていることも知っており、ひとりのために広告収入を貯金していた。のちに、そのお金を新しいギターを購入する資金として渡している。変な特技を数多く持っており、隠れ身の術で壁に隠れていたり、完熟マンゴー仮面を凝ったデザインに改造したりしている。後藤ふたりから家族ヒエラルキーの中で最下位に置かれており、なんとかそれを挽回したいと考えている。

 2号 (にごう)
美術大学の映像学科に在籍する女子。髪をミディアムヘアに整えている。「結束バンド」のファン「2号」を名乗っており、1号とは友人同士で、よく二人で行動している。後藤ひとりと廣井きくりの路上ライブを見て、彼女たちのことを知り、ファンとなった。路上ライブの際にひとりが緊張しているのを心配して応援の言葉を投げかけたが、これが結果的に彼女が観客と向き合う大きなきっかけとなっている。映像制作の勉強をしているため、結束バンドがMVを制作する際に伊地知虹夏から依頼され、1号と共にMV制作を手伝った。

 本城 楓子 (ほんじょう ふうこ)
メタルバンド「SIDEROS(シデロス)」に所属する少女。ギターを担当している。髪をロングに伸ばし、明るい雰囲気を漂わせている。人当たりがよく、同年代の友達は「ちゃん」付けで呼ぶ。大槻ヨヨコのことは「先輩」と呼び慕っているが、一方でコミュニケーション下手なのを面倒くさいとも思っている。

 吉田 銀次郎 (よしだ ぎんじろう)
ライブハウス「FOLT」の店長を務める男性。年齢は37歳。見た目は長髪強面でピアスをつけて目つきが鋭く、初対面の人物は思わず怯えるほどビジュアルが怖い。しかし「心が乙女なおっさん」らしく、女言葉で話すなど物腰は柔らかい。見た目と言動のギャップが激しく、周囲を混乱させることがある。好きな音楽のジャンルはパンクロック系。

 1号 (いちごう)
美術大学の映像学科に在籍する女子。セミロングヘアにしている。「結束バンド」のファン「1号」を名乗っており、2号とは友人同士で、よく二人で行動している。後藤ひとりと廣井きくりの路上ライブを見て、彼女たちのことを知り、ファンとなった。映像制作の勉強をしているため、結束バンドがMVを制作する際に伊地知虹夏から依頼され、2号と共にMV制作を手伝った。かなりの面食いで、かわいい女の子が大好き。ひとりのビジュアルがお気に入りで、彼女がキメ顔になったらテンションが上がるが、そのビジュアルが10秒しか持たなかったためにMVに使うのはあきらめ、ひとりの出番をほとんどカットした。

 PAさん (ぴーえーさん)
ライブハウス「STARRY」のPA(Public Address)エンジニアを務める女性。口ピアスをつけ、黒い髪をストレートロングにしている。伊地知虹夏たちと仲がよく、彼女たちには本名を名乗っているが、覚えられておらず「PAさん」と呼ばれている。ぱっと見はまじめな清楚系に見えるが、かなりいい加減な性格で、高校も朝起きれず1年で中退している。伊地知星歌とは同年代のようで、たまに彼女と独り身同士の闇を吐き出している。

 伊地知 星歌 (いじち せいか)
下北沢のライブハウス「STARRY(スターリー)」の店長を務める女性。誕生日を迎えて30歳になった。伊地知虹夏の姉。ライトイエロー色の髪をストレートロングに伸ばして、やさぐれた雰囲気を漂わせている。シビアな性格をしており、口が非常に悪い。しかし根は優しく、厳しいことを言うのも妹を気づかってのもの。また、かわいいもの好きで、ぬいぐるみがないと眠れないなど乙女な一面を持つ。自分では着ないものの、家にかわいらしい衣装を観賞用としてコレクションしており、メイド服や学校の制服をいくつも所持している。後藤ひとりのギターのテクニックと才能に興味を抱いている。また、ひとりの容姿がかわいらしいのを気に入っており、何かと気に掛けている。現在は解散しているが、過去にバンドをしており、ギターを担当していた。当時はかなり傍若無人な性格をしており、「御茶ノ水の魔王」と呼ばれていた。




その他キーワード
 完熟マンゴー仮面 (かんじゅくまんごーかめん)
後藤ひとりが引きこもるために使う段ボール箱。「完熟マンゴー」と書かれているため、「完熟マンゴー仮面」と呼ばれている。ひとりが人前に立つと緊張するため、人と接するときはおろか、ライブ演奏中でもスキあらばかぶろうとする。また、実はひとりの父が用意しているらしく、少しずつ進化しており、のちに人型ロボットのような外見へと変化している。ひとりは完熟マンゴー仮面がない場合は、ゴミ箱やトランクを代用品としてかぶってひきこもろうとする。

 ギターヒーロー
後藤ひとりが動画配信の際に用いるハンドルネーム。ひとりは「ギターヒーロー」の名で動画配信して、スゴ腕の女子高生ギタリストとしてカリスマ的な人気を集めている。動画配信は素顔を映さない形で行っており、ひとりがギターヒーローであることはほとんどの人たちが知らない。チャンネル登録者もかなり多いらしく、大槻ヨヨコによれば、ドームを2回満員にするほどにチャンネル登録者がいるという。ただ一方で、ひとりの承認欲求が肥大化した原因でもあり、ギターヒーローとしての活動コメントは、「彼氏がいる」「友達とカラオケ」など捏造がひどい。実はアカウントが家族で共有されているため、ひとりの父にはそれらの虚言癖は最初からバレており、のちにその事実に気づく。また佐藤愛子にひとりがギターヒーローであることを暴露されたことをきっかけに周囲の人間にもそれが知れ渡り、以降は激しく後悔してコメントを自粛している。



書誌情報
ぼっち・ざ・ろっく! 6巻 芳文社〈まんがタイムKRコミックス〉
第1巻 (2019-02-27発行、 978-4832270725)
第2巻 (2020-02-27発行、 978-4832271708)
第3巻 (2021-02-25発行、 978-4832272521)
第4巻 (2022-08-26発行、 978-4832273887)
第5巻 (2022-11-26発行、 978-4832274198)
第6巻 (2023-08-25発行、 978-4832274778)
SHARE

の内容を

bocchi.txt	

として保存

from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from transformers import AutoTokenizer

# 使用モデルを選択します。
llm = Ollama(model="elyza:jp8b", request_timeout=40.0)

# HuggingFace Embedding モデルの初期化
embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")

# Transformers tokenizerの設定
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2", clean_up_tokenization_spaces=True)

reader = SimpleDirectoryReader(input_files=["./data/10k/bocchi.txt"])
data = reader.load_data()
index = VectorStoreIndex.from_documents(data, embed_model=embed_model)


# クエリエンジンの初期化
query_engine = index.as_query_engine(llm=llm, streaming=True, similarity_top_k=3)

# ストリーミングでレスポンスを取得
response = query_engine.query(
    "文化祭ライブはどうだったのか**日本語**で簡潔に説明してください。 "
    " page reference after each statement."
)
response.print_response_stream()

test_txt.py

として保存

これを実行すると

/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/transformers/tokenization_utils_base.py:1601: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted in transformers v4.45, and will be then set to `False` by default. For more details check this issue: https://github.com/huggingface/transformers/issues/31884
  warnings.warn(
文化祭ライブでは、結束バンドが演奏し、機材トラブルが発生したものの、ひとりがとっさにボトルネック奏法のアドリブを行い、成功を収めた。%    

となる

とりあえず動くようになったので
他のファイルなどを使って実験してみる

LINEbot で特定のユーザーのメッセージの取得

LINEbot で特定のユーザーのメッセージの取得

LINEbotで特定のユーザのメッセージの取得をする
webhook には firebase第一世代を使用
使用しているのは node.js の18
この構成で LINEbot で特定のユーザーのメッセージの取得する python コード

これを行うには
Firebase プロジェクトが設定されており、
Firestore のアクセスに必要なクレデンシャル JSON ファイルがある

google-cloud-firestore パッケージがインストールされている
の条件を満たす必要がある

Firebase プロジェクトで
Firestore のアクセスに必要なクレデンシャル JSON ファイルが設定されているかを確認

まず
Firebase Console にアクセスし、対象のプロジェクトを選択

左側のメニューから 「プロジェクトの設定」 (Project settings) をクリック

「サービスアカウント」 (Service accounts) タブを開く

Admin SDK 構成スニペット
があるので調べる

Admin SDK 構成スニペットにはどれを選べば良い?
webhook には firebase第一世代を使用し 使用しているのは node.js の18の場合は?


GPTで調べると
Firebase 第一世代の webhook に Node.js 18 を使用している場合、
Firestore の管理者権限でアクセスするには、Admin SDK 構成スニペットとして Node.js を選択

となるので
Node.js のままで

var admin = require("firebase-admin");

var serviceAccount = require("path/to/serviceAccountKey.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

「新しい秘密鍵の生成」をクリックして、サービスアカウントの JSON クレデンシャルをダウンロード
ファイルをプロジェクトにコピーする

とりあえずJSONファイルは取得できたので
次に

pip install google-cloud-firestore


firesroreのセットアップ

しかしよく考えてみれば
node.jsは firebaseで動かしているが
m1macbookAir では動かしていない

違いがわからないため再度質問

Admin SDK 構成スニペットにはどれを選べば良い?
webhook には firebase第一世代を使用し 使用しているのは node.js の18の場合は?
の答えは node.js だった。
この場合動かす環境は firebaseを想定しているか?
それともm1macbookAir で node.js を想定しているのか?

答えは

この場合、**動かす環境は Firebase を想定**しています。
具体的には、Node.js 18 を使って Firebase Gen 1 環境上で webhook が動作する構成です。
したがって、Node.js 用の Firebase Admin SDK 構成スニペットを選択することになります。

この Node.js のスニペットは、Firebase サーバー側で
webhook として動作する Node.js 環境向けに設定されており、
M1 MacBook Air 上ではなく、
**Firebase のサーバー上で稼働する Node.js のコードに適用**されるものです。

一方で、**Python は MacBook Air 上で動作し、
Firestore からメッセージを取得するためのコード**として使われます。
そのため、Python の Firebase Admin SDK を MacBook Air 上で利用して
Firestore にアクセスする構成になります。

となった

このため再度Admin SDK 構成スニペットをpythonで取得する

import firebase_admin
from firebase_admin import credentials

cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)

ダウンロードできたら
でファイルをコピーする

なおこの構成スニペットで表示されているコードは
Python 用の Firebase Admin SDK を使用して、Firebase プロジェクトに接続するための初期設定を行うもの

このスクリプトによって、Python 環境から Firebase にアクセスできるようになる

serviceAccountKey.json
へファイル名を変更

これで

vim access.python

として

import firebase_admin
from firebase_admin import credentials

cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

として保存

実行したら

python access.json                
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/access.json", line 1, in <module>
    import firebase_admin
ModuleNotFoundError: No module named 'firebase_admin'

となった

pip install firebase-admin

でインストール

今度は実行しても何も表示されない

接続が正常に行われているか確認するために、
Firestore のデータベースからデータを取得したり、簡単なメッセージを表示させる
以下のようにして Firestore からデータを取得

import firebase_admin
from firebase_admin import credentials, firestore

# サービスアカウント JSON ファイルのパスに置き換える
cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)

# Firestore クライアントの取得
db = firestore.client()

# Firestore の 'messages' コレクションからデータを取得
def get_messages():
    messages_ref = db.collection("messages")
    docs = messages_ref.stream()
    
    for doc in docs:
        print(f"{doc.id} => {doc.to_dict()}")

# 動作確認のためにメッセージを取得
get_messages()

これをcheck.py
として実行たがエラー

原因は
Cloud Firestore API がプロジェクト「voicelinebot」で有効になっていないために発生しています。
Firestore API を有効にするがエラー

プロジェクトに Cloud Firestore データベースがまだ存在しないために発生しています。
Firestore データベースを初期化する必要があります

とのこと

カレンダー読み上げのReadMe作成

カレンダー読み上げのReadMe作成

コードはコピーしたので
ReadMe の原案を書いておく

M1 MacbookAir 16GB で動作しています

Gmail と Google Calendar を操作するためAPIとtoken.jsonが必要になります
https://developers.google.com/gmail/api/quickstart/python?hl=ja
を参考にAPIを使用可能にし、token.jsonを取得し同一ディレクトリに設置してください

使用にあたり pip install -r requirements.txt
を実行後

GoogleDrive のフォルダIDが必要になるので
create_folder.py で作業フォルダの作成とIDの取得を行います
add_gdrive_calendar.py
でIDを設定してください

label_gmail.py
でGmail ラベル一覧の取得ができます

add_gmail_calendar.py
で取得するGmailのラベルを設定します

Ollamaでelyza:jp8b’を使用します
arch -arm64 brew install git-lfs
git lfs install
git clone https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-GGUF.git
でダウンロード

vim Modelfile
でファイルを作成

中身を
FROM ./Llama-3-ELYZA-JP-8B-q4_k_m.gguf
TEMPLATE “””{{ if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>“””
PARAMETER stop “<|start_header_id|>”
PARAMETER stop “<|end_header_id|>”
PARAMETER stop “<|eot_id|>”
PARAMETER stop “<|reserved_special_token" として保存 ollama create elyza:jp8b -f Modelfile を実行し Successとなったら ollama run elyza:jp8b で実行します これで Ollamaでelyza:jp8bが動作します calendar_utils.py でDocker VoicevoxマシンのURLを指定していますので 環境に応じて変更してください 音声の作成に voicevox の docker が必要になります
docker pull voicevox/voicevox_engine:cpu-ubuntu20.04-latest
で取得しています 動作させるには バックグランドでの起動で -d オプションをつけて
docker run -d -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest
というように起動させます
IPアドレス部分はご自身のマシンのIPに変えてください

この文章をMarkdownで書き換える

# webcom_face_gcalendar
Perform face identification with OpenCV When the face of the registered person is recognized Get this week's schedule using Google calendar API Generate and read audio with Docker's voicevox


## 動作環境
M1 MacbookAir 16GB で動作しています

Gmail と Google Calendar を操作するためAPIとtoken.jsonが必要になります  
https://developers.google.com/gmail/api/quickstart/python?hl=ja  
を参考にAPIを使用可能にし、token.jsonを取得し同一ディレクトリに設置してください


使用にあたり 
`pip install -r requirements.txt`
を実行後

GoogleDrive のフォルダIDが必要になるので
create_folder.py で作業フォルダの作成とIDの取得を行います
add_gdrive_calendar.py
でIDを設定してください

label_gmail.py
でGmail ラベル一覧の取得ができます

add_gmail_calendar.py
で取得するGmailのラベルを設定します

Ollamaでelyza:jp8b’を使用します
```
arch -arm64 brew install git-lfs  
git lfs install  
git clone https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-GGUF.git
```
でダウンロード

`vim Modelfile`
でファイルを作成

中身を
```
FROM ./Llama-3-ELYZA-JP-8B-q4_k_m.gguf
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>"""
PARAMETER stop "<|start_header_id|>"
PARAMETER stop "<|end_header_id|>"
PARAMETER stop "<|eot_id|>"
PARAMETER stop "<|reserved_special_token"
```
として保存  
` ollama create elyza:jp8b -f Modelfile`
を実行し   
Successとなったら  
`ollama run elyza:jp8b`
で実行します  

これで Ollamaでelyza:jp8bが動作します  

calendar_utils.py  
でDocker VoicevoxマシンのURLを指定していますので  
環境に応じて変更してください  

音声の作成に voicevox の docker が必要になります  
`docker pull voicevox/voicevox_engine:cpu-ubuntu20.04-latest`
  
で取得しています  

動作させるには  
バックグランドでの起動で -d オプションをつけて  

`docker run -d -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest`  

というように起動させます
IPアドレス部分はご自身のマシンのIPに変えてください

とした

空白2つで改行
`で囲むとコード表示

複数行なら
“`
で囲む

URLはそのままでOK

タイトルは##の後に表示