テキストファイルを読み込み ollamaでGoogleカレンダーに送れる形式にする

テキストファイルを読み込み ollamaでGoogleカレンダーに送れる形式にする

PDFからテキストと抽出するので

pip install pdf2image pytesseract

Tesseract OCRのインストール

brew install tesseract

日本語言語データを追加

brew install tesseract-lang

とりあえずここまではOK

以前PDFの内容の取り出しはしたことがあるので
肝心の文章から予定を ollamaで取り出しを行う

テキストから日時とイベント情報を抽出します。今回のテキストでは、日付が「10月7日~10月15日」や「10月25日(金)」のように記載されています。これらを正規表現とdateparserライブラリで解析する

pip install dateparser

PDFの前に
テキストファイルの読み込み
テキストをOllamaで解析し、日時と予定を抽出
GoogleカレンダーAPIを使って予定を追加
を行うようにする

これはメールでお知らせすることがあるため

def read_text_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        text = file.read()
    return text

# 使用例
text_file_path = 'school_notice.txt'  # テキストファイルのパス
text_content = read_text_file(text_file_path)

でテキストファイルを読み込む

Ollamaを使用してテキストを解析し、日時とイベント情報を抽出
PythonからOllamaを呼び出す
Ollamaがローカルで動作している前提で、requestsライブラリを使用してHTTPリクエストを送信

import requests
import json

def parse_text_with_ollama(text):
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日時とそれに対応する予定を抽出してください。結果はJSON形式で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]
"""

    payload = {
        'model': 'your-ollama-model-name',  # 使用するOllamaモデルの名前
        'prompt': prompt
    }

    response = requests.post(ollama_url, json=payload)
    response_text = response.text

    # Ollamaの出力からJSON部分を抽出
    try:
        start_index = response_text.index('[')
        end_index = response_text.rindex(']') + 1
        json_str = response_text[start_index:end_index]
        events = json.loads(json_str)
    except (ValueError, json.JSONDecodeError) as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

# 使用例
events = parse_text_with_ollama(text_content)
print("抽出されたイベント:", events)


抽出されたイベントをGoogleカレンダーに追加
import os
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

def add_events_to_calendar(events):
    SCOPES = ['https://www.googleapis.com/auth/calendar']
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    else:
        print("token.json が見つかりません。認証を実行してください。")
        return

    service = build('calendar', 'v3', credentials=creds)

    for event in events:
        # 日付の形式を確認し、必要に応じて変換
        try:
            event_date = datetime.strptime(event['date'], '%Y-%m-%d')
        except ValueError:
            print(f"無効な日付形式: {event['date']}")
            continue

        event_body = {
            'summary': event['event'],
            'start': {
                'date': event_date.strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
            'end': {
                'date': (event_date + timedelta(days=1)).strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
        }

        # イベントをカレンダーに追加
        created_event = service.events().insert(calendarId='primary', body=event_body).execute()
        print(f"イベントが作成されました: {created_event.get('htmlLink')}")

# 使用例
add_events_to_calendar(events)

これらを

import os
import requests
import json
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

def read_text_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        text = file.read()
    return text

def parse_text_with_ollama(text):
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日時とそれに対応する予定を抽出してください。結果はJSON形式で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]
"""

    payload = {
        'model': 'your-ollama-model-name',  # 使用するOllamaモデルの名前
        'prompt': prompt
    }

    response = requests.post(ollama_url, json=payload)
    response_text = response.text

    # Ollamaの出力からJSON部分を抽出
    try:
        start_index = response_text.index('[')
        end_index = response_text.rindex(']') + 1
        json_str = response_text[start_index:end_index]
        events = json.loads(json_str)
    except (ValueError, json.JSONDecodeError) as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

