Ollama のモデル削除

Ollama のモデル削除

ollama list

でインストール済みモデルを調べる

NAME                               	ID          	SIZE  	MODIFIED     
deepseek-r1:8b-llama-distill-q4_K_M	28f8fd6cdc67	4.9 GB	47 hours ago	
llava:latest                       	8dd30f6b0cb1	4.7 GB	6 months ago	
elyza:jp8b                         	4b08bac08c49	4.9 GB	6 months ago	
llama3:latest                      	365c0bd3c000	4.7 GB	6 months ago	

elyza:jp8b
以外は不要なので削除する

 ollama rm llava:latest 
ollama rm deepseek-r1:8b-llama-distill-q4_K_M
ollama rm llama3:latest 

これで再度

ollama run hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-gguf

を実行したけど

pulling manifest 
pulling daf92c0e1436... 100% ▕████████████████▏ 9.0 GB                         
pulling 8aa4c0321ccd...   0% ▕                ▏    0 B/ 493 B                  
Error: unexpected status code 200

となる

 ollama --version

だと

ollama version is 0.3.6

2024年8月の時点のものなので最新のものにする

Apple Silicon(M3) Macで Ollama を動かしてみる
を参考に

https://ollama.com/download
からダウンロードして
Ollama-darwin.zip
をダブルクリックで解凍

Ollama.app
ができているので
これをダブルクリックで進めればOK

再度

ollama --version        

で最新のものになるのを確認

ollama version is 0.5.11

https://huggingface.co/cyberagent/DeepSeek-R1-Distill-Qwen-14B-Japanese
を参考にモデル名を
cyberagent/DeepSeek-R1-Distill-Qwen-14B-Japanese

しかし

ollama list
NAME                                                                        ID              SIZE      MODIFIED       
hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-gguf:latest    26e5cc1b03cd    9.0 GB    13 minutes ago    
elyza:jp8b                                                                  4b08bac08c49    4.9 GB    6 months ago     

となる

このためモデル名を
hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-gguf:latest
として実行したが

INFO     [browser_use] BrowserUse logging setup complete with level info
INFO     [root] Anonymized telemetry enabled. See https://github.com/browser-use/browser-use for more information.
INFO     [agent] 🚀 Starting task: 
        1. https://cookpad.com/jp にアクセス
        
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 1/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 2/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 3/3 times:
 
ERROR    [agent] ❌ Stopping due to 3 consecutive failures
INFO     [agent] Created GIF at agent_history.gif
AgentHistoryList(all_results=[ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True)], all_model_outputs=[])

もしかしたら、蒸留してしまうと使えないのかもしれない

とりあえず、モデル名の指定がダメかも

import os

from langchain_ollama import OllamaLLM
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.controller.service import Controller

import asyncio

controller = Controller()
agent = None

@controller.registry.action('スクリーンショットを保存する')
async def save_screenshot(filename: str = "screenshot.png") -> str:
    page = await agent.browser_context.get_current_page()
    await page.screenshot(path=filename)
    return f"スクリーンショットを {filename} として保存しました"
    
async def main():
    global agent
    llm = OllamaLLM(model="hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-gguf:latest")
    agent = Agent(
        task="""
        1. https://cookpad.com/jp にアクセス
        """,
        llm=llm,
        controller=controller,
        browser=Browser(config=BrowserConfig(
            disable_security=True, 
            headless=False,
        )),
    )
    result = await agent.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

だったので
Latest を抜いて再度実行する
しかし変わらない

ollama rm hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-gguf:latest

で一度削除

deepseekr1 browser-use ollama

で検索

Browser-use web-uiでollamaを使ってDeepseek-r1を使用する
を参考に

ollama run deepseek-r1:14b

しかし

import os

from langchain_ollama import OllamaLLM
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.controller.service import Controller

import asyncio

controller = Controller()
agent = None

@controller.registry.action('スクリーンショットを保存する')
async def save_screenshot(filename: str = "screenshot.png") -> str:
    page = await agent.browser_context.get_current_page()
    await page.screenshot(path=filename)
    return f"スクリーンショットを {filename} として保存しました"
    
async def main():
    global agent
    llm = OllamaLLM(model="deepseek-r1:14b")
    agent = Agent(
        task="""
        Navigate to 'https://en.wikipedia.org/wiki/Internet' and scroll to the string 'The vast majority of computer'
        """,
        llm=llm,
        controller=controller,
        browser=Browser(config=BrowserConfig(
            disable_security=True, 
            headless=False,
        )),
    )
    result = await agent.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

としても結果は変わらない

もしかしたらコードが違うのかもしれない

ollama browser-use

で検索

browser-use + ollamaでローカルサイト(Zabbix)を操作してみた
を参考に

Test.pyとして動かすが失敗

どうやらモデルが原因みたい
取り合えず動く事例が出るまでまつ

Browser-useでollamaを使ってローカルLLMを使用する
https://zenn.dev/tossy21/articles/5b0ff1452af70a#googleへアクセスし、%22openai%22で検索した最初のurlを取得
では実験したすべてのモデルが失敗に終わってるし

Browser-use. deepseekR1 + Ollama (エラーとなるため失敗)

Browser-use. deepseekR1 + Ollama (エラーとなるため失敗)

browser-useをGeminiで動かすまで(langchain-google-genai 2.0.8がPyPIにリリースされるまでは暫定的にリポジトリからインストール)
が気になったけど
これはgemini を動かす時に必要なリポジトリについて
なので
Deepseek には関係ない

DeepSeek R1をMac Book Airで動かしてみた:R1の6つの「蒸留」モデルの使い方
では
実際に、「DeepSeek-R1-Distill-Llama-8B」を筆者のMac Book Airで動かしてみたところ、普通に使えるスピードで動作したので、夢が広がる。
ちなみに、iPhone / Android でもDeepSeek R1を動かすことができる

とのこと

DeepSeek-R1ローカル環境とWebUIのお手軽実装 & モデル選択のススメ
とかもあるけど
スペックが
* Windows11 WSL2(2.3.26.0)
* Ubuntu(22.04.3 LTS)
* Docker Desktop(27.3.1)
* PCスペック:RTX 4070 VRAM 12GB / RAM 32GB

ログインを伴う作業を行いたいため deepseekR1 をローカルで動かすため
M1 MacBook Air 16GBで動作可能なモデルを調べてみる

GPTによれば

蒸留モデルだと
M1 MacBook Air (16GB) で動作可能な DeepSeek R1 の蒸留モデルをまとめます。
推奨モデル
1. DeepSeek R1 1.5B 蒸留モデル
* モデル名: deepseek-r1:1.5b-qwen-distill-q4_K_M
* パラメータ数: 1.5B(15億)
* サイズ: 約1.1 GB
* 推奨メモリ: 8GB 以上
* 特徴: 最小の蒸留モデルで、軽量かつ高速に動作します。基本的なタスクや軽量な処理に適しています。
2. DeepSeek R1 7B 蒸留モデル
* モデル名: deepseek-r1:7b-qwen-distill-q4_K_M
* パラメータ数: 7B(70億)
* サイズ: 約4.7 GB
* 推奨メモリ: 16GB
* 特徴: バランスの取れた性能を持ち、一般的なNLPタスクに適しています。M1 MacBook Air でも快適に動作します。
3. DeepSeek R1 8B 蒸留モデル
* モデル名: deepseek-r1:8b-llama-distill-q4_K_M
* パラメータ数: 8B(80億)
* サイズ: 約4.9 GB
* 推奨メモリ: 16GB
* 特徴: やや大きめのモデルですが、M1 MacBook Air でも動作可能です。より高度なタスクや複雑な処理に適しています。

とりあえず browser-use の動作ができれば目的は達成できそうなので
* deepseek-r1:8b-llama-distill-q4_K_M
で実験

deepseek-r1:8b-llama-distill-q4_K_M
で検索

https://www.youtube.com/watch?v=oUBeJkKwBcc の動画を日本語に要約。 セットアップ手順を示して

とGPTで調べたが
実行方法が出たのは蒸留モデルではない方だった
とは言ってもこれで要約して方法を動画から抽出できるのは便利だと思う

【蒸留モデル】DeepSeek-R1ローカル実行時におすすめのモデル
を参考に行う

https://ollama.com/library/deepseek-r1/tags

利用可能なすべてのモデルタグは公式タグ一覧で確認可能

8b-llama-distill-q4_K_M
で検索すると出るので
これを使うことにする

インストールコマンドは

ollama pull deepseek-r1:8b-llama-distill-q4_K_M

これでインストールできるので
Deepseek で browser-use を試す

import os

from langchain_community.llms import Ollama  # Ollamaを使用
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.controller.service import Controller

import asyncio

controller = Controller()
agent = None

@controller.registry.action('スクリーンショットを保存する')
async def save_screenshot(filename: str = "screenshot.png") -> str:
    page = await agent.browser_context.get_current_page()
    await page.screenshot(path=filename)
    return f"スクリーンショットを {filename} として保存しました"
    
async def main():
    global agent
    llm = Ollama(model="deepseek-r1:8b-llama-distill-q4_K_M")  # Ollamaのローカルモデルを指定
    agent = Agent(
        task="""
        1. https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc にアクセス
        2. 「フルスクリーン」ボタンをクリック後、数秒待機
        3. スクリーンショットを step-{n}.png として保存
        """,
        llm=llm,
        controller=controller,
        browser=Browser(config=BrowserConfig(
            disable_security=True, 
            headless=False,
        )),
    )
    result = await agent.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

として実行

しかし

Traceback (most recent call last): File "/Users/snowpool/aw10s/deepseekR1/advertisement.py", line 3, in <module> from langchain_community.llms import Ollama # Ollamaを使用 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ModuleNotFoundError: No module named 'langchain_community'

となるので

pip install langchain-community
pip install -U langchain-ollama

の後に
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="deepseek-r1:8b-llama-distill-q4_K_M")

としたけど

INFO     [browser_use] BrowserUse logging setup complete with level info
INFO     [root] Anonymized telemetry enabled. See https://github.com/browser-use/browser-use for more information.
INFO     [agent] 🚀 Starting task: 
        1. https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc にアクセス
        2. 「フルスクリーン」ボタンをクリック後、数秒待機
        3. スクリーンショットを step-{n}.png として保存
        
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 1/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 2/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 3/3 times:
 
ERROR    [agent] ❌ Stopping due to 3 consecutive failures
INFO     [agent] Created GIF at agent_history.gif
AgentHistoryList(all_results=[ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True)], all_model_outputs=[])

このため ollamaの動作確認

ollama run deepseek-r1:8b-llama-distill-q4_K_M --prompt "こんにちは。あなたは何ができますか?"

Error: unknown flag: --prompt

となる

–prompt フラグはサポートされていないため、プロンプトは位置引数として渡すので

ollama run deepseek-r1:8b-llama-distill-q4_K_M "こんにちは。あなたは何ができますか?"

とした

<think>
Alright, the user greeted me with "こんにちは。" which is Japanese for 
"Hello." I should respond in a friendly manner.

I need to let them know what I can do. Since I'm an AI assistant, I should 
mention various tasks like answering questions, providing information, and 
helping with problems.

It's important to keep it simple and conversational, avoiding any 
technical jargon.

I'll make sure my response is welcoming and open-ended to encourage them 
to ask more.
</think>

こんにちは!私は人工知能助手です。何ができますか?回答、情報検索、問題解決な
ど、さまざまなタスクを担当します。どのようなお手伝いが必要ですか?

と問題ない

しかしエラーになるので
クックパッドの検索を試す

import os

from langchain_ollama import OllamaLLM
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.controller.service import Controller

import asyncio

controller = Controller()
agent = None

@controller.registry.action('スクリーンショットを保存する')
async def save_screenshot(filename: str = "screenshot.png") -> str:
    page = await agent.browser_context.get_current_page()
    await page.screenshot(path=filename)
    return f"スクリーンショットを {filename} として保存しました"
    
async def main():
    global agent
    llm = OllamaLLM(model="deepseek-r1:8b-llama-distill-q4_K_M")
    agent = Agent(
        task="""
        1. https://cookpad.com/jp にアクセスし、ページが完全に読み込まれるまで待機する。
        2. ページ上部にある検索ボックスに「ぶり大根」と入力し、検索ボタンをクリックする。
        3. 検索結果が表示されたら、一番最初のレシピをクリックする。
        4. 各ステップが完了するたびに、スクリーンショットを step-{n}.png として保存する。
        """,
        llm=llm,
        controller=controller,
        browser=Browser(config=BrowserConfig(
            disable_security=True, 
            headless=False,
        )),
    )
    result = await agent.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

しかし

INFO     [browser_use] BrowserUse logging setup complete with level info
INFO     [root] Anonymized telemetry enabled. See https://github.com/browser-use/browser-use for more information.
INFO     [agent] 🚀 Starting task: 
        1. https://cookpad.com/jp にアクセスし、ページが完全に読み込まれるまで待機する。
        2. ページ上部にある検索ボックスに「ぶり大根」と入力し、検索ボタンをクリックする。
        3. 検索結果が表示されたら、一番最初のレシピをクリックする。
        4. 各ステップが完了するたびに、スクリーンショットを step-{n}.png として保存する。
        
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 1/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 2/3 times:
 
INFO     [agent] 
📍 Step 1
ERROR    [agent] ❌ Result failed 3/3 times:
 
ERROR    [agent] ❌ Stopping due to 3 consecutive failures
INFO     [agent] Created GIF at agent_history.gif
AgentHistoryList(all_results=[ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='', include_in_memory=True)], all_model_outputs=[])

となる

gemini でのスクショの撮影

プロンプトを

https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pcへアクセスし、「日替」と書かれた画像をクリックした後に「プリント」をクリックし
送信先から「PDFに保存」を選択して「保存」をクリックしてください。

だと
プリント画面で固まる
おそらくプリントの部分がbrowserではないため制御外になると思う

なのでフルスクリーンにしてスクショ
が正解っぽい