def add_events_to_calendar(events):
    SCOPES = ['https://www.googleapis.com/auth/calendar']
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    else:
        print("token.json が見つかりません。認証を実行してください。")
        return

    service = build('calendar', 'v3', credentials=creds)

    for event in events:
        # 日付の形式を確認し、必要に応じて変換
        try:
            event_date = datetime.strptime(event['date'], '%Y-%m-%d')
        except ValueError:
            print(f"無効な日付形式: {event['date']}")
            continue

        event_body = {
            'summary': event['event'],
            'start': {
                'date': event_date.strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
            'end': {
                'date': (event_date + timedelta(days=1)).strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
        }

        # イベントをカレンダーに追加
        created_event = service.events().insert(calendarId='primary', body=event_body).execute()
        print(f"イベントが作成されました: {created_event.get('htmlLink')}")

# メインの実行部分
text_file_path = 'school_notice.txt'  # テキストファイルのパス
text_content = read_text_file(text_file_path)
events = parse_text_with_ollama(text_content)
print("抽出されたイベント:", events)
add_events_to_calendar(events)

というように1つにすることもできるが
検証と後で他でも使えるように
モジュールにして他でも使えるようにしたい

calendar_module.py

read_text_file(file_path)
parse_text_with_ollama(text, model_name)
add_events_to_calendar(events, calendar_id='primary', token_file='token.json')

を入れる

touch calendar_module.py

でファイルを作成

import os
import requests
import json
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

def read_text_file(file_path):
    """テキストファイルを読み込み、その内容を文字列として返します。"""
    with open(file_path, 'r', encoding='utf-8') as file:
        text = file.read()
    return text

def parse_text_with_ollama(text, model_name='your-ollama-model-name'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日時とそれに対応する予定を抽出してください。結果はJSON形式で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    response = requests.post(ollama_url, json=payload)
    response_text = response.text

    # Ollamaの出力からJSON部分を抽出
    try:
        start_index = response_text.index('[')
        end_index = response_text.rindex(']') + 1
        json_str = response_text[start_index:end_index]
        events = json.loads(json_str)
    except (ValueError, json.JSONDecodeError) as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

def add_events_to_calendar(events, calendar_id='primary', token_file='token.json'):
    """
    抽出されたイベントをGoogleカレンダーに追加します。

    Args:
        events (list): イベントのリスト。
        calendar_id (str): イベントを追加するカレンダーのID。
        token_file (str): 認証トークンファイルのパス。
    """
    SCOPES = ['https://www.googleapis.com/auth/calendar']
    creds = None
    if os.path.exists(token_file):
        creds = Credentials.from_authorized_user_file(token_file, SCOPES)
    else:
        print(f"{token_file} が見つかりません。認証を実行してください。")
        return

    service = build('calendar', 'v3', credentials=creds)

    for event in events:
        # 日付の形式を確認し、必要に応じて変換
        try:
            event_date = datetime.strptime(event['date'], '%Y-%m-%d')
        except ValueError:
            print(f"無効な日付形式: {event['date']}")
            continue

        event_body = {
            'summary': event['event'],
            'start': {
                'date': event_date.strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
            'end': {
                'date': (event_date + timedelta(days=1)).strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
        }

        # イベントをカレンダーに追加
        created_event = service.events().insert(calendarId=calendar_id, body=event_body).execute()
        print(f"イベントが作成されました: {created_event.get('htmlLink')}")

として保存

モジュールを作成したら、別のスクリプトからインポートして使用

 touch main.py

でファイルを作成

from calendar_module import read_text_file, parse_text_with_ollama, add_events_to_calendar

# テキストファイルのパス
text_file_path = 'school_notice.txt'  # 処理するテキストファイルのパス

# テキストの読み込み
text_content = read_text_file(text_file_path)

# Ollamaでテキストを解析(モデル名を指定)
events = parse_text_with_ollama(text_content, model_name='your-ollama-model-name')

# 抽出されたイベントを表示
print("抽出されたイベント:", events)

# Googleカレンダーにイベントを追加
add_events_to_calendar(events, calendar_id='primary', token_file='token.json')

とする

ただし今回はテストなので
とりあえずカレンダーにイベントを追加する部分はコメントアウトし
まずは Ollamaの結果を見る

またモデルには
parse_text_with_ollama関数内のモデル名をelyzaに変更
model_name引数のデフォルト値をelyza:jp8bに設定

ということで

import os
import requests
import json
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

def read_text_file(file_path):
    """テキストファイルを読み込み、その内容を文字列として返します。"""
    with open(file_path, 'r', encoding='utf-8') as file:
        text = file.read()
    return text

def parse_text_with_ollama(text, model_name='elyza:jp8b'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前(デフォルトは 'elyza:jp8b')。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日時とそれに対応する予定を抽出してください。結果はJSON形式で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    response = requests.post(ollama_url, json=payload)
    response_text = response.text

    # Ollamaの出力からJSON部分を抽出
    try:
        # レスポンスをJSONとして直接パース
        events = json.loads(response_text.strip())
    except json.JSONDecodeError:
        try:
            # JSON部分のみを抽出
            start_index = response_text.index('[')
            end_index = response_text.rindex(']') + 1
            json_str = response_text[start_index:end_index]
            events = json.loads(json_str)
        except (ValueError, json.JSONDecodeError) as e:
            print("Ollamaからのレスポンスの解析に失敗しました:", e)
            events = []

    return events

def add_events_to_calendar(events, calendar_id='primary', token_file='token.json'):
    """
    抽出されたイベントをGoogleカレンダーに追加します。

    Args:
        events (list): イベントのリスト。
        calendar_id (str): イベントを追加するカレンダーのID。
        token_file (str): 認証トークンファイルのパス。
    """
    SCOPES = ['https://www.googleapis.com/auth/calendar']
    creds = None
    if os.path.exists(token_file):
        creds = Credentials.from_authorized_user_file(token_file, SCOPES)
    else:
        print(f"{token_file} が見つかりません。認証を実行してください。")
        return

    service = build('calendar', 'v3', credentials=creds)

    for event in events:
        # 日付の形式を確認し、必要に応じて変換
        try:
            event_date = datetime.strptime(event['date'], '%Y-%m-%d')
        except ValueError:
            print(f"無効な日付形式: {event['date']}")
            continue

        event_body = {
            'summary': event['event'],
            'start': {
                'date': event_date.strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
            'end': {
                'date': (event_date + timedelta(days=1)).strftime('%Y-%m-%d'),
                'timeZone': 'Asia/Tokyo',
            },
        }

        # イベントをカレンダーに追加
        created_event = service.events().insert(calendarId=calendar_id, body=event_body).execute()
        print(f"イベントが作成されました: {created_event.get('htmlLink')}")

というように

calendar_module.py

を変更

またmain.pyを

from calendar_module import read_text_file, parse_text_with_ollama, add_events_to_calendar

# テキストファイルのパス
text_file_path = 'school_notice.txt'  # 処理するテキストファイルのパス

# テキストの読み込み
text_content = read_text_file(text_file_path)

# Ollamaでテキストを解析(モデル名を指定)
events = parse_text_with_ollama(text_content, model_name='elyza:jp8b')

# 抽出されたイベントを表示
print("抽出されたイベント:", events)

# Googleカレンダーにイベントを追加
#add_events_to_calendar(events, calendar_id='primary', token_file='token.json')

としておく

touch school_notice.txt

でテキストを
学校からのお知らせ内容をテキストファイルにして実験

しかし

Ollamaからのレスポンスの解析に失敗しました: substring not found
抽出されたイベント: []

となってしまう

機能を分割して問題を探す

以前のコードを見たらリクエストURLが

response = requests.post("http://localhost:11434/api/generate", 

なので

ollama_url = 'http://localhost:11434/api/generate'

に修正

import requests
import json

def parse_text_with_ollama(text, model_name='elyza/jp8b'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前(デフォルトは 'elyza/jp8b')。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント(修正)
    ollama_url = 'http://localhost:11434/api/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日時とそれに対応する予定を抽出してください。結果はJSON形式で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    try:
        response = requests.post(ollama_url, json=payload)
        response.raise_for_status()
        # レスポンスをJSONとしてパース
        response_json = response.json()
        # テキスト部分を取得
        response_text = response_json.get('response', '')
    except requests.exceptions.RequestException as e:
        print("Ollamaへのリクエストに失敗しました:", e)
        return []
    except json.JSONDecodeError as e:
        print("OllamaからのレスポンスがJSON形式ではありません:", e)
        return []

    # Ollamaの出力からJSON部分を抽出
    try:
        start_index = response_text.index('[')
        end_index = response_text.rindex(']') + 1
        json_str = response_text[start_index:end_index]
        events = json.loads(json_str)
    except (ValueError, json.JSONDecodeError) as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

とした

しかし

Ollamaからのレスポンスの解析に失敗しました: Expecting value: line 1 column 2 (char 1)
抽出されたイベント:

となる

ChatGGPTで情報を調べると以下の方になる

Ollamaはストリーミング形式でレスポンスを返しています。
つまり、モデルの生成結果が複数のJSONオブジェクトとして順次送られてきています

{“model”:”elyza:jp8b”,”created_at”:”…”,”response”:”…”,”done”:false}

この”response”フィールドに、モデルが生成したテキストの一部が含まれています。全体のレスポンスを組み立てるには、これらの”response”フィールドを順番に連結する必要があります

requestsライブラリを使用して、ストリーミングレスポンスを処理します。response.iter_lines()を使用して、各行を逐次処理

各行をJSONとしてパースし、”response”フィールドの値を取り出して連結

連結したテキストがJSON文字列(リスト)であることを前提に、json.loads()でパース

となるようにコード変更

import requests
import json

def parse_text_with_ollama(text, model_name='elyza:jp8b'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/api/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日付とそれに対応する予定を抽出してください。結果は純粋なJSON形式で、日本語で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]

重要事項:
- 出力は純粋なJSON形式で、追加のテキストや説明は含めないでください。
- 日付は"YYYY-MM-DD"の形式で出力してください。
- イベント名は元の文章から適切に抽出してください。
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    try:
        # ストリーミングレスポンスを取得
        response = requests.post(ollama_url, json=payload, stream=True)
        response.raise_for_status()

        # レスポンスのストリームを処理
        response_text = ''
        for line in response.iter_lines():
            if line:
                line_str = line.decode('utf-8')
                # 各行をJSONとしてパース
                line_json = json.loads(line_str)
                # "response"フィールドを連結
                response_text += line_json.get('response', '')

        print("Ollamaのレスポンス:")
        print(response_text)
    except requests.exceptions.RequestException as e:
        print("Ollamaへのリクエストに失敗しました:", e)
        return []
    except json.JSONDecodeError as e:
        print("レスポンスの解析に失敗しました:", e)
        return []

    # 連結したテキストをJSONとして解析
    try:
        events = json.loads(response_text)
    except json.JSONDecodeError as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

とする

実行結果は

{'date': '2024-10-07', 'event': 'ペットボトルの準備'}
{'date': '2024-10-15', 'event': None}
{'date': '2024-10-25', 'event': '準備物の確認'}
{'date': '2024-10-26', 'event': None}

となる

一応はエラーは消えたが他のものを試してみる

台風10号の影響により、9月2日(月)の給食が中止となりました。 弁当の準備をお願いします。 8月30日(金)現時点では、日課の変更はありません。 台風や大雨の状況によっては、変更する場合があります。 4月にコドモンで知らせした、「R6年度 自然災害発生時、警報発表・避難情報発表時等に伴う学校の対処」とおり対応します。 今一度御確認ください。
という文章で実験すると

{'date': '8月30日', 'event': '弁当の準備'}
{'date': '9月2日', 'event': '給食中止'}

とほぼ目的に近いものになる

PTA会員 様  Caros membros do PTA
 
 日頃よりPTA活動に御理解・御協力いただいき、ありがとうございます。
 令和7年度のPTA本部役員候補選考の時期になりました。
 アンケート形式にて本部役員の立候補を募ります。
 添付の文書を御一読いただき、以下のリンクからアンケートに回答をお願いします。
 回答期限は9月30日(月)とします。

だと

Ollamaのレスポンス:
[
    {"date": "2024-09-25", "event": "PTA本部役員候補選出についてのアンケート"},
    {"date": "2024-09-30", "event": "アンケート回答期限"}
]
抽出されたイベント:
{'date': '2024-09-25', 'event': 'PTA本部役員候補選出についてのアンケート'}
{'date': '2024-09-30', 'event': 'アンケート回答期限'}

テキストファイルの内容によっては

抽出されたイベント: {'date': '2024-10-07', 'event': 'ペットボトルの準備'} {'date': '2024-10-15', 'event': None} {'date': '2024-10-25', 'event': '準備物の確認'} {'date': '2024-10-26', 'event': None}

となるため
プロンプトを改善して、モデルにeventがnullにならないように強調

prompt = f"""
以下の文章から、日付とそれに対応する予定を抽出してください。結果は純粋なJSON形式で、日本語で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]

重要事項:
- 出力は純粋なJSON形式で、追加のテキストや説明は含めないでください。
- 日付は"YYYY-MM-DD"の形式で出力してください。
- **イベント名が存在しない場合、そのエントリを出力しないでください。**
- イベント名は元の文章から適切に抽出してください。
"""



リスト内包表記を使用:eventsリストからevent['event']がNoneでないイベントだけを新しいリストにします。
is not Noneを使用:event['event']がNoneでないことを確認

# Ollamaでテキストを解析(モデル名を指定)
events = parse_text_with_ollama(text_content, model_name='elyza:jp8b')

# 抽出されたイベントを表示
print("抽出されたイベント:")
for event in events:
    print(event)

# eventがNoneのものを削除
events = [event for event in events if event['event'] is not None]

# フィルタリング後のイベントを表示
print("有効なイベント:")
for event in events:
    print(event)

これらを変更するので

import requests
import json

def parse_text_with_ollama(text, model_name='elyza:jp8b'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/api/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日付とそれに対応する予定を抽出してください。結果は純粋なJSON形式で、日本語で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]

重要事項:
- 出力は純粋なJSON形式で、追加のテキストや説明は含めないでください。
- 日付は"YYYY-MM-DD"の形式で出力してください。
- イベント名は元の文章から適切に抽出してください。
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    try:
        # ストリーミングレスポンスを取得
        response = requests.post(ollama_url, json=payload, stream=True)
        response.raise_for_status()

        # レスポンスのストリームを処理
        response_text = ''
        for line in response.iter_lines():
            if line:
                line_str = line.decode('utf-8')
                # 各行をJSONとしてパース
                line_json = json.loads(line_str)
                # "response"フィールドを連結
                response_text += line_json.get('response', '')

        print("Ollamaのレスポンス:")
        print(response_text)
    except requests.exceptions.RequestException as e:
        print("Ollamaへのリクエストに失敗しました:", e)
        return []
    except json.JSONDecodeError as e:
        print("レスポンスの解析に失敗しました:", e)
        return []

    # 連結したテキストをJSONとして解析
    try:
        events = json.loads(response_text)
    except json.JSONDecodeError as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

import requests
import json

def parse_text_with_ollama(text, model_name='elyza:jp8b'):
    """
    Ollamaを使用してテキストから日時とイベントを抽出します。

    Args:
        text (str): 解析するテキスト。
        model_name (str): 使用するOllamaモデルの名前。

    Returns:
        list: 抽出されたイベントのリスト。
    """
    # OllamaのAPIエンドポイント
    ollama_url = 'http://localhost:11434/api/generate'

    # Ollamaに送信するプロンプトを作成
    prompt = f"""
以下の文章から、日付とそれに対応する予定を抽出してください。結果は純粋なJSON形式で、日本語で、"date"と"event"のキーを持つオブジェクトのリストとして返してください。

文章:
{text}

出力例:
[
    {{"date": "2024-10-07", "event": "ペットボトルの準備"}},
    {{"date": "2024-10-25", "event": "準備物の確認"}}
]

重要事項:
- 出力は純粋なJSON形式で、追加のテキストや説明は含めないでください。
- 日付は"YYYY-MM-DD"の形式で出力してください。
- **イベント名が存在しない場合、そのエントリを出力しないでください。**
- イベント名は元の文章から適切に抽出してください。
"""

    payload = {
        'model': model_name,
        'prompt': prompt
    }

    try:
        # ストリーミングレスポンスを取得
        response = requests.post(ollama_url, json=payload, stream=True)
        response.raise_for_status()

        # レスポンスのストリームを処理
        response_text = ''
        for line in response.iter_lines():
            if line:
                line_str = line.decode('utf-8')
                # 各行をJSONとしてパース
                line_json = json.loads(line_str)
                # "response"フィールドを連結
                response_text += line_json.get('response', '')

        print("Ollamaのレスポンス:")
        print(response_text)
    except requests.exceptions.RequestException as e:
        print("Ollamaへのリクエストに失敗しました:", e)
        return []
    except json.JSONDecodeError as e:
        print("レスポンスの解析に失敗しました:", e)
        return []

    # 連結したテキストをJSONとして解析
    try:
        events = json.loads(response_text)
    except json.JSONDecodeError as e:
        print("Ollamaからのレスポンスの解析に失敗しました:", e)
        events = []

    return events

にして

from ollama_module import parse_text_with_ollama
from google_calendar_module import add_events_to_calendar

# テキストファイルのパス
text_file_path = 'school_notice.txt'  # 処理するテキストファイルのパス

# テキストの読み込み
with open(text_file_path, 'r', encoding='utf-8') as file:
    text_content = file.read()

# Ollamaでテキストを解析(モデル名を指定)
events = parse_text_with_ollama(text_content, model_name='elyza:jp8b')

# 抽出されたイベントを表示
print("抽出されたイベント:")
for event in events:
    print(event)

# Googleカレンダーにイベントを追加
# add_events_to_calendar(events, calendar_id='primary', token_file='token.json', credentials_file='credentials.json')

from ollama_module import parse_text_with_ollama
from google_calendar_module import add_events_to_calendar

# テキストファイルのパス
text_file_path = 'school_notice.txt'  # 処理するテキストファイルのパス

# テキストの読み込み
with open(text_file_path, 'r', encoding='utf-8') as file:
    text_content = file.read()

# Ollamaでテキストを解析(モデル名を指定)
events = parse_text_with_ollama(text_content, model_name='elyza:jp8b')

# 抽出されたイベントを表示
print("抽出されたイベント:")
for event in events:
    print(event)

# eventがNoneのものを削除
events = [event for event in events if event['event'] is not None]

# フィルタリング後のイベントを表示
print("有効なイベント:")
for event in events:
    print(event)

# 有効なイベントがある場合のみGoogleカレンダーに追加
if events:
    add_events_to_calendar(events, calendar_id='primary', token_file='token.json', credentials_file='credentials.json')
else:
    print("有効なイベントがありません。")

とした

Googleカレンダーの今週の予定を取得する

Googleカレンダーの今週の予定を取得する

実行環境
M1 MacbookAir 16GB

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib pytz

で必要なライブラリをインストール

touch get_week.py

でファイルを作成

import os
import datetime
import pytz
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# カレンダーAPIのスコープ
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']

def main():
    """今週のGoogleカレンダーの予定を取得して表示します。"""
    creds = None
    # 既存のトークンファイルを使用
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    else:
        print("トークンファイルが見つかりません。認証を実行してください。")
        return

    # Google Calendar APIサービスを構築
    service = build('calendar', 'v3', credentials=creds)

    # タイムゾーンの設定(日本時間)
    tz = pytz.timezone('Asia/Tokyo')

    # 現在の日時を取得
    now = datetime.datetime.now(tz)

    # 今週の開始日(月曜日)と終了日(日曜日)を計算
    start_of_week = now - datetime.timedelta(days=now.weekday())
    end_of_week = start_of_week + datetime.timedelta(days=7)

    # RFC3339形式に変換
    time_min = start_of_week.isoformat()
    time_max = end_of_week.isoformat()

    print(f"{time_min} から {time_max} までの予定を取得します。")

    # イベントを取得
    events_result = service.events().list(
        calendarId='primary',
        timeMin=time_min,
        timeMax=time_max,
        singleEvents=True,
        orderBy='startTime'
    ).execute()
    events = events_result.get('items', [])

    if not events:
        print('今週の予定はありません。')
    else:
        print('今週の予定:')
        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            summary = event.get('summary', '(タイトルなし)')
            print(f"{start} - {summary}")

if __name__ == '__main__':
    main()

実行結果は

2024-09-30T23:52:51.254684+09:00 から 2024-10-07T23:52:51.254684+09:00 までの予定を取得します。
今週の予定:
2024-10-05T09:00:00+09:00 - APIを使って追加したイベント

となる

一週間の予定は月曜日からの予定となっている

次は実行した日より前の予定は出力しないようにコード変更する

現在のコードでは、time_min(開始時刻)が「今週の始まり(週の月曜日)」

これを「現在の日時」に変更することで、実行した日より前の予定を除外

time_minをnow(現在の日時)に変更

import os
import datetime
import pytz
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# カレンダーAPIのスコープ
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']

def main():
    """今週のGoogleカレンダーの予定を取得して表示します。"""
    creds = None
    # 既存のトークンファイルを使用
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    else:
        print("トークンファイルが見つかりません。認証を実行してください。")
        return

    # Google Calendar APIサービスを構築
    service = build('calendar', 'v3', credentials=creds)

    # タイムゾーンの設定(日本時間)
    tz = pytz.timezone('Asia/Tokyo')

    # 現在の日時を取得
    now = datetime.datetime.now(tz)

    # 今週の開始日(月曜日)と終了日(日曜日)を計算
    start_of_week = now - datetime.timedelta(days=now.weekday())
    end_of_week = start_of_week + datetime.timedelta(days=7)

    # RFC3339形式に変換
    time_min = start_of_week.isoformat()
    time_max = end_of_week.isoformat()

    print(f"{time_min} から {time_max} までの予定を取得します。")

    # イベントを取得
    events_result = service.events().list(
        calendarId='primary',
        timeMin=time_min,
        timeMax=time_max,
        singleEvents=True,
        orderBy='startTime'
    ).execute()
    events = events_result.get('items', [])

    if not events:
        print('今週の予定はありません。')
    else:
        print('今週の予定:')
        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            summary = event.get('summary', '(タイトルなし)')
            print(f"{start} - {summary}")

if __name__ == '__main__':
    main()

import os
import datetime
import pytz
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# カレンダーAPIのスコープ
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']

def main():
    """今週の残りのGoogleカレンダーの予定を取得して表示します。"""
    creds = None
    # 既存のトークンファイルを使用
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    else:
        print("トークンファイルが見つかりません。認証を実行してください。")
        return

    # Google Calendar APIサービスを構築
    service = build('calendar', 'v3', credentials=creds)

    # タイムゾーンの設定(日本時間)
    tz = pytz.timezone('Asia/Tokyo')

    # 現在の日時を取得
    now = datetime.datetime.now(tz)

    # 今週の終了日(日曜日)を計算
    start_of_week = now - datetime.timedelta(days=now.weekday())
    end_of_week = start_of_week + datetime.timedelta(days=7)

    # time_minを現在の日時に設定
    time_min = now.isoformat()
    # time_maxは今週の終了日時
    time_max = end_of_week.isoformat()

    print(f"{time_min} から {time_max} までの予定を取得します。")

    # イベントを取得
    events_result = service.events().list(
        calendarId='primary',
        timeMin=time_min,
        timeMax=time_max,
        singleEvents=True,
        orderBy='startTime'
    ).execute()
    events = events_result.get('items', [])

    if not events:
        print('今週の残りの予定はありません。')
    else:
        print('今週の残りの予定:')
        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            summary = event.get('summary', '(タイトルなし)')
            print(f"{start} - {summary}")

if __name__ == '__main__':
    main()

とすることで解決

次はテキストの内容から日時と予定を取り出しGoogleカレンダーへAPIで予定を書き込みできるようにする
また
日時と予定の取り出しはOllamaを使うことで汎用性を持たせることにする

Google カレンダーに予定をpythonで追加する

Google カレンダーに予定をpythonで追加する

実行環境
M1 MacbookAir 16GB

APIなどの登録が必要になるので
[初心者向け] GoogleカレンダーにPythonから予定を追加・編集してみた

を参考に行う

https://developers.google.com/calendar/api/v3/reference/calendarList?hl=ja
がリファレンス

なお情報の取得も後で必要になるので

Googleカレンダー情報を取得する
も参考にする

まずは google カレンダーのAPIを有効にする

流れとしては
* 認証情報の発行
* 認証情報を使ってPythonのプログラムを実行

Google Cloudのコンソールにログイン

プロジェクトはすでに作成しているものを選択
APIとサービスをクリック

APIとサービスを有効にするをクリック

calendar
で検索する
なお日本語でカレンダーとしても出ないので注意

Google Calendar API をクリック
有効にするをクリック

これでOK
認証関連のJSONファイルは以前Gmailで作成してるので
今回もいずれGmailを使うので省略

mkdir week_calendar_voice

で作業ディレクトリを作成

ここに

 cp ../mail_auto/*.json .

で以前作成したプロジェクトから
credentials.json
token.json
をコピーする

import os.path
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build

# カレンダーAPIのスコープ(Google Calendarの読み書き権限)
SCOPES = ['https://www.googleapis.com/auth/calendar']

# 既存の token.json を使用
creds = None
if os.path.exists('token.json'):
    creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    
# トークンが無効またはスコープが一致しない場合は再認証
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
        
    # トークンを保存
    with open('token.json', 'w') as token:
        token.write(creds.to_json())

# Google Calendar APIサービスを構築
service = build('calendar', 'v3', credentials=creds)

# イベントの詳細を設定
event = {
  'summary': 'APIを使って追加したイベント',
  'location': 'オンライン',
  'description': 'Google Calendar APIで追加されたイベントです。',
  'start': {
    'dateTime': '2024-10-05T09:00:00',
    'timeZone': 'Asia/Tokyo',
  },
  'end': {
    'dateTime': '2024-10-05T10:00:00',
    'timeZone': 'Asia/Tokyo',
  },
  'attendees': [
    {'email': 'example@example.com'},
  ],
  'reminders': {
    'useDefault': False,
    'overrides': [
      {'method': 'email', 'minutes': 24 * 60},
      {'method': 'popup', 'minutes': 10},
    ],
  },
}

# カレンダーにイベントを挿入
event = service.events().insert(calendarId='primary', body=event).execute()
print(f"イベントが作成されました: {event.get('htmlLink')}")

がchatgpt で生成されたコードだが

追加する予定は

# イベントの詳細を設定 event = { 'summary': 'APIを使って追加したイベント', 'location': 'オンライン', 'description': 'Google Calendar APIで追加されたイベントです。', 'start': { 'dateTime': '2024-10-05T09:00:00', 'timeZone': 'Asia/Tokyo', }, 'end': { 'dateTime': '2024-10-05T10:00:00', 'timeZone': 'Asia/Tokyo', }, 'attendees': [ {'email': 'example@example.com'}, ], 'reminders': { 'useDefault': False, 'overrides': [ {'method': 'email', 'minutes': 24 * 60}, {'method': 'popup', 'minutes': 10}, ], }, } 

このうち
summaryがカレンダーに表示する予定
locationが場所で住所や会場名を指定することも可能
descriptionが詳細な説明
‘start’: イベントの開始時刻
‘end’: イベントの終了時刻
‘attendees’: 出席者のリスト 多分これは使わない
‘reminders’: リマインダーの設定 これも使わない

とりあえずテストなので
attendersと reminders を削除して予定を追加してみる

 touch add_calendar.py

でファイルを作成

これで実行したら

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/week_calendar_voice/add_calendar.py", line 18, in <module>
    creds.refresh(Request())
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/oauth2/credentials.py", line 335, in refresh
    ) = reauth.refresh_grant(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/oauth2/reauth.py", line 351, in refresh_grant
    _client._handle_error_response(response_data, retryable_error)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/oauth2/_client.py", line 73, in _handle_error_response
    raise exceptions.RefreshError(
google.auth.exceptions.RefreshError: ('invalid_scope: Bad Request', {'error': 'invalid_scope', 'error_description': 'Bad Request'})

これはgmailの許可はあるけど
Calendar のAPIのスコープがないのが原因

なので
一度 token.jsonを削除して再度実行したけどだめだった

これは認証関連の問題で
再度

rm token.json

で削除してから

import datetime
import os.path
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/calendar']

# トークンファイルのチェック
creds = None
if os.path.exists('token.json'):
    creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    
# トークンがない場合、新しい認証を実行
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
        
    # トークンを保存
    with open('token.json', 'w') as token:
        token.write(creds.to_json())

 touch test2.py

で作成して

python test2.py

を実行することで認証画面になるので
そのまま進めていけば認証が完了する

再度

python add_calendar.py

を実行すれば
無事に予定が追加される

とりあえず予定の追加はできたので
次に予定の取得をする
まずは今週の予定から