スクショに関しては
https://zenn.dev/gunjo/articles/2f6898b846d371

https://zenn.dev/kbyk/articles/3e997a2f762018
を参考に進める

以下コード

from langchain_google_genai import ChatGoogleGenerativeAI
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.controller.service import Controller

import asyncio

controller = Controller()
agent = None

@controller.registry.action('スクリーンショットを保存する')
async def save_screenshot(filename: str = "screenshot.png") -> str:
    page = await agent.browser_context.get_current_page()
    await page.screenshot(path=filename)
    return f"スクリーンショットを {filename} として保存しました"
    
async def main():
    global agent
    llm = ChatGoogleGenerativeAI(model="gemini-pro")  # Gemini Pro モデルを指定
    agent = Agent(
        task="""
        以下の手順を実行してください:
        1. https://s.shufoo.net/chirashi/860323/?cid=nmail_pc にアクセス
        2. 「日替」という画像をクリック
        3. 「フルスクリーン」ボタンをクリック
        4. 拡大画像が表示されたら、スクリーンショットを step-{n}.png として保存
        """,
        llm=llm,
        controller=controller,
        browser=Browser(config=BrowserConfig(
            disable_security=True, 
            headless=False,
        )),
    )
    result = await agent.run()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

これで実行すると

INFO [browser_use] BrowserUse logging setup complete with level info INFO [root] Anonymized telemetry enabled. See https://github.com/browser-use/browser-use for more information. INFO [agent] 🚀 Starting task: 以下の手順を実行してください: 1. https://s.shufoo.net/chirashi/860323/?cid=nmail_pc にアクセス 2. 「日替」という画像をクリック 3. 「フルスクリーン」ボタンをクリック 4. 拡大画像が表示されたら、スクリーンショットを step-{n}.png として保存 INFO [agent] 📍 Step 1 WARNING [langchain_google_genai.chat_models] Retrying langchain_google_genai.chat_models._achat_with_retry.<locals>._achat_with_retry in 2.0 seconds as it raised NotFound: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.. ERROR [agent] ❌ Result failed 1/3 times: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods. INFO [agent] 📍 Step 1 WARNING [langchain_google_genai.chat_models] Retrying langchain_google_genai.chat_models._achat_with_retry.<locals>._achat_with_retry in 2.0 seconds as it raised NotFound: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.. ERROR [agent] ❌ Result failed 2/3 times: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods. INFO [agent] 📍 Step 1 WARNING [langchain_google_genai.chat_models] Retrying langchain_google_genai.chat_models._achat_with_retry.<locals>._achat_with_retry in 2.0 seconds as it raised NotFound: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.. ERROR [agent] ❌ Result failed 3/3 times: 404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods. ERROR [agent] ❌ Stopping due to 3 consecutive failures INFO [agent] Created GIF at agent_history.gif AgentHistoryList(all_results=[ActionResult(is_done=False, extracted_content=None, error='404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.', include_in_memory=True), ActionResult(is_done=False, extracted_content=None, error='404 models/gemini-gemini-2.0-flash-exp is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.', include_in_memory=True)], all_model_outputs=[]) となった

エラーメッセージから、指定した gemini-gemini-2.0-flash-exp というモデルが Google Generative AI API v1beta では見つからない という問題が発生

現在、LangChain で GoogleGenerativeAI を使用する際にサポートされているモデルには以下の
gemini-pro
gemini-pro-vision
gemini-1.5-pro
gemini-1.5-flash
らしい

gemini-1.5-flash
で実験したが
意図しない場所でスクショになってしまう

そもそも見ているページが違っている

https://www.shufoo.net/pntweb/shopDetail/860323/45225639804667/
の挙動を見ていたが

指定しているURLが
メールだと
https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc
になっているので

メールからではなく
チラシのURLが動的でないかを調べることにする

なおメールに記載されているURLに関しては固定のままなので
そのまま使えるURLになっている

URLを変更したが、フルスクリーンをクリックした後すぐにスクショだと
フルスクリーン画面になる前に撮影してしまうため
3秒停止とプロンプトに追加したが失敗する

このため

        1. https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc にアクセス
        2. 「フルスクリーン」ボタンをクリック後、数秒待機
        3. スクリーンショットを step-{n}.png として保存
        """,

というようにプロンプトを変更することで成功

画像が小さいため

        以下の手順を実行してください:
        1. https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc にアクセス
        2. 「フルスクリーン」ボタンをクリック後、数秒待機
        3. フルスクリーン画面が表示されたら、拡大をクリック後、数秒待機
        4. スクリーンショットを step-{n}.png として保存
        """,

とすると今度は拡大のみになる
ブラウザの取り扱いが別のブラウザ扱いなのかもしれない

冷蔵庫の食材画像から料理を提案するWebアプリをGeminiで作る

冷蔵庫の食材画像から料理を提案するWebアプリをGeminiで作るレシピ

を試す

既にAPIキーは取得済み

pip install google-generativeai

でインストール

モデルを指定してgenerate_content関数にプロンプトを与えることでtext to textの生成結果を取得できる

import google.generativeai as genai

# APIKEY
key = ""

genai.configure(api_key=key)

model = genai.GenerativeModel('gemini-1.5-flash')

# res = model.generate_content("こんにちは、今日の天気は?")
# # print(res)
# print(res.text)

prompt = "日本のラーメンについて語ってください"

response = model.generate_content(prompt)

print(response.text)

で実験

実行結果は

python gemini_text1.py 

の結果

日本のラーメンは、単なる食べ物ではなく、日本の食文化を代表する存在と言えるでしょう。その歴史は長く、中国から伝わった麺料理が、日本の風土や食文化と融合し、多様な地域性と個性豊かな一杯へと進化を遂げてきました。

まず、その**スープ**はラーメンの命と言えるでしょう。豚骨、鶏ガラ、魚介、醤油、味噌など、ベースとなるスープの種類は多岐にわたり、それぞれのスープに合わせた独特の製法と材料の組み合わせが、奥深い味わいを生み出しています。豚骨スープの濃厚なコク、鶏ガラのあっさりとした上品さ、魚介の繊細な旨味など、スープだけでも多様な体験が可能です。

**麺**も重要な要素です。細麺、中太麺、太麺など、スープとのバランスや食感の組み合わせが綿密に考えられています。小麦粉の種類や製法によって、食感や風味も変化し、それぞれのラーメンに最適な麺が選ばれています。ツルツルとした喉越し、コシの強さ、小麦の香りなど、麺だけでも多くのバリエーションがあります。

**具材**もラーメンの魅力を高める重要な要素です。チャーシュー、メンマ、ネギ、海苔、味玉など、定番の具材に加え、地域独特の食材や季節感を取り入れたものなど、多様な組み合わせが可能です。それぞれの具材がスープや麺と調和し、全体的なバランスを考慮した構成となっています。

そして忘れてならないのが、**地域性**です。北海道の味噌ラーメン、東京の醤油ラーメン、九州の豚骨ラーメンなど、地域によってスープの種類や麺、具材、味付けなどが異なり、まさにラーメンは日本の食文化の縮図と言えます。同じ豚骨ラーメンでも、地域によって濃厚さや油の量、風味などが大きく異なるため、食べ比べる楽しみも尽きません。

さらに近年では、**創作ラーメン**と呼ばれる新しいタイプのラーメンも数多く登場しています。伝統的なラーメンをベースに、斬新なアイデアや食材を取り入れた、独創的な一杯が次々と生み出されています。

一口に「日本のラーメン」と言っても、そのバリエーションは無限と言えるほど広大です。それぞれのラーメンに込められた作り手の情熱と、地域ごとの文化や歴史を感じながら、一杯のラーメンを味わうことは、日本の食文化に触れる貴重な体験となるでしょう。  ぜひ、様々なラーメンを味わって、あなたのお気に入りの一杯を見つけてみてください。

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
E0000 00:00:1738680923.836079 3266497 init.cc:232] grpc_wait_for_shutdown_with_timeout() timed out.

つまり

generate_content()


プロンプトを渡せばOK
変数にして入れても、そのまま直打ちしてもOK

send_message関数を使えばマルチターンのチャットにも対応
既存のチャットの内容を反映した返信を返してくれます。
初期化したい場合はstart_chat(history=[])を実行

例として

chat = model.start_chat(history=[])

response = chat.send_message(
    'しりとりしましょう。りんご')

print(response.text)
#response
#んご、ですね!では、 **ごりら**  はどうでしょう? 

response = chat.send_message(
    'ラッパ')

print(response.text)
#response
#ぱ、ですね!では**パンダ** はどうでしょう?


image to textは以下のようにupload_file関数を使います。
pathに読み込みたい画像のファイルパスを与えます

upload_file = genai.upload_file(path="/image.jpg",
                            display_name="image")

image to textの場合はgenerate_content関数の第一引数に先ほどのupload_file を与えます。第二引数にpromptを与えます。

upload_file = genai.upload_file(path="./image.jpg",
                            display_name="image")

prompt = "日本語で内容を簡潔にまとめてください"

# Prompt the model with text and the previously uploaded image.
response = model.generate_content([upload_file, prompt ])

print(response.text)

とすれば

python gemini_text1.py

を実行すると

白い背景に、オレンジ色の毛並みの小さな猫が写っています。猫はカメラの方を見ていますが、少しうつむき加減です。  かわいらしい表情をしています。

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
E0000 00:00:1738681425.059689 3274804 init.cc:232] grpc_wait_for_shutdown_with_timeout() timed out.

というように写真の説明が出る

なおプロンプトを要約する
にした場合
MP3などの音声ファイルやPDFでも要約可能

とりあえず色々できるようなので
要約には使えるかもしれない

とりあえずそれは置いておき、冷蔵庫の画像から食材の認識を進める

pip install streamlit pillow google-generativeai

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

touch ingredients.py

でファイルを作成

import streamlit as st
from PIL import Image
import io
import google.generativeai as genai

でライブラリインポート

PIL: 画像の操作に使用されるライブラリで、ユーザーがアップロードした画像を扱います。
io: バイトデータを扱うために使用され、画像をバイト形式に変換するために利用します。
google.generativeai: Googleの生成AIを利用して、食材認識とレシピ生成を行うために使用

key = ""

genai.configure(api_key=key)

model = genai.GenerativeModel('gemini-1.5-flash')


geminiの認証

st.title('AI料理研究家')
st.write("冷蔵庫の中身の写真をアップロードすると、レシピを提案します!")

st.title: Webアプリのタイトルとして「AI料理研究家」を表示します。

st.write: 説明文として「冷蔵庫の中身の写真をアップロードすると、レシピを提案します!」を表示

uploaded_image = st.file_uploader("冷蔵庫の中身の写真を選択してください", type=["jpg", "png", "jpeg"])


ユーザーが画像ファイルをアップロードできるインターフェースを作成

def recognize_ingredients(input_file):
    response = model.generate_content([input_file, "日本語で写っている食材を詳細に説明してください"])
    return response.text

recognize_ingredients: 画像から食材を認識するための関数

input_fileとして画像をGoogle Generative AIに送り、「日本語で写っている食材を説明してください」というプロンプトを追加

generate_content: モデルを使用して指定されたプロンプトに基づき、食材を認識します。返されるのは食材のリストやテキストになる

次にレシピ提案メソッドの作成

def suggest_recipes(ingredients):
    prompt = f"以下の食材だけを使った料理とそのレシピをいくつか提案してください: " + ingredients
    
    response = model.generate_content(prompt)

suggest_recipes: 認識された食材に基づいてレシピを生成する関数
ngredientsに基づいてプロンプトを作成し、AIに「これらの食材だけを使った料理とレシピを提案してください」と指示
再度、generate_contentを使用して、レシピを生成

    if response:
        return response.text  
    else:
        st.error("レシピの生成に失敗しました")
        return None

レスポンスが正常に返ってきた場合、その内容を返し
何らかのエラーが発生した場合には、Streamlitを使ってエラーメッセージを表示

if uploaded_image is not None:
    image = Image.open(uploaded_image)
    st.image(image, caption="Uploaded Image", use_column_width=True)

画像がアップロードされた場合、その画像を開き、Streamlitのインターフェースに表示
captionとして「Uploaded Image」を設定し、画像を表示させる領域の幅に合わせてリサイズ

    img_byte_arr = io.BytesIO()
    image.save(img_byte_arr, format='PNG')
    img_byte_arr = img_byte_arr.getvalue()

アップロードされた画像をバイトデータに変換
BytesIO()を使って、画像データをバイト形式に保存し、それをimg_byte_arrに取得

    with open("uploaded_fridge_image.png", "wb") as f:
        f.write(img_byte_arr)
    input_file = genai.upload_file(path=r"<pythonファイルが置かれたフォルダパス>\uploaded_fridge_image.png",
                            display_name="image")

MacOSの場合は以下のよう

input_file = genai.upload_file(path="/Users/test/python/uploaded_fridge_image.png",
                            display_name="image")

変換した画像をwrite関数でローカルに保存します。ファイル名は「uploaded_fridge_image.png」
genai.upload_file: Google Generative AIのために画像をアップロード
このために
パスと表示名を指定

の部分は
Pythonファイルを保存しているフォルダのパスで置き換える

一度手順6のコマンドでStreamlitを実行し(途中でエラーになりますが画像は保存されます)、画像を保存してからその画像をVScodeで右クリックしてパスをコピーすると楽

    with st.spinner('食材を認識中...'):
        ingredients = recognize_ingredients(input_file)

画像から食材を認識している間、st.spinnerで「食材を認識中…」というローディングメッセージを表示

recognize_ingredients関数を呼び出して、食材を認識
    if ingredients:
        st.write("認識された食材:")
        st.write(ingredients)

食材が正常に認識された場合、「認識された食材:」というメッセージを表示し、具体的にどの食材が認識されたかをリスト形式で表示

        with st.spinner('レシピを提案中...'):
            recipes = suggest_recipes(ingredients)

認識された食材に基づいてレシピを生成する際に、「レシピを提案中…」というローディングメッセージを表示します。
suggest_recipes関数を呼び出し、レシピを提案

        if recipes:
            st.write("こちらがいくつかのレシピ提案です:")
            st.write(recipes)
        else:
            st.error("レシピが見つかりませんでした。")

提案されたレシピがあれば、「こちらがいくつかのレシピ提案です:」というメッセージを表示し、生成されたレシピをリストとして表示

レシピが生成されなかった場合にはエラーメッセージを表示

input_file = genai.upload_file(path="/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png",
                            display_name="image")

の部分は出力先なので
この設定にした

で実行

streamlit run ingredients.py 

  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://192.168.1.17:8501

  For better performance, install the Watchdog module:

  $ xcode-select --install
  $ pip install watchdog
            
2025-02-08 05:04:21.476 Uncaught app execution
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
  File "/Users/snowpool/aw10s/gemini/ingredients.py", line 43, in <module>
    input_file = genai.upload_file(path="/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png",
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/files.py", line 85, in upload_file
    response = client.create_file(
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/client.py", line 114, in create_file
    media = googleapiclient.http.MediaFileUpload(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
    return wrapped(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/http.py", line 594, in __init__
    self._fd = open(self._filename, "rb")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png'
2025-02-08 05:05:16.750 The `use_column_width` parameter has been deprecated and will be removed in a future release. Please utilize the `use_container_width` parameter instead.
2025-02-08 05:05:19.701 Uncaught app execution
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
  File "/Users/snowpool/aw10s/gemini/ingredients.py", line 43, in <module>
    input_file = genai.upload_file(path="/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png",
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/files.py", line 85, in upload_file
    response = client.create_file(
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/client.py", line 114, in create_file
    media = googleapiclient.http.MediaFileUpload(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
    return wrapped(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/http.py", line 594, in __init__
    self._fd = open(self._filename, "rb")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png'
2025-02-08 05:05:30.335 The `use_column_width` parameter has been deprecated and will be removed in a future release. Please utilize the `use_container_width` parameter instead.
2025-02-08 05:05:31.820 Uncaught app execution
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
  File "/Users/snowpool/aw10s/gemini/ingredients.py", line 43, in <module>
    input_file = genai.upload_file(path="/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png",
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/files.py", line 85, in upload_file
    response = client.create_file(
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/client.py", line 114, in create_file
    media = googleapiclient.http.MediaFileUpload(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
    return wrapped(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/googleapiclient/http.py", line 594, in __init__
    self._fd = open(self._filename, "rb")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/Users/snowpool/aw10s/gemini/uploaded_fridge_image.png'

となった

ファイルが存在しないとなるので

mv ~/Downloads/PXL_20250205_220637199.jpg ./uploaded_fridge_image.png

で撮影した写真を
Downloadsフォルダにある PXL_20250205_220637199.jpg を mv コマンドでカレントディレクトリに uploaded_fridge_image.png にリネームして移動

再度実行したら

streamlit run ingredients.py

  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://192.168.1.17:8501

  For better performance, install the Watchdog module:

  $ xcode-select --install
  $ pip install watchdog
            
2025-02-08 05:15:18.391 The `use_column_width` parameter has been deprecated and will be removed in a future release. Please utilize the `use_container_width` parameter instead.
2025-02-08 05:15:30.390 Uncaught app execution
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
  File "/Users/snowpool/aw10s/gemini/ingredients.py", line 47, in <module>
    ingredients = recognize_ingredients(input_file)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/aw10s/gemini/ingredients.py", line 19, in recognize_ingredients
    response = model.generate_content([input_file, "日本語で写っている食材を詳細に説明してください"])
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/generativeai/generative_models.py", line 331, in generate_content
    response = self._client.generate_content(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/ai/generativelanguage_v1beta/services/generative_service/client.py", line 830, in generate_content
    response = rpc(
               ^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/gapic_v1/method.py", line 131, in __call__
    return wrapped_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/retry/retry_unary.py", line 293, in retry_wrapped_func
    return retry_target(
           ^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/retry/retry_unary.py", line 153, in retry_target
    _retry_error_helper(
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/retry/retry_base.py", line 212, in _retry_error_helper
    raise final_exc from source_exc
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/retry/retry_unary.py", line 144, in retry_target
    result = target()
             ^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/timeout.py", line 120, in func_with_timeout
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/google/api_core/grpc_helpers.py", line 78, in error_remapped_callable
    raise exceptions.from_grpc_error(exc) from exc
google.api_core.exceptions.ResourceExhausted: 429 Resource has been exhausted (e.g. check quota).

となった
今度は

google.api_core.exceptions.ResourceExhausted: 429 Resource has been exhausted (e.g. check quota).

の部分から
Google の API(この場合は Generative AI の API)で利用できるリソースやクォータが使い果たされた、もしくは上限に達しているのが原因

無料プランなので
1分以上経過すればリソースが回復するので
再度実行したらできた

実行結果は

認識された食材:
はい、冷蔵庫の中身にある日本語で書かれた食材を詳細に説明します。
上段:
* 卵: パックに入った鶏卵。個数は不明ですが、少なくとも数個入っています。
* パン粉、または何かしら粉状のもの: 透明なビニール袋に入っています。内容物の特定は難しいです。
* 様々な食品: 複数の袋やパッケージに入った食品が詰め込まれており、個々の商品の特定は困難です。日本語のラベルが見えますが、商品名は判読できません。
* 雪印スノーブランドバター: ピンク色のパッケージのバター。
2段目:
* BIOブランドヨーグルト: 少なくとも2個のヨーグルト。プレーン味と、おそらく加糖味があります。
* その他ヨーグルト: いくつかのヨーグルト容器が見えますが、ブランドや味は不明です。
* その他食品: 複数の小さな容器やパックに入った食品があり、内容物は判別できません。
* 野菜や漬物など: 青色のプラスチック容器に入った野菜、おそらく漬物類が入っています。
3段目:
* 生みそ: 「生みそ」と大きく書かれた容器に入った味噌。中甘口のようです。
* その他食品: いくつかの小さな容器やパックに入った食品が確認できますが、詳細は不明です。
* クリームチーズ: クラフトのクリームチーズと思われる容器が見えます。
* ご飯: 透明なプラスチック容器に入ったご飯が、少なくとも2つあります。
* 調理済みの料理: ラップで覆われたお皿に料理が残っています。内容は不明です。
4段目:
* 赤い鍋: 蓋付きの赤い鍋が、中に何か入っている状態で置かれています。
* バナナ: ラップで覆われたお皿に、バナナが1本入っています。
* その他食品: いくつかの小さな容器やパックに入った食品が見えますが、詳細は不明です。
* 豚肉の塊: 透明なプラスチック容器に入った、生の豚肉の塊と思われるものがあります。
下段:
* 牛乳: 明治の牛乳。900mlパック。
* ジュース: コープブランドのアップルジュースとグレープジュースのパック。他にもいくつかジュースのパックが見えますが、味は不明です。
全体的に、冷蔵庫は様々な食品でかなりいっぱい詰まっており、整理整頓されていない状態です。多くの食品の内容物が判別できないのが現状です。
こちらがいくつかのレシピ提案です:
冷蔵庫の中身が不明瞭な部分が多いので、確実にできる料理と、可能性のある料理を分けて提案します。
確実に作れる料理 (材料の特定が可能なもの):
1. 豚みそ炒めご飯:
    * 材料: 豚肉、生みそ、ご飯、雪印スノーブランドバター、卵(あれば)、冷蔵庫にある野菜(漬物でも可、刻んで使用)。
    * レシピ: 豚肉を一口大に切り、バターで炒め、味噌を溶き入れ、ご飯を加えて炒め合わせる。卵を割り入れて混ぜても良い。野菜や漬物を加えて炒め、塩胡椒で味を調える。
2. ヨーグルト和風ドレッシングの豚しゃぶサラダ風:
    * 材料: 豚肉、BIOブランドヨーグルト(プレーン)、冷蔵庫にある野菜(漬物でも可、細かく刻む)、ご飯(少量)、雪印スノーブランドバター(少量、風味付け)。
    * レシピ: 豚肉をしゃぶしゃぶ風に軽く茹でる。ヨーグルトをベースに、塩、醤油、少しのバターで和風ドレッシングを作る。茹でた豚肉と野菜、ご飯をドレッシングで和える。
可能性のある料理 (材料の推測が必要なもの):
1. パン粉を使った揚げ物: 粉状のものがパン粉なら、豚肉をパン粉で揚げることができます。卵を溶いて衣に混ぜると美味しくなります。野菜があれば一緒に揚げても良いでしょう。しかし、油がないので、バターで焼くか、少量の油で揚げる必要があります。
2. クリームチーズを使った何か: クリームチーズとヨーグルト、パン粉(もしパン粉なら)、卵で何か作れる可能性があります。例えば、クリームチーズとヨーグルトを混ぜて、パン粉を混ぜて焼いたり、卵と混ぜて焼いたり。詳細は不明なため、レシピは提示できませんが、可能性はあります。
3. 冷蔵庫の残り物を使ったスープ: 冷蔵庫にある調理済み料理と、野菜、牛乳、ヨーグルトなどを活用して、雑炊風スープを作ることができます。味見をして、塩や味噌で味を調える必要があります。
重要な注意点:
* 多くの食材が不明なため、上記のレシピはあくまで推測に基づいています。
* 食材の賞味期限を確認し、安全な食材のみを使用してください。
* 冷蔵庫の中身を整理して、食材を把握してから料理を作る方が効率的です。
冷蔵庫の整理と食材の特定ができれば、もっと多くの料理のバリエーションを提案できます。 写真があれば、より正確な提案が可能になります。

Llama 3.2とNeo4jでローカルGraphRAG環境を構築

Llama 3.2とNeo4jでローカルGraphRAG環境を構築
を参考にしてみる

ソースが
https://github.com/satken2/local-llama-graphrag
にあるので
Git clone してみる

この中の
graph_generation.py
で作成しているので
コードを調べることにするのと

graph_retrieval.py
でリレーションをしているので
おそらく分割が必要と思われる

有料プランでならGPTとか使えばできるけど
毎回トークンを使用するのは無理
ってそもそもllama3.2なので課金は存在しない
むしろ試すならcolabで行けるはず

まずはgit clone を Mac でやってモデルをelzaにして動かしてみる

解説は
https://qiita.com/satken2/items/ea3ddc5f273d43cd63d4?utm_source=chatgpt.com
もみながら調べていく

Graph RAGではナレッジグラフを活用することで、データ間の関係性や全体の文脈を考慮したコンテキストの取得をすることを試みる技術

これまでのRAGではベクトルデータベースの中からプロンプトに合致しそうなデータを局所的に取得してきて、
それをコンテキストとして言語モデルに与えていました。
ですがこの方法は元となるデータ全体の文脈をあまり考慮できておらず、
結果としてあまり一致度の高くないデータを取得してしまって回答精度の低下に繋がる場合がありました。

* CPU: Core i5 12400F
* メモリ: DDR4 32GB
* GPU : RTX 3070 (8GB)
* OS: Windows 11
* Python: 3.11.0

が参考スペック

とりあえず M1MacbookAir 16GBで試してみる

グラフデータベースとしてNeo4jをDockerで起動
メインとなる言語モデルはオープンの言語モデルであるLlama3.2の3BモデルをOllamaで動作させ、
LangChainでグラフデータとの連携とかプロンプトの管理をしています。
* Llama 3.2 (3B)
* Ollama
* Neo4j
* langchain

とりあえず実験終了したらOllamaを日本語対応可能な
elyza:jp8b
にしてみる

Neo4jのCypherクエリ言語を拡張するライブラリapoc-coreを使用は
既にセットアップしたのでそのまま使う

docker compose up -d

で起動

次に
Wikipediaのハリーポッター(人物)の記事をRAGで検索したい
これはリポジトリに含まれているので
harrypotter.txt
の内容をそのまま使う

ちなみに内容は

Harry James Potter is a fictional character in the Harry Potter series of novels by J. K. Rowling. The plot of the seven-book series chronicles seven years in the life of the orphan Harry, who, on his eleventh birthday, learns he is a wizard. He attends Hogwarts, a school of magic, where he receives guidance from the headmaster Albus Dumbledore and becomes friends with Ron Weasley and Hermione Granger. Harry learns that during his infancy, the Dark wizard Lord Voldemort murdered his parents but was unable to kill him as well. The plot of the series revolves around Harry's struggle to adapt to the wizarding world and defeat Voldemort.

Harry is regarded as a fictional icon and has been described by many critics, readers, and audiences as one of the greatest literary and film characters of all time. He is portrayed by Daniel Radcliffe in all eight Harry Potter films. Harry also appears in the play Harry Potter and the Cursed Child, which has been produced on stage since 2016.

According to Rowling, the idea for Harry Potter and the novel series came to her while she was waiting for a train in 1990. She first conceived of Harry as a "scrawny, black-haired, bespectacled boy" who is unaware that he is a wizard.[2] While developing ideas for the first book, she decided to make Harry an orphan who attends a boarding school called Hogwarts. She explained in a 1999 interview with The Guardian: "Harry had to be an orphan—so that he's a free agent, with no fear of letting down his parents, disappointing them ... Then there's the security. Having a child of my own reinforces my belief that children above all want security, and that's what Hogwarts offers Harry."[3]

The death of Rowling's mother in December 1990 inspired her to write Harry as a boy longing for his dead parents. She explained that his anguish became "much deeper" and "much more real" than in earlier drafts because she related to it herself.[2] In a 2000 interview, Rowling said that the character Wart in T. H. White's novel The Once and Future King is Harry's "spiritual ancestor".[4] Although Rowling gave Harry her own birth date of 31 July, she maintained that he is not directly based on any real-life person. She explained that he came "out of a part of me".[5]

Rowling has claimed that Harry is a suitable real-life role model for children. She said, "The advantage of a fictional hero or heroine is that you can know them better than you can know a living hero, many of whom you would never meet ... if people like Harry and identify with him, I am pleased, because I think he is very likeable."[6]

Novels
Harry Potter and the Philosopher's Stone
Harry is first introduced in Harry Potter and the Philosopher's Stone (1997) as an orphan living with his abusive aunt and uncle, Vernon and Petunia Dursley, and their bullying son, Dudley. On his eleventh birthday, Harry discovers he is a wizard when Rubeus Hagrid brings him an acceptance letter from Hogwarts School of Witchcraft and Wizardry. Harry learns that his parents, James and Lily Potter, were murdered by a powerful Dark wizard, Lord Voldemort, who also tried to kill Harry. However, Voldemort's killing curse bounced back and apparently destroyed him, leaving Harry with a lightning-bolt-shaped scar on his forehead. Due to his miraculous survival, Harry became famous among wizards.

Hagrid takes Harry to Diagon Alley to shop for school supplies. Harry buys a wand, and the proprietor tells him that his wand and Voldemort's wand contain feathers from the same phoenix. At the end of the summer, Harry boards the Hogwarts Express, where he befriends Ron Weasley and meets Hermione Granger. When he arrives at Hogwarts, he is assigned to Gryffindor House by the Sorting Hat. During the school year, he receives guidance from the Transfiguration professor Minerva McGonagall and the headmaster Albus Dumbledore. He develops animosity towards a fellow first-year named Draco Malfoy, and becomes increasingly wary of the Potions professor, Severus Snape. Harry and Ron become friends with Hermione after they save her from a troll, and Harry becomes the youngest Seeker on the Gryffindor Quidditch team in a century.

During Christmas holidays, Harry receives an anonymous gift – his father's invisibility cloak. Aided by the cloak, he is able to explore the school freely. He, Ron and Hermione decide to enter a trapdoor they discovered one night, which is guarded by an enormous three-headed dog. They believe a valuable magical object, the Philosopher's Stone, lies beyond the door. After they navigate a series of obstacles, Harry faces Professor Quirrell, whose body has been possessed by Voldemort. When Harry obtains the Stone, Quirrell tries to kill him. However, Quirrell's flesh burns upon contact with Harry's skin, and Harry passes out.

Harry awakens in the Hogwarts infirmary, where Dumbledore explains that Harry was protected by his mother's love, which also killed Quirrell. Voldemort survived, and the Philosopher's Stone was destroyed. During the school's end-of-year feast, Gryffindor is awarded the House Cup thanks to the many points Harry and his friends obtained from their adventure.

Harry Potter and the Chamber of Secrets
In Harry Potter and the Chamber of Secrets (1998), Muggle-born students are being Petrified in the Hogwarts castle. Some students suspect that Harry is behind the attacks, which alienates him from his peers. Harry learns that he shares Voldemort's ability to communicate with snakes, and he begins to question whether he is worthy of Gryffindor House. After Ron's sister Ginny disappears, Harry enters the Chamber of Secrets to rescue her. He battles Tom Riddle, a version of Voldemort that lives in a diary which had possessed Ginny. In addition to Riddle, Harry encounters the basilisk which has been attacking students. To defeat it, Harry summons the Sword of Gryffindor from the Sorting Hat. After Harry triumphs over the basilisk and saves Ginny, Dumbledore reassures him that he is worthy of Gryffindor.

Harry Potter and the Prisoner of Azkaban
Near the beginning of Harry Potter and the Prisoner of Azkaban (1999), Harry becomes angry after his Aunt Marge disparages his parents. He accidentally inflates her with magic, then runs away from home. As he re-enters the wizarding community, he learns about Sirius Black, a notorious killer who escaped from the wizard prison Azkaban. On the train to Hogwarts, Harry is assaulted by Dementors, which cause him to relive his worst memories. During a Care of Magical Creatures lesson with Hagrid, Draco is injured after provoking a hippogriff named Buckbeak. As a result, the Ministry of Magic sentences Buckbeak to death. After Harry has more encounters with Dementors, he asks the new Defence Against the Dark Arts professor, Remus Lupin, to help him ward them off. Lupin teaches Harry the Patronus charm, which Harry struggles to master.

While in the village of Hogsmeade, Harry learns that his parents were betrayed by Sirius, their friend and Harry's godfather, and that Sirius also killed Peter Pettigrew. After Ron is taken to the Shrieking Shack by a wild dog, Harry and Hermione give chase and realize the dog is Sirius in Animagus form. Lupin arrives, and he and Sirius explain that Sirius did not kill Pettigrew. They also reveal that Ron's pet rat, Scabbers, is Pettigrew. Lupin then transforms into a werewolf and injures Sirius, and Pettigrew escapes amid the chaos. A swarm of Dementors appear, but they are driven off by a Patronus cast by a mysterious figure.

Awakening in the school infirmary, Harry and Hermione learn that Sirius is being held captive in Hogwarts and is awaiting the Dementor's Kiss. At Dumbledore's advice, they use a Time-Turner to travel back in time to the moment before Buckbeak's execution. After they save the hippogriff, Harry notices the past versions of himself, Hermione, and Sirius being attacked by Dementors. He conjures a strong Patronus to fight them off, then realizes that he was the mysterious figure who appeared earlier. Harry and Hermione free Sirius, who flies away on Buckbeak, still a wanted fugitive. Harry and Hermione then return to their original timeline.

Harry Potter and the Goblet of Fire
In Harry Potter and the Goblet of Fire (2000), Harry is mysteriously chosen by the Goblet of Fire to compete in the dangerous Triwizard Tournament, even though another Hogwarts champion, Cedric Diggory, has already been selected. During the Tournament's final challenge, Harry and Cedric are transported to a graveyard, where Cedric is killed by Peter Pettigrew. Voldemort, aided by Pettigrew, uses Harry's blood to resurrect his body. Harry then duels Voldemort, and their wands connect. Spirit echoes of Voldemort's victims, including Harry's parents, emerge from his wand. The spirits protect Harry as he escapes to Hogwarts with Cedric's body.

Rowling said it was necessary to depict violence and death in this novel because it allows Harry's bravery and compassion to show. She said that Harry's effort to prevent Cedric's body from falling into Voldemort's hands was based on the scene in Homer's Iliad where Achilles retrieves the corpse of his friend Patroclus from his enemy Hector.[7] She asserted that Goblet of Fire marks a turning point in Harry's life. She said Harry has been "very protected until now" but in this novel he is no longer protected.[8]

Harry Potter and the Order of the Phoenix
In Harry Potter and the Order of the Phoenix (2003), the Ministry of Magic wages a smear campaign against Harry and Dumbledore, disputing their claims that Voldemort has returned. The Ministry appoints Dolores Umbridge as the new Defence Against the Dark Arts teacher. Because the Ministry suspects that Dumbledore is building a student army, Umbridge refuses to teach students practical defensive magic. Harry, Hermione and Ron subsequently form a secret group called Dumbledore's Army to teach students defensive spells. The group is disbanded, however, when one student informs Umbridge about it. To shield his students from punishment, Dumbledore falsely claims that Dumbledore's Army was his idea. As a result, he is ousted from his position as headmaster and replaced with Umbridge.

During this time, Harry's increasingly angry and erratic behaviour nearly estranges him from Ron and Hermione. He suffers another emotional blow when his godfather Sirius is killed by the Death Eater Bellatrix Lestrange during a battle at the Department of Mysteries. Also present is Voldemort, who tries to possess Harry so Dumbledore will kill him. This ruse fails, and Harry and Dumbledore thwart Voldemort's plan to steal an important prophecy. Later, Dumbledore explains to Harry that Voldemort chose Harry as his equal, and that either he or Voldemort must eventually kill the other. A subplot of the novel involves Harry's romance with Cho Chang, which quickly unravels.[9]

Rowling said she put Harry through extreme emotional stress in Order of the Phoenix to show his emotional vulnerability and humanity, which contrast with Voldemort's inhumanity.[10]

Harry Potter and the Half-Blood Prince
In Harry Potter and the Half-Blood Prince (2005), Dumbledore brings Harry along as he attempts to persuade Horace Slughorn to rejoin the Hogwarts faculty as the Potions professor. Harry excels in Slughorn's class after he begins using an old textbook that once belonged to someone known as "The Half-Blood Prince". Outside of class, Dumbledore uses the Pensieve to teach Harry about Voldemort's life and his rise to power. Harry and Dumbledore learn that Voldemort created six Horcruxes to gain immortality, and that they must be destroyed before Voldemort can be killed. Harry repeatedly warns Dumbledore that Draco Malfoy is an agent of Voldemort, but Dumbledore refuses to take action against Draco. He tells Harry that he already knows more about what is happening than Harry does. During the course of the novel, Harry becomes romantically involved with Ginny.

Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he is attacked by Inferi. They try to drown Harry, but Dumbledore rescues him. Dumbledore and Harry then return to the Hogwarts Astronomy Tower with the Horcrux. Dumbledore asks Harry not to interfere in the events that are about to take place, and puts him in a body-binding curse. Immobilized, Harry is unable to intervene as Snape arrives and kills Dumbledore. Later, Harry ends his relationship with Ginny to protect her from Voldemort.

Rowling said that Harry's tumultuous puberty in the novel is based on her own difficult teenage years and those of her sister.[11][12] She said that after Half-Blood Prince Harry becomes "battle-hardened" and is ready to fight and take revenge against Voldemort and Snape.[13]

Harry Potter and the Deathly Hallows
Harry, Ron, and Hermione leave Hogwarts in Harry Potter and the Deathly Hallows (2007) to search for and demolish Voldemort's four remaining Horcruxes. They must be destroyed with basilisk venom, the Sword of Gryffindor, or some other powerful means. Ron, Hermione, Vincent Crabbe and Neville Longbottom each destroy one of these Horcruxes. After a battle at Hogwarts, Harry goes into the Forbidden Forest to meet Voldemort, who uses the Elder Wand to cast the Killing Curse on Harry. The curse stuns Harry into a deathlike state, and he finds himself in an ethereal realm with Dumbledore. The deceased headmaster tells Harry that he is a Horcrux that Voldemort unknowingly created. There was a part of Voldemort's soul inside Harry, but it was destroyed by the curse Voldemort cast moments ago. Harry subsequently returns to the living world and duels with Voldemort. The Dark Lord uses the Killing Curse again, but it rebounds and kills him. Harry, not Voldemort, was the true master of the Elder Wand, and the wand did not wish to harm its master. Harry decides to leave the Elder Wand in Dumbledore's tomb and the Resurrection Stone hidden in the forest, but he keeps the Invisibility Cloak.

In the epilogue, set nineteen years after Voldemort's death, Harry and Ginny are a married couple and have three children: James Sirius Potter, Albus Severus Potter and Lily Luna Potter. Albus is departing for his first year at Hogwarts, and is worried that he will be sorted into Slytherin. Harry explains to him that not all Slytherins are bad.

Harry Potter and the Cursed Child
Harry Potter and the Cursed Child is a play written by Jack Thorne from a story by Thorne, J. K. Rowling and John Tiffany. It takes place nineteen years after the events of Deathly Hallows. In the play, Harry is Head of the Department of Magical Law Enforcement at the Ministry of Magic. The story focuses on Harry's son, Albus Severus Potter.

Film adaptations
Harry is portrayed by Daniel Radcliffe in all eight Harry Potter film adaptations. Radcliffe was asked to audition for the role in 2000 by David Heyman, who was producing the first film in the series. Radcliffe met Heyman while attending a London play called Stones in His Pockets.[14][15]

In a 2007 interview with MTV, Radcliffe described the development of his character during the course of the series: "That's what the films are about for me: a loss of innocence, going from being a young kid in awe of the world around him, to someone who is more battle-hardened by the end of it."[16] Radcliffe said that after the death of Cedric Diggory in Goblet of Fire, Harry experiences survivor's guilt and feels immense loneliness. Because of this, Radcliffe talked to a bereavement counsellor to help him prepare for the role.[16] Radcliffe stated that the most repeated question he has been asked is how Harry Potter has influenced his own life, to which he regularly answers it has been "fine", and that he did not feel pigeonholed by the role, but rather sees it as a huge privilege to portray Harry.[17]

In 2011, Empire magazine ranked Radcliffe's Harry Potter as the 36th greatest film character of all time. The magazine published another character ranking in 2018, this time placing Harry at 67th.[18]


Outward appearance

A stylized image of Harry and the two most instantly recognizable features of his appearance: his lightning bolt scar and his circular glasses.
In the novels, Harry is described as having his father's perpetually untidy black hair, his mother's bright green eyes, and a lightning bolt-shaped scar on his forehead. He is short and skinny for his age, with a thin face and "knobbly" knees, and he wears Windsor glasses. When asked about the meaning behind Harry's scar, Rowling said she wanted him to be "physically marked by what he has been through". She said the scar is "an outward expression of what he has been through inside".[19] Rowling also said that Harry's glasses are the clue to his vulnerability.[20] Harry eventually grows taller, and by the final book is almost the height of his father, and is described as tall by other characters.[21]

Personality
According to Rowling, Harry is strongly guided by his own conscience, and has a keen feeling of what is right and wrong. Having "very limited access to truly caring adults", Rowling said, Harry "is forced, for such a young person, to make his own choices".[22] He "does make mistakes", she conceded, but in the end, he does what his conscience tells him to do. According to Rowling, one of Harry's pivotal scenes came in the fourth book when he protects his dead schoolmate Cedric Diggory's body from Voldemort, because it shows he is brave and selfless.[7]

Rowling has stated that Harry's character flaws include anger and impulsiveness; however, Harry is also innately honourable.[23][24] "He's not a cruel boy. He's competitive, and he's a fighter. He doesn't just lie down and take abuse. But he does have native integrity, which makes him a hero to me. He's a normal boy but with those qualities most of us really admire."[25] For the most part, Harry shows humility and modesty, often downplaying his achievements; though he uses a litany of his adventures as examples of his maturity early in the fifth book. However, these very same accomplishments are later employed to explain why he should lead Dumbledore's Army, at which point he asserts them as having just been luck, and denies that they make him worthy of authority. After the seventh book, Rowling commented that Harry has the ultimate character strength, which not even Voldemort possesses: the acceptance of the inevitability of death.

Magical abilities and skills
Throughout the series, Harry is described as a gifted wizard. He has a particular talent for flying, which manifests itself in Harry Potter and the Philosopher's Stone the first time he tries it, and gets him a place on a Quidditch team one year before the normal minimum joining age. He captains it in his sixth year. In his fourth year, Harry is able to confront a dragon on his broomstick.

Harry is also gifted in Defence Against the Dark Arts, in which he becomes proficient due to his repeated encounters with Voldemort and various monsters. In his third year, Harry becomes able to cast the very advanced Patronus Charm, and by his fifth year he has become so talented at the subject that he is able to teach his fellow students in Dumbledore's Army, some even older than him how to defend themselves against Dark Magic. At the end of that year, he achieves an 'Outstanding' Defence Against the Dark Arts O.W.L., something that not even Hermione achieved.

Harry also has the unusual ability to speak and understand "Parseltongue", a language associated with Dark Magic. This, it transpires, is because he harbours a piece of Voldemort's soul. He loses this ability after the part of Voldemort's soul inside him is destroyed at the end of The Deathly Hallows. However, in the events of Harry Potter and the Cursed Child, it was revealed that he had not lost the ability to recognise or speak the language when he encountered Delphini, Voldemort's daughter, who was trying to use his son and Draco Malfoy's son Scorpius to fulfill a prophecy that could guarantee the return of Voldemort by changing time.

Possessions
Harry's parents left him a large pile of gold in the wizarding bank, Gringotts. After Sirius' death later in the series, all of his remaining possessions are also passed along to Harry, including Number Twelve, Grimmauld Place, and Sirius's vast amount of gold were transferred into Harry's account at Gringotts.

Among the school items Harry purchases in Diagon Alley after discovering his gold inheritance is his first wand, an 11-inch-long holly and phoenix feather model that he learns is the twin of Voldemort's wand, as the feathers that both wands contain as their cores both comes from Fawkes, the phoenix that Dumbledore keeps as a pet in his office until his death in Half-Blood Prince.[26] Harry's wand is broken in Deathly Hallows. For a time, he borrows Hermione's wand, and later steals Draco's. With his defeat of Voldemort at the end of the series, he comes into the possession of the Elder Wand, but uses it only to repair his holly wand, before returning it to Dumbledore's tomb, from which Voldemort had stolen it. In the film version of Deathly Hallows Part 2, Harry destroys the Elder Wand.

Harry also inherits indirectly two of his father's prized possessions. One is the Marauder's Map, given to him by interim owners Fred and George Weasley, which endows Harry with comprehensive knowledge of Hogwarts' facilities, grounds, and occupants. The other is his father's invisibility cloak, given to him by Dumbledore, which eventually proves Harry's descent from the Peverell family. Harry uses these tools both to aid in excursions at school and to protect those he cares about; the Invisibility Cloak, in particular, can hide two full-grown people. If three fully-grown people hide under the cloak their feet will be visible.

Throughout the majority of the books, Harry also has a pet owl named Hedwig, used to deliver and receive messages and packages. Hedwig is killed in the seventh book, about which Rowling says: "The loss of Hedwig represented a loss of innocence and security. She has been almost like a cuddly toy to Harry at times. I know that death upset a lot of people!"[23] As a Quidditch player, Harry has owned two high-quality brooms. The first, a Nimbus Two Thousand, was procured for him by Professor Minerva McGonagall when Harry was added to Gryffindor's Quidditch team despite being a first-year student. This broom was destroyed by the Whomping Willow during a match in Harry's third year. It was replaced by a Firebolt, an even faster broom, purchased for Harry by Sirius; however, as Sirius was believed to be trying to murder Harry at the time, the broom was subjected to stringent security inspections before Harry was allowed to ride it. Harry used it throughout his Hogwarts career until it, along with Hedwig, was lost during the July escape from Privet Drive in the final book.

Harry also owns a mokeskin pouch, or small 'bag' that is used for storing items, which no one but the owner can get out. Harry uses the pouch throughout the course of Deathly Hallows to keep several objects such as the Marauder's Map, a shard of the magical mirror given to him by his god-father Sirius, the fake Horcrux locket that had belonged to Sirius's brother R.A.B. (Regulus Arcturus Black), the Snitch bequeathed to him by Dumbledore, containing the Resurrection Stone that had previously been set into Voldemort's grandfather Marvolo Gaunt's signet ring, which Harry discovers is actually the second Hallow, a letter from his mother to Sirius with part of a photo (of him and his father, James), and eventually, his own broken wand (which Harry later repairs with the Elder Wand).

Family
In the novels and films, Harry is the only child of James and Lily Potter, orphaned as an infant. Rowling made him an orphan because she felt an orphan would be interesting to write about.[3] However, after her mother's death, Rowling wrote Harry as a child longing to see his dead parents again, incorporating her own anguish into him. Harry is categorised as a "half-blood" wizard in the series, because although both his parents were magical, Lily was "Muggle-born", and James was a pure-blood.

Harry's aunt and uncle kept the truth about his parents' deaths from Harry, telling him that they had died in a car crash.[2] James Potter is a descendant of Ignotus Peverell, the third of the three original owners of the Deathly Hallows, and thus so is Harry, a realisation he makes during the course of the final book. The lineage continues at the end of the saga through his three children with Ginny: James Sirius Potter, Albus Severus Potter and Lily Luna Potter.

Reception
In 2002, Book magazine ranked Harry Potter No. 85 among the "100 Best Fictional Characters since 1900".[27] Entertainment Weekly ranked Harry Potter number two on its 2010 "100 Greatest Characters of the Last 20 Years" list, saying "Long after we've turned the last page and watched the last end credit, Harry still feels like someone we know. And that's the most magical thing about him."[28] UGO Networks listed Harry as one of their best heroes of all time, who said that "Harry is a hero to the often oppressed and downtrodden young fan boys and girls out there, who finally have an icon that is respected and revered by those who might otherwise look down on robe-wearing and wand waving as dork fodder".[29] Harry Potter was also ranked number thirty-six on Empire's 2008 list of "100 Greatest Movie Characters of All Time".[30] IGN said that Harry Potter was their favourite Harry Potter character, calling him a "sympathetic figure" and saying in response to his fights against Voldemort that "everybody loves an underdog story of good vs. evil".[31]

On the other hand, he has received criticism. In 2010, he was voted the 35th "Worst Briton" in Channel 4's "100 Worst Britons We Love to Hate" programme.[32] In The Irish Times, Ed Power called Harry an "anointed cherub" who is "told he is special from the very outset" before he has any "winning attributes". Power said Harry is "celebrated as an overachiever before he's achieved anything".[33] Lannah Marshall called Harry "a bland character". She further described him as "an audience surrogate, or what I call a 'puppet protagonist'. A puppet protagonist is a main character with dull, limited personality, enabling the audience to step inside the role and use their imagination to fill in the rest.[34]

Despite blowback from some Christian fundamentalists critical of Rowling's usage of witchcraft and magic in the series,[35] other Christian critics, including Rev. John Killinger, have argued that Potter is a Christ figure in the series. Killinger opined in 2002 that "J.K. Rowling has written the Christ story of the 21st century, and it's wonderful that she has attained such a magnificent following worldwide." He noted several allusions to Jesus in Potter's character arcs in Philosopher's Stone and Chamber of Secrets.[36] Rowling herself later admitted that the Gospel story inspired that of Harry Potter, especially with his ultimate sacrifice in Deathly Hallows and apparent death before returning to defeat Voldemort once and for all.[37]

既にOllamaのセットもしてあるので
これも飛ばす

次にRAGの構築のため
グラフデータベースを構築

Neo4jの初期化とLlama3.2の読み込み
No4jとOllamaはどちらもlangchainを使う

from langchain_ollama import ChatOllama
from langchain_community.graphs import Neo4jGraph

# Initialize LLM model
llm = ChatOllama(model="llama3.2:1b", temperature=0, format="json")

# Initialize the Neo4j graph
graph = Neo4jGraph()

このモデルの部分を
llama3.2:1b
から別のモデルにすれば行けるはず
elyza:jp8b
にしてみる

RAGの構築用プログラムgraph_generator.pyを作成していく

テキストデータの読み込みと分割
最初に準備したWikipediaの記事 harrypotter.txtを読み込んで、
RecursiveCharacterTextSplitterを使ってチャンクに分割する

しかし
このテキスト分割は文脈などを考慮せず機械的に切っているだけなので、重要な情報が途中でぶった切られる

対策として
重要な情報が途中でぶった切られるのを防ぐため、
chunk_overlapを設定して
チャンクと次のチャンクの範囲をある程度重複させて分割

# Load documents from a text file
loader = TextLoader(file_path="harrypotter.txt", encoding = 'UTF-8')
docs = loader.load()
# Split documents into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=250, chunk_overlap=24)
documents = text_splitter.split_documents(documents=docs)

このチャンクの範囲の設定については適切な量はGPTで調べてみる

## 1. **チャンクサイズ (`chunk_size`) の決定**
– **短すぎると**:単語や文章が分断され、情報の流れが失われる。
– **長すぎると**:分割する意味が薄れ、メモリ消費が大きくなる。
– **一般的な目安**:
– **250~500文字**:短め(検索用、要約用)
– **500~1000文字**:バランス型(QAシステム、ベクトル検索)
– **1000~2000文字**:長め(LLMのコンテキスト処理)

例:
– 段落単位で検索するなら **500~1000文字**
– 文脈を広く持ちたいなら **1000~2000文字**

## 2. **チャンクの重複 (`chunk_overlap`) の決定**
`chunk_overlap` を設定することで、情報の途切れを防ぐことができます。適切な値は以下のように決めます。

– **目安**:
– `chunk_size` の **10~20%** くらいが一般的
– 例えば `chunk_size=500` なら `chunk_overlap=50~100`
– 重要な文脈を保持したいなら `chunk_overlap=20~30%`

### **例**
– `chunk_size=250`, `chunk_overlap=24` (10%程度) → 短めのコンテンツ
– `chunk_size=500`, `chunk_overlap=100` (20%程度) → 一般的な設定
– `chunk_size=1000`, `chunk_overlap=200` (20%程度) → 長めのコンテンツ

– 一般的には **`chunk_size=500`, `chunk_overlap=100` (20%)** くらいが良い
– **内容が切れやすいなら `chunk_overlap` を増やす**
– **より自然な分割には `separators` を活用する**
– **実際に試して調整するのが最も確実**

とのこと

この辺りは別の時に試すことにする

チャンクからグラフデータを作成

LLMGraphTransformerを使ってチャンクに分割したテキストをグラフドキュメントに変換する

これは
LLMがテキストからエンティティやその間の関係性を抽出し、構造化されたグラフデータを生成
その後、それをNeo4jに保存
という流れになる

# Convert documents to graph format
llm_transformer = LLMGraphTransformer(llm=llm, ignore_tool_usage=False)
graph_documents = llm_transformer.convert_to_graph_documents(documents)

# Add documents to the graph
graph.add_graph_documents(
    graph_documents,
    baseEntityLabel=True,
    include_source=True
)

この辺りのコードは変更なさそう

次にベクトルインデックスの作成
グラフデータだけでなく
テキストから作成した埋め込み行列(ベクトルインデックス)も計算し、
これもNeo4jに格納

ベクトルインデックスの計算にはmxbai-embed-largeという別のモデルを使用
Neo4jVector.from_existing_graphを使用することで、
embeddingsに指定したモデルを使用してテキストの埋め込み行列が作成され、
グラフデータベースに保存される

このコードも変わらないと思う

# Initialize embeddings
embeddings = OllamaEmbeddings(
    model="mxbai-embed-large",
)

# Create a vector index from the existing graph
Neo4jVector.from_existing_graph(
    embeddings,
    search_type="hybrid",
    node_label="Document",
    text_node_properties=["text"],
    embedding_node_property="embedding"
)

次に
フルテキストインデックスの作成
最後に通常の全文検索に使用するインデックス
(ベクトルインデックスとは異なります)を作成

これは単にNeo4jに接続してCREATE FULLTEXT INDEXを使えばOK

これは

# Connect to the Neo4j database and create a full-text index
driver = GraphDatabase.driver(
    uri=os.environ["NEO4J_URI"],
    auth=(os.environ["NEO4J_USERNAME"], os.environ["NEO4J_PASSWORD"])
)

def create_fulltext_index(tx):
    query = '''
    CREATE FULLTEXT INDEX `fulltext_entity_id` 
    FOR (n:__Entity__) 
    ON EACH [n.id];
    '''
    tx.run(query)

try:
    with driver.session() as session:
        session.execute_write(create_fulltext_index)
        print("Fulltext index created successfully.")
except Exception as e:
    print(f"Failed to create index: {e}")

driver.close()

でコードもそのままで行けるはず

上記のコードが実行できたら、http://localhost:7474にアクセスしてグラフの状況を確認

次に
RAGを使った質問の回答
このグラフRAGでは質問に回答するときに以下の3段階でデータを取ってます。
1. 質問文に含まれる人名や組織名を抽出し、それに関連するグラフノードを取得する
2. 取得したグラフノードでベクトル検索を行い、コンテキストを作る
3. 元の質問文に作成したコンテキストを添えて言語モデルに投げ、最終的な回答を得る

“What does Harry Potter looks like?”という質問をした場合

1.質問に関連するグラフデータの抽出
質問文から”Harry Potter”という人名が抽出され、”Harry Potter”に言及するグラフノードが取得されます

2.ベクトル検索による関連情報の抽出
さらに、これはハリーの見た目に関する質問だということを言語モデルが解釈して、1で取得したグラフノードに含まれる文章に対してベクトル検索を行い、見た目に関する言及を抽出してコンテキストとします

3.最終的な回答の生成
最後に、context_builderが作ったコンテキストと元の質問である”What does Harry Potter looks like?”をつなげてLlamaに投げています

ここからが検索用プログラム

質問文からの人名抽出

まず、ユーザーの質問文からエンティティを抽出するための処理を書きます。
Entitiesクラスを定義し、descriptionに「テキストに出てくる全ての人名・組織名」と定義することで言語モデルに人名と組織名を抽出させるように指定

そして、そのEntitiesクラスをllm.with_structured_outputで言語モデルに渡してinvokeすることで質問から人名・組織名を抽出

コードは

class Entities(BaseModel):
    """Identifying information about entities."""
    names: list[str] = Field(
        ...,
        description="All the person, organization, or business entities that "
        "appear in the text",
    )

# Create a prompt for extracting entities
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are extracting organization and person entities from the text."),
        ("human", "Use the given format to extract information from the following input: {question}"),
    ]
)

dict_schema = convert_to_openai_function(Entities)
entity_chain = prompt | llm.with_structured_output(dict_schema)
entities = entity_chain.invoke({"question": question})
entity_names = eval(entities['names'])

この中の英文
フィールドの指定で
descriptionで
All the person, organization, or business entities that ”
“appear in the text”

「すべての個人、組織、または事業体」
「本文中に登場します」

プロンプトで
You are extracting organization and person entities from the text.
テキストから組織と個人のエンティティを抽出しています。

Use the given format to extract information from the following input: {question}
指定された形式を使用して、次の入力から情報を抽出します: {question}

わかりにくいのでGPTでこの部分を調べた

system と human の関係
* system メッセージ → AI の振る舞いやタスクの定義
* human メッセージ → 実際の入力データと実行すべき処理の具体的な指示
この二つを組み合わせることで、AI に 「何をするべきか(system)」 と 「何を元に処理するか(human)」を伝える

このため応用するなら
プロンプトとフィールドの部分を変えれば
他の用途、例えば場所や商品の検索にも使えるらしい

次にグラフデータ取得
ここは単なるNeo4jの操作
先ほどリスト形式で取得したエンティティをループさせてNeo4jのクエリを複数回投げる

これもそのまま使える

# Retrival of related nodes
related_nodes = []
for entity in entity_names:
    response = graph.query(
        """
        MATCH (n)-[:MENTIONS]->(m)
        WHERE m.id = $entity
        RETURN n.text AS text, n.embedding AS embedding
        """,
        {"entity": entity},
    )
    for record in response:
        text = record["text"]
        embedding = np.array(record["embedding"])
        related_nodes.append((entity, text, embedding))

次に取得したグラフデータに対するベクトル類似度計算
上の質問で質問に関連する人物に関するグラフノードを取得しましたが、
元のデータの量によっては膨大な量のデータが取れるかもしれません

そのため、取得したグラフデータに対して質問文との類似度を計算し、
上位いくつかのデータだけをコンテキストとして採用する

まずは質問文の埋め込みベクトルを計算し、cosine_similarityで各グラフデータとの類似度を計算
上位いくつかのデータを並べて、#Documentというタグをつけておく

これもそのままで行けるはず

# Create embeddings from question
question_embedding = embeddings.embed_query(question)

# Calculation and sorting of cosine similarity
def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

related_nodes.sort(
    key=lambda x: cosine_similarity(question_embedding, x[2]),
    reverse=True
)

top_related_texts = [node[0] for node in related_nodes[:top_n]]

graph_context = ""
for node in related_nodes[:top_n]:
    graph_context += f"#Document\nExplanation about '{node[0]}' - '{node[1]}'\n\n"

次に通常のベクトル検索も行う
グラフデータの検索だけでなく通常のRAG検索であるベクトル検索も行います
Neo4jVector.from_existing_indexを使用してグラフデータを取得するためのリトリーバーを取得してから、
それを使って検索を実行
これも#Documentというタグをつけてテキストに整形

ここもコードはそのままでOK

# Retrieve the vector retriever
vector_index = Neo4jVector.from_existing_index(
    embeddings,
    search_type="hybrid",
    index_name="Document_embedding",
    keyword_index_name="Document_keyword",
    node_label="Document",
    text_node_property="text",
    embedding_node_property="embedding"
)
vector_retriever = vector_index.as_retriever()

# Execute vector search
vector_data = [el.page_content for el in vector_retriever.invoke(question)]
vector_context = ""
for text in vector_data[:top_n]:
    vector_context += f"#Document\nAdditional information - '{text}'\n\n"

コンテキストの作成と言語モデルの実行
コンテキストができたので、あとはこれをLlamaに渡すだけ
このコード例ではgraph_retrieverとvector_retriverが上記のコンテキスト作成プログラムになっています

def context_builder(question: str) -> str:
    graph_data = graph_retriever(question)
    vector_data = vector_retriver(question)
    return f"""
    Context from graph data:
    {graph_data}

    Context from vector data:
    {vector_data}
    """

# Create a prompt template for the final answer
template = """Answer the question based only on the following context:
{context}

Question: {question}
Use natural language and be concise.
Answer:"""
prompt = ChatPromptTemplate.from_template(template)

# Set up the final chain
chain = (
    {
        "context": context_builder,
        "question": lambda x: x,
    }
    | prompt
    | llm
    | StrOutputParser()
)

input_question = input("Question: ")
result = chain.invoke(question)
print("Answer:", result)

プロンプトやchainの作成などはLangChainの基本的な使い方の話なのでここでは割愛
とあるのでGPTで解説

このコードは、質問 (question) に対して グラフデータとベクトルデータ を取得し、
それらの情報を元に回答を生成する処理を行っています。
LangChain を用いた パイプライン処理 を実装しており、関数型プログラミングのスタイルでデータの流れを明確にしています。

def context_builder(question: str) -> str:
    graph_data = graph_retriever(question)
    vector_data = vector_retriver(question)
    return f"""
    Context from graph data:
    {graph_data}

    Context from vector data:
    {vector_data}
    """


graph_retriever(question) を用いて、質問に関連する グラフデータ を取得
vector_retriver(question) を用いて、質問に関連する ベクトルデータ を取得
それらを フォーマット済みのコンテキスト にまとめて返す

ポイント
* グラフデータ:ノードとエッジの関係を利用した情報(例:知識グラフ)
* ベクトルデータ:埋め込みモデルを使った検索結果(例:類似度検索)
* 統合したコンテキストを作成し、後続の LLM へ渡す

template = """Answer the question based only on the following context:
{context}

Question: {question}
Use natural language and be concise.
Answer:"""
prompt = ChatPromptTemplate.from_template(template)

でプロンプトの作成

役割
* LLM に与える プロンプトのテンプレート を定義。
* {context} の部分に context_builder() で作成したコンテキストを挿入。
* {question} に元の質問を挿入。
* 「与えられたコンテキストだけを使って回答せよ」 という指示を明示。


例えば、質問 “What is the capital of France?” に対して、context_builder() が以下のようなデータを返したとする:
Context from graph data:
– France -> Capital -> Paris

Context from vector data:
– The capital of France is Paris.

この場合、最終的なプロンプトは:
Answer the question based only on the following context:
Context from graph data:
– France -> Capital -> Paris

Context from vector data:
– The capital of France is Paris.

Question: What is the capital of France?
Use natural language and be concise.
Answer:

このプロンプトを LLM に渡して、回答を生成す

chain(処理の流れを定義)
chain = (
    {
        "context": context_builder,
        "question": lambda x: x,
    }
    | prompt
    | llm
    | StrOutputParser()
)

データの流れを順番に処理するためのパイプライン を構築

{
    "context": context_builder,
    "question": lambda x: x,
}

context_builder(question) で コンテキストを生成。
lambda x: x で 質問をそのまま渡す
| prompt
ChatPromptTemplate を適用し、コンテキストと質問を埋め込んだプロンプトを作成。

| llm
prompt で作成したプロンプトを LLM に入力し、回答を得る

| StrOutputParser()
LLM の出力を文字列としてパース し、最終的な回答として返す

ユーザーの質問を処理

input_question = input("Question: ")
result = chain.invoke(question)
print("Answer:", result)

* input() で ユーザーの質問を受け取る。
* chain.invoke(question) を実行し、質問に基づいて LLM に回答を生成させる。
* print() で 最終的な回答を出力。

ポイント
✅ グラフ検索 (graph_retriever()) と ベクトル検索 (vector_retriver()) を組み合わせて、幅広い情報を取得

✅ プロンプトテンプレート (ChatPromptTemplate) を使用して、質問とコンテキストを LLM に適切に渡す

✅ データ処理の流れをパイプライン (| 演算子) で簡潔に記述

✅ LLM への入力と出力を一貫して処理できるフレームワーク

改良のアイデア
1. スコア付きの情報を使用
* graph_data や vector_data にスコアを追加し、信頼性の高い情報を優先。
2. 回答に信頼性スコアを付与
* LLM の回答に「出典(graph / vector)」を示す。
3. エンティティ拡張
* question からエンティティを抽出し、graph_retriever() の検索クエリを改善。

とのこと

ここまで完成したら、以下のようなプロンプトを投げてみます。
Where did Dumbledore and Harry entered in search of a Horcrux?
ダンブルドアとハリーは分霊箱を探してどこに入りましたか?

graph_dataをログに出力してみると、HarryとDumbledoreが人名として解釈され、2人に言及するグラフデータが取得されてコンテキストとして整形されています

結果ログは

Context from graph data:
#Document
Explanation about 'Dumbledore' - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Explanation about 'Harry' - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Explanation about 'Dumbledore' - 'Harry awakens in the Hogwarts infirmary, where Dumbledore explains that Harry was protected by his mother's love, which also killed Quirrell. Voldemort survived, and the Philosopher's Stone was destroyed. During the school's end-of-year feast,'

#Document
Explanation about 'Harry' - 'Hagrid takes Harry to Diagon Alley to shop for school supplies. Harry buys a wand, and the proprietor tells him that his wand and Voldemort's wand contain feathers from the same phoenix. At the end of the summer, Harry boards the Hogwarts Express,'

#Document
Explanation about 'Dumbledore' - 'that both wands contain as their cores both comes from Fawkes, the phoenix that Dumbledore keeps as a pet in his office until his death in Half-Blood Prince.[26] Harry's wand is broken in Deathly Hallows. For a time, he borrows Hermione's wand, and'

graph_dataをログに出力してみると、HarryとDumbledoreが人名として解釈され、
2人に言及するグラフデータが取得されてコンテキストとして整形されていま

ベクトル検索データの確認
ベクトル検索から得られたコンテキストも確認します。
だいたい同じようなデータが取得されています

Context from vector data:
#Document
Additional information - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Additional information - 'Dumbledore to drink, he is attacked by Inferi. They try to drown Harry, but Dumbledore rescues him. Dumbledore and Harry then return to the Hogwarts Astronomy Tower with the Horcrux. Dumbledore asks Harry not to interfere in the events that are'

#Document
Additional information - 'Harry, Ron, and Hermione leave Hogwarts in Harry Potter and the Deathly Hallows (2007) to search for and demolish Voldemort's four remaining Horcruxes. They must be destroyed with basilisk venom, the Sword of Gryffindor, or some other powerful'

#Document
Additional information - 'or some other powerful means. Ron, Hermione, Vincent Crabbe and Neville Longbottom each destroy one of these Horcruxes. After a battle at Hogwarts, Harry goes into the Forbidden Forest to meet Voldemort, who uses the Elder Wand to cast the Killing'

最終的な質問文
ChatPromptTemplateに渡しているテンプレートの内容と合わさって、最終的にLlamaには以下のような質問が投げられます

Answer the question based only on the following context:
Context from graph data:
#Document
Explanation about 'Dumbledore' - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Explanation about 'Harry' - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Explanation about 'Dumbledore' - 'Harry awakens in the Hogwarts infirmary, where Dumbledore explains that Harry was protected by his mother's love, which also killed Quirrell. Voldemort survived, and the Philosopher's Stone was destroyed. During the school's end-of-year feast,'

#Document
Explanation about 'Harry' - 'Hagrid takes Harry to Diagon Alley to shop for school supplies. Harry buys a wand, and the proprietor tells him that his wand and Voldemort's wand contain feathers from the same phoenix. At the end of the summer, Harry boards the Hogwarts Express,'

#Document
Explanation about 'Dumbledore' - 'that both wands contain as their cores both comes from Fawkes, the phoenix that Dumbledore keeps as a pet in his office until his death in Half-Blood Prince.[26] Harry's wand is broken in Deathly Hallows. For a time, he borrows Hermione's wand, and'

Context from vector data:
#Document
Additional information - 'Near the end of the book, Dumbledore and Harry enter a cave in search of a Horcrux. Dumbledore drinks a potion inside the Horcrux's container and begins to scream in agony. When Harry attempts to retrieve some lake water for Dumbledore to drink, he'

#Document
Additional information - 'Dumbledore to drink, he is attacked by Inferi. They try to drown Harry, but Dumbledore rescues him. Dumbledore and Harry then return to the Hogwarts Astronomy Tower with the Horcrux. Dumbledore asks Harry not to interfere in the events that are'

#Document
Additional information - 'Harry, Ron, and Hermione leave Hogwarts in Harry Potter and the Deathly Hallows (2007) to search for and demolish Voldemort's four remaining Horcruxes. They must be destroyed with basilisk venom, the Sword of Gryffindor, or some other powerful'

#Document
Additional information - 'or some other powerful means. Ron, Hermione, Vincent Crabbe and Neville Longbottom each destroy one of these Horcruxes. After a battle at Hogwarts, Harry goes into the Forbidden Forest to meet Voldemort, who uses the Elder Wand to cast the Killing'

Question: Where did Dumbledore and Harry entered in search of a Horcrux?

Use natural language and be concise.

Answer:

これだけ事前情報が与えられれば、パラメータ数の少ないLlama3.2 3Bでも正確な回答ができます。回答は以下の

A cave.

となる

プロンプトにおけるsystem と human の関係

プロンプトにおけるsystem と human の関係

`system` と `human` の部分は、
**`ChatPromptTemplate.from_messages`** を使って
プロンプトを構築する際の役割を明確にするためのラベルです。
これは OpenAI の `ChatPromptTemplate` に準拠した形式になっています。

## **1. `system` メッセージ**

("system", "You are extracting organization and person entities from the text.")

### **役割**
– **システムメッセージ (`system`)** は、AI の振る舞いを指定するために使用される。
– ここでは **「組織や人物のエンティティを抽出する役割のAI」** であることを明示。
– 通常、モデルの動作を方向づける基本的な指示を含む。

### **意味**
> 「あなた(AI)はテキストから組織と人物のエンティティを抽出する役割を持ちます。」

## **2. `human` メッセージ**

("human", "Use the given format to extract information from the following input: {question}")

### **役割**
– **ユーザーからの入力を表すメッセージ** (`human`)。
– `{question}` はプレースホルダーで、実際の入力がここに挿入される。
– AI が処理するための具体的な指示を与える。

### **意味**
> 「以下の入力から、指定されたフォーマットを使って情報を抽出してください:{question}」

## **`system` と `human` の関係**
– **`system` メッセージ** → AI の振る舞いやタスクの定義
– **`human` メッセージ** → 実際の入力データと実行すべき処理の具体的な指示

この二つを組み合わせることで、AI に **「何をするべきか(system)」** と **「何を元に処理するか(human)」** を伝える。

## **実行の流れ**
1. **`system`**:「AI はテキストから組織と人物のエンティティを抽出する」
2. **`human`**:「具体的な入力 `{question}` に基づいて、決まったフォーマットで情報を抽出せよ」
3. AI はこのプロンプトを解釈し、テキスト内のエンティティを抽出する。

## **補足**
このプロンプトの後に `convert_to_openai_function(Entities)` を適用して、結果を `Entities` クラスのスキーマに適合させているため、出力は `names` のリスト形式で返される。

この形式を使うことで、AI はエンティティ抽出タスクを **明確なルール** のもとで実行することができる。

他の商品検索などにも使えるらしい

GPTによれば
はい、その通りです。
**`Entities` のスキーマ定義とプロンプトの `system` / `human` メッセージを変更すれば、別のケースに適用可能** です。

## **具体的な変更方法**
### **1. スキーマ (`Entities` クラス) を変更**
抽出したい情報が異なる場合、`names` の部分を適切なフィールド名・型・説明に変更すれば対応可能です。

#### **例1:地名(場所)を抽出する場合**

class Locations(BaseModel):
    """Identifying information about locations."""
    locations: list[str] = Field(
        ...,
        description="All the locations, cities, or countries that appear in the text",
    )

#### **例2:商品名を抽出する場合**

class Products(BaseModel):
    """Identifying information about products."""
    products: list[str] = Field(
        ...,
        description="All the product names that appear in the text",
    )

### **2. プロンプト (`system` / `human`) を変更**
スキーマ変更に合わせて、AI に適切な指示を与えるため `system` と `human` のメッセージも修正する。

#### **例1:地名(場所)を抽出する場合**

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are extracting location names from the text."),
        ("human", "Use the given format to extract location names from the following input: {question}"),
    ]
)

#### **例2:商品名を抽出する場合**

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are extracting product names from the text."),
        ("human", "Use the given format to extract product names from the following input: {question}"),
    ]
)

### **3. そのまま適用**
スキーマとプロンプトを変更すれば、あとは **同じロジックで処理可能** です。

dict_schema = convert_to_openai_function(Locations)  # スキーマ変更
location_chain = prompt | llm.with_structured_output(dict_schema)
locations = location_chain.invoke({"question": question})
location_names = eval(locations['locations'])

または、商品名の場合:

dict_schema = convert_to_openai_function(Products)
product_chain = prompt | llm.with_structured_output(dict_schema)
products = product_chain.invoke({"question": question})
product_names = eval(products['products'])

## **結論**
> **スキーマ (`Entities` クラスの `names` 部分) とプロンプト (`system` / `human` メッセージ) を変更すれば、別の用途でもそのまま使える。**

`BaseModel` の型 (`list[str]`) や `description` を変更することで、抽出する対象を自由にカスタマイズ可能です。

チャンク範囲の設定

チャンク範囲の設定

重要な情報が途中でぶった切られるのを防ぐため、
chunk_overlapを設定して
チャンクと次のチャンクの範囲をある程度重複させて分割

# Load documents from a text file
loader = TextLoader(file_path="harrypotter.txt", encoding = 'UTF-8')
docs = loader.load()
# Split documents into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=250, chunk_overlap=24)
documents = text_splitter.split_documents(documents=docs)

とあるが 適切なチャンク範囲の設定はどのようにすれば良いのか?

これをGPTで調べてみた

適切な `chunk_size` と `chunk_overlap` の設定は、以下の要因によって決まります。

## 1. **チャンクサイズ (`chunk_size`) の決定**
– **短すぎると**:単語や文章が分断され、情報の流れが失われる。
– **長すぎると**:分割する意味が薄れ、メモリ消費が大きくなる。
– **一般的な目安**:
– **250~500文字**:短め(検索用、要約用)
– **500~1000文字**:バランス型(QAシステム、ベクトル検索)
– **1000~2000文字**:長め(LLMのコンテキスト処理)

例:
– 段落単位で検索するなら **500~1000文字**
– 文脈を広く持ちたいなら **1000~2000文字**

## 2. **チャンクの重複 (`chunk_overlap`) の決定**
`chunk_overlap` を設定することで、情報の途切れを防ぐことができます。適切な値は以下のように決めます。

– **目安**:
– `chunk_size` の **10~20%** くらいが一般的
– 例えば `chunk_size=500` なら `chunk_overlap=50~100`
– 重要な文脈を保持したいなら `chunk_overlap=20~30%`

### **例**
– `chunk_size=250`, `chunk_overlap=24` (10%程度) → 短めのコンテンツ
– `chunk_size=500`, `chunk_overlap=100` (20%程度) → 一般的な設定
– `chunk_size=1000`, `chunk_overlap=200` (20%程度) → 長めのコンテンツ

## 3. **チューニング方法**
適切な値を決めるには、以下の手順で調整すると良いです。

1. **テスト用の分割を試す**
“`python
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
chunks = text_splitter.split_documents(docs)
for i, chunk in enumerate(chunks[:5]):
print(f”Chunk {i+1}:\n{chunk}\n{‘=’*50}”)
“`
2. **実際の分割結果を確認**
– 文が不自然に切れていないか?
– 重要なキーワードが両方のチャンクに残っているか?
3. **調整する**
– 重要な部分が切れるなら `chunk_overlap` を増やす
– 無駄に長いなら `chunk_size` を小さくする

## 4. **応用**
### **改行や句読点で分割する**
より自然な分割をしたい場合、`separator` を設定できます。

“`python
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
separators=[“\n\n”, “\n”, “。”, “、”, ” “]
)
“`
– 段落 (`\n\n`)、改行 (`\n`)、句読点 (`。` や `、`) で優先的に分割
– なるべく意味のある単位でチャンクを作成可能

### **結論**
– 一般的には **`chunk_size=500`, `chunk_overlap=100` (20%)** くらいが良い
– **内容が切れやすいなら `chunk_overlap` を増やす**
– **より自然な分割には `separators` を活用する**
– **実際に試して調整するのが最も確実**

Broser-useで実行結果の保存

Broser-useで実行結果の保存

https://fx.minkabu.jp/indicators
へアクセスし、今日の経済指標カレンダーの日程を取得。結果をcalendar.txt へ保存
というプロンプトでは
画像ファイルができるだけで
テキストファイルは生成されない

Broser-use ファイル保存で検索

https://qiita.com/Syoitu/items/5aa84b5d8c6047c4d41b
によれば
カスタムアクションの登録とエージェントの並列化が必要

Browser Useでスクリーンショットを取得する
では
スクリーンショットを取得するカスタムアクションを登録
となっている

どうやらプロンプトではなく
カスタムアクションを作成する必要がある

これができるなら
チラシのxpathの取得で画像を取得するよりも
Broser-useでチラシ画像をクリックし
これをカスタムアクションでスクショ保存して
これをOCRした方が汎用性がありそう

ということでカスタムアクションについて調べる

カスタムアクションについては
AI Agent】browser-useを使って、自分のブラウザを操作させてみる


browser-useによるブラウザ操作自動化のサンプル集
を参考に進める

原油と金価格の取得

自国と各国の経済指標とかをまとめて可視化
為替れーと、原油とか金、ビットコインの推移を見れることが
できるものを作成する

GPTで調べると

以下の要件に基づいて、経済指標や各種市場データをまとめて可視化するツールを作成する方法を提案します。

### **プロジェクト概要**
#### **目的**
– 各国の経済指標や市場データをまとめて可視化。
– 為替レート、原油価格、金価格、ビットコイン価格の推移をリアルタイムで確認可能。

#### **必要な機能**
1. **データ取得**
– 経済指標(GDP、失業率、インフレ率など)
– 為替レート(主要通貨ペア: USD/JPY、EUR/USDなど)
– 原油価格(WTI、ブレント)
– 金価格(トロイオンス単位)
– 仮想通貨(ビットコイン、イーサリアムなど)

2. **データの可視化**
– 時系列グラフ(過去の推移)
– ダッシュボード形式でのリアルタイム表示

3. **通知機能(任意)**
– 為替レートや価格の変動が大きい場合にアラートを送信。

4. **多言語対応(日本語/英語)**

### **開発環境**
#### **プログラミング言語**
– Python(データ取得、可視化に適切)

#### **使用ライブラリ・API**
– **データ取得**
– 為替レート: [ExchangeRate-API](https://www.exchangerate-api.com/) or [Alpha Vantage](https://www.alphavantage.co/)
– 原油・金価格: [Yahoo Finance API](https://rapidapi.com/apidojo/api/yahoo-finance1/)
– 仮想通貨: [CoinGecko API](https://www.coingecko.com/) or [CoinMarketCap API](https://coinmarketcap.com/api/)
– 経済指標: [OECD API](https://data.oecd.org/) or [World Bank API](https://data.worldbank.org/)

– **データ可視化**
– `Plotly`(インタラクティブなグラフ)
– `Dash`(ダッシュボード作成)
– `matplotlib`(簡易な静的グラフ)

– **バックエンド**
– Flask または FastAPI(Webアプリケーションのベース)

– **フロントエンド**
– Dash(Pythonのみで作成可能)

#### **サーバー**
– ローカル開発: PCまたはRaspberry Pi(Pi 5が最適)
– デプロイ: AWS、GCP、Heroku、またはVPS

### **基本的な実装手順**

1. **データ取得モジュールの作成**
– 各APIからデータを取得するPythonモジュールを作成。
– 必要に応じて取得データをCSVまたはデータベース(SQLiteなど)に保存。

2. **データ処理**
– Pandasを使用してデータを加工(例: 日付フォーマットの統一、不要な列の削除)。
– 過去1年分などの期間を指定してデータをフィルタリング。

3. **可視化**
– Plotlyでインタラクティブなグラフを作成。
– 例: 為替レートの時系列推移を折れ線グラフで表示。

4. **ダッシュボードの作成**
– Dashを使い、リアルタイムにデータが更新されるダッシュボードを構築。
– 各市場データをカード形式やグラフで整理。

5. **通知機能(オプション)**
– 適当な閾値を設定し、変動が大きい場合にLINE Notifyやメールでアラート送信。

### **プロジェクトの初期設計例**

#### **ディレクトリ構造**
“`
economy_dashboard/
├── app.py # メインスクリプト
├── data_fetcher/
│ ├── forex.py # 為替データ取得
│ ├── commodities.py # 原油/金価格取得
│ ├── crypto.py # 仮想通貨データ取得
│ └── indicators.py # 経済指標取得
├── templates/ # DashやFlaskのHTMLテンプレート
├── static/ # CSS/JSファイル
└── requirements.txt # 必要ライブラリ
“`

#### **ダッシュボードイメージ**
– **ヘッダー**
– 今日の日付と最新の市場状況(例: USD/JPY、ビットコイン価格など)。
– **メインセクション**
– 為替レート推移グラフ
– 原油価格と金価格の時系列データ
– 仮想通貨の市場状況
– **サイドバー**
– 通知の設定や期間選択。

### **サンプルコード(Dashを使った可視化)**

```python
import dash
from dash import dcc, html
import pandas as pd
import plotly.graph_objects as go

# サンプルデータ
data = {
    "Date": pd.date_range(start="2024-01-01", periods=10, freq="D"),
    "USD_JPY": [130 + i for i in range(10)],
    "Bitcoin": [30000 + i * 500 for i in range(10)],
}
df = pd.DataFrame(data)

# Dashアプリ
app = dash.Dash(__name__)

app.layout = html.Div([
    html.H1("経済指標ダッシュボード"),
    dcc.Graph(
        id="forex_chart",
        figure={
            "data": [
                go.Scatter(x=df["Date"], y=df["USD_JPY"], mode="lines", name="USD/JPY"),
                go.Scatter(x=df["Date"], y=df["Bitcoin"], mode="lines", name="Bitcoin"),
            ],
            "layout": go.Layout(title="為替とビットコインの推移", xaxis_title="日付", yaxis_title="価格"),
        },
    ),
])

if __name__ == "__main__":
    app.run_server(debug=True)
```

### **今後の拡張案**
1. **AI活用**
– 市場予測モデルを構築し、将来のトレンドを予測。
2. **モバイル対応**
– スマホで見やすいUIを設計。
3. **ユーザー設定**
– 関心のある国や通貨ペアを選択可能に。

作成を進める中で質問や具体的な実装サポートが必要であれば、ぜひお知らせください!

となった

まずはWTIからやるので

commodities.py

を作成する

import requests
import datetime

def fetch_commodities_data(api_key, symbol, interval="1d", range="1mo"):
    """
    Fetch commodity prices such as crude oil (WTI/Brent) or gold.

    Parameters:
        api_key (str): API key for the Yahoo Finance or similar API.
        symbol (str): Symbol of the commodity (e.g., CL=F for WTI Crude Oil, GC=F for Gold).
        interval (str): Data interval (e.g., '1d', '1wk').
        range (str): Data range (e.g., '1mo', '3mo', '1y').

    Returns:
        dict: Parsed JSON response containing commodity price data.
    """
    base_url = "https://query1.finance.yahoo.com/v8/finance/chart/"
    url = f"{base_url}{symbol}"

    params = {
        "region": "US",
        "lang": "en",
        "interval": interval,
        "range": range,
    }

    headers = {
        "x-api-key": api_key
    }

    try:
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()
        data = response.json()
        return data

    except requests.exceptions.RequestException as e:
        print(f"Error fetching commodity data: {e}")
        return None

# Example usage
if __name__ == "__main__":
    API_KEY = "your_api_key_here"  # Replace with your actual API key
    symbol_wti = "CL=F"  # WTI Crude Oil
    symbol_gold = "GC=F"  # Gold

    # Fetch WTI data
    wti_data = fetch_commodities_data(API_KEY, symbol_wti)
    if wti_data:
        print("WTI Data:", wti_data)

    # Fetch Gold data
    gold_data = fetch_commodities_data(API_KEY, symbol_gold)
    if gold_data:
        print("Gold Data:", gold_data)

というコードが提示された

しかし確かAPIキーなしでも動作する

yfinance API を使って株のデータを取得する

も参考にする

Python で過去の株価データを CSVデータで取得する
でもキーは不要で動く

Pythonでリアルタイムな株価を取得する方法
でも同じ

[解説]Pythonを用いた株価・経済データの取得方法をまとめてみた

でもキーは不要

ということで
原油/金価格取得のため yfinance を使い python で取得したい
というように変更

import yfinance as yf

def fetch_commodities_data(symbol, start_date, end_date):
    """
    Fetch commodity prices such as crude oil (WTI/Brent) or gold using yfinance.

    Parameters:
        symbol (str): Symbol of the commodity (e.g., 'CL=F' for WTI Crude Oil, 'GC=F' for Gold).
        start_date (str): Start date for historical data in 'YYYY-MM-DD' format.
        end_date (str): End date for historical data in 'YYYY-MM-DD' format.

    Returns:
        DataFrame: Pandas DataFrame containing the historical price data.
    """
    try:
        data = yf.download(symbol, start=start_date, end=end_date)
        return data
    except Exception as e:
        print(f"Error fetching commodity data: {e}")
        return None

# Example usage
if __name__ == "__main__":
    symbol_wti = "CL=F"  # WTI Crude Oil
    symbol_gold = "GC=F"  # Gold

    # Define the date range
    start_date = "2023-01-01"
    end_date = "2023-12-31"

    # Fetch WTI data
    wti_data = fetch_commodities_data(symbol_wti, start_date, end_date)
    if wti_data is not None:
        print("WTI Data:")
        print(wti_data)

    # Fetch Gold data
    gold_data = fetch_commodities_data(symbol_gold, start_date, end_date)
    if gold_data is not None:
        print("Gold Data:")
        print(gold_data)

となって実行すると

[*********************100%***********************]  1 of 1 completed
WTI Data:
Price           Close       High        Low       Open  Volume
Ticker           CL=F       CL=F       CL=F       CL=F    CL=F
Date                                                          
2023-01-03  76.930000  81.500000  76.599998  80.570000  338520
2023-01-04  72.839996  77.419998  72.730003  77.250000  352434
2023-01-05  73.669998  74.919998  72.459999  73.250000  300731
2023-01-06  73.769997  75.470001  73.239998  73.970001  258128
2023-01-09  74.629997  76.739998  73.470001  73.470001  329290
...               ...        ...        ...        ...     ...
2023-12-22  73.559998  74.980003  73.389999  73.910004  222600
2023-12-26  75.570000  76.180000  73.129997  73.559998  208715
2023-12-27  74.110001  75.660004  73.769997  75.320000  253323
2023-12-28  71.769997  74.400002  71.720001  73.800003  262748
2023-12-29  71.650002  72.620003  71.250000  71.989998  214486

[250 rows x 5 columns]
[*********************100%***********************]  1 of 1 completed
Gold Data:
Price             Close         High          Low         Open Volume
Ticker             GC=F         GC=F         GC=F         GC=F   GC=F
Date                                                                 
2023-01-03  1839.699951  1839.699951  1836.199951  1836.199951     29
2023-01-04  1852.800049  1859.099976  1845.599976  1845.599976     25
2023-01-05  1834.800049  1855.199951  1834.800049  1855.199951     24
2023-01-06  1864.199951  1868.199951  1835.300049  1838.400024     26
2023-01-09  1872.699951  1880.000000  1867.000000  1867.000000     62
...                 ...          ...          ...          ...    ...
2023-12-22  2057.100098  2068.699951  2052.199951  2055.699951    202
2023-12-26  2058.199951  2060.800049  2054.199951  2060.000000     64
2023-12-27  2081.899902  2081.899902  2064.800049  2067.300049    586
2023-12-28  2073.899902  2087.300049  2066.500000  2081.600098    338
2023-12-29  2062.399902  2068.899902  2062.100098  2068.000000     47

[250 rows x 5 columns]

となる

さらにデータをCSVに保存するので

import yfinance as yf

def fetch_commodities_data(symbol, start_date, end_date):
    """
    Fetch commodity prices such as crude oil (WTI/Brent) or gold using yfinance.

    Parameters:
        symbol (str): Symbol of the commodity (e.g., 'CL=F' for WTI Crude Oil, 'GC=F' for Gold).
        start_date (str): Start date for historical data in 'YYYY-MM-DD' format.
        end_date (str): End date for historical data in 'YYYY-MM-DD' format.

    Returns:
        DataFrame: Pandas DataFrame containing the historical price data.
    """
    try:
        data = yf.download(symbol, start=start_date, end=end_date)
        return data
    except Exception as e:
        print(f"Error fetching commodity data: {e}")
        return None

# Example usage
if __name__ == "__main__":
    symbol_wti = "CL=F"  # WTI Crude Oil
    symbol_gold = "GC=F"  # Gold

    # Define the date range
    start_date = "2023-01-01"
    end_date = "2023-12-31"

    # Fetch WTI data
    wti_data = fetch_commodities_data(symbol_wti, start_date, end_date)
    if wti_data is not None:
        print("WTI Data:")
        print(wti_data)
        wti_data.to_csv("wti_data.csv")

    # Fetch Gold data
    gold_data = fetch_commodities_data(symbol_gold, start_date, end_date)
    if gold_data is not None:
        print("Gold Data:")
        print(gold_data)
        gold_data.to_csv("gold_data.csv")

として実行

とりあえず問題なし

次は経済指標だが
カレンダーは
メルマガから取得するより
Browser use で経済指標カレンダーを取得した方が早いかもしれない

https://fx.minkabu.jp/indicators
へアクセスし、今日の経済指標カレンダーの日程を取得

これで一応は取得できてるみたい

結果を保存したいので
https://fx.minkabu.jp/indicators
へアクセスし、今日の経済指標カレンダーの日程を取得。結果をcalendar.txt へ保存
としたけどできなかった

@sから今週のイベントを抽出したいのでプロンプトを作成する

@sから今週のイベントを抽出したいのでプロンプトを作成する

参考として
Udemy のプロンプトを見る

なおプロンプトは

task = """
https://kabutan.jp/warning/?mode=3_3&market=3からクローズ市場の本日、年初来高値を更新した銘柄のうち、株価が1000円以下のものをピックアップしてください。
その後、https://plsk.net/へアクセスし、フォームに"kabutest4"と入力し、移動をクリックした後、https://plsk.net/kabutest4へアクセスし、
先頭に「今日の口出」を入力した後、取得したすべての銘柄の「銘柄コード、銘柄名、株価」を転記してください。
最後に「編集する」ボタンをクリックし終了します。
"""

というように
マルチライン文字列を使用する

https://kabutan.jp/warning/?mode=3_3&market=3からクローズ市場の本日、年初来高値を更新した銘柄のうち、株価が1000円以下のものをピックアップしてください。
その後、https://plsk.net/へアクセスし、フォームに"kabutest4"と入力し、移動をクリックした後、https://plsk.net/kabutest4へアクセスし、先頭に「今日の口出」を入力した後、取得したすべての銘柄の「銘柄コード、銘柄名、株価」を転記してください。最後に「編集する」ボタンをクリックし終了します。


https://kakaku.com/ へアクセスし、「青森産 王林りんご10キロ」の最安値を検索してください。
次に、https://www.amazon.co.jp/ へアクセスし「青森産 王林りんご10キロ」の最安値を検索してください。
kakaku.comの最安値と同じかそれよりも+500円までであればアマゾンで買っていいよ、というアドバイスと、価格を教えて下さい。


aigym.jpというサイトにアクセスし、ブログメニューをクリックします。そして、ブログ一覧から、タイトルに"OpenAI"というキーワードが含まれる記事のみへのリンク一覧を取得します。OpenAIキーワードを含むリンク一覧のサイトへ順にアクセスし、時系列順(ブログ更新日時順)に、ブログの概要をまとめてください。

これらを参考にプロンプトを作成する

https://www.at-s.com/event/
が@sのイベントのページ

今日から一週間以内に開催されるイベント情報
からイベント情報を取得したい

これをもっと具体的なものにする

https://www.at-s.com/event/search/
でイベントの検索ができる

イベント開催日カレンダー
で今日の日付をクリックし
検索をクリック

ここまでをプロンプトにする

https://www.at-s.com/event/search/
へアクセスし、
イベント開催日カレンダー
の今日の日付をクリックし
検索をクリックします。
日付検索結果から10件のイベントを取得してください

これで実行すると

{'text': 'Extracted 10 events from the page:\n\n1. 第10回オクシズ森林の市\n2. 清水みなとと海洋ミュージアム2025\n3. 三明寺 節分祭\n4. みんなで冬の遊びを楽しもう事前申込\n5. アベオクマルシェ\n6. 親子でアルティメット体験 1月26日事前申込\n7. 週末ボタニカル SBSマイホームセンター静岡展示場\n8. ハチャメチャワクワクランド SBSマイホームセンター掛川展示場\n9. ハチャメチャワクワクランド SBSマイホームセンター藤枝展示場\n10. ハチャメチャワクワクランド SBSマイホームセンター富士展示場'}}])

とりあえず取得に関してはOK

次はtwitter の検索を試す

https://x.com/

検索が
https://x.com/explore

高速の事故情報を知りたいので
https://x.com/c_nexco_tokyo
へアクセスし
最新10件のポストを取得してください
これだと画像とかのパスまで取得するので

https://x.com/c_nexco_tokyo
へアクセスし
最新10件のポストからテキスト情報のみ取得してください
とした

しかし

{'text': 'Here are the latest 10 posts from the specified X account:\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2023年6月14日高速道路上の工事規制箇所へ衝突する事故が多発しています!\n運転支援機能を過信し、前方を見ていないと思われる事故や、スマートフォン見ながらの「ながら運転」による事故の多発と考えられます。走行中は前を見て運転してください。\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年12月20日【#東名#リニューアル工事工事終了のお知らせ】\n本日12/20(金)11時53分をもちまして、E1東名 大井松田IC~清水JCT間のリニューアル工事はすべて終了しました。\nお客さまにはご迷惑をおかけしました。ご協力ありがとうございました.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年3月1日【事故多発区間\u3000走行上のご注意】\n高速道路では、渋滞末尾の追突事故が多発しています。\n渋滞が多発する区間の手前では、標識や情報板などで注意喚起を行っていますので、車間距離を十分にとって、安全なご走行をお願いいたします.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年2月5日【除雪情報】18時40分現在\nE1 東名高速道路 横浜青葉IC付近の除雪状況です.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年11月6日【お知らせ】\n障害物等の落下地点の数キロ手前から、状況によりパトロールカーが低速走行し、交通流の抑制を行いながら調査をする場合があります。その際、後続のお客さまはパトロールカーを抜かさずにハザードランプを点灯し、低速での走行をお願いいたします.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年7月26日【緊急走行中の#ヒヤリハット】\n追越車線を緊急走行中、第1走行車線を進行中の普通乗用車が後方安全不確認のまま第2走行車線さらには追越車線に進路変更してきたため、衝突回避したものです。\n速度超過に注意するとともに安全な車間距離を保ち、心にも余裕をもって安全運転をお願いします。\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年10月12日【E1#東名#事故#ランプ閉鎖】\n6時35分現在\n下り線(名古屋方面)\n開始時間:6時00分\n場所:横浜町田IC 出口 \n(本線から料金所に向かう出口ランプ)\n概要:貨物単独 / 人身事故\n状況:負傷者救助中\n現場付近を先頭に渋滞長 10㎞\n※下り線さらに表示引用\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年10月12日【E1#東名#事故#渋滞情報】\n5時20分現在\n下り線(名古屋方面)\n区間:横浜青葉IC~横浜町田IC\n場所:横浜町田IC 出口分岐\n概要:貨物単独 / 人身事故\n状況:負傷者救助中 / 本線左側車線規制 / \n出口ランプ右側規制\n現場を先頭に渋滞 1㎞\n規制情報\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年2月5日【除雪情報】22時00分現在\nE1#東名高速道路横浜町田IC付近の状況です。#通行止#大雪#高速道路\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年2月5日【お知らせ】20時20分現在\nE1#東名高速道路東京料金所付近の状況です.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年12月28日【お知らせ】\n全車線に渡る短時間の作業、本線を横断する作業を安全に行うため複数台の管理車両(黄パト)が横一列に並ぶことがあります.\n\nNEXCO中日本 東京支社@c_nexco_tokyo·2024年10月25日'}}])

となって過去の記事を取得している

とりあえずtwitterでも取得はできそう