[Work 022] Geminiと一緒にETLパイプライン構築①【プロンプトログ】
* **RAG(検索拡張生成)**
LLMが外部データ(ファイル等)を検索して、その情報を元に回答を作成する仕組みのこと。
* **Embedding**
文章などのデータを、AIが計算処理できるように数値の列に変換する技術。
* **KVキャッシュ(Key-Value Cache)**
LLMが文章を生成する際、過去の計算結果を一時保存し、処理を高速化するためのメモリ領域。
* **JSON Schema**
JSONデータの構造(型、必須項目など)を定義し、データが正しい形式か検証するためのルール。
* **Map-Reduce**
大量のデータを分割して並列処理(Map)し、その結果を集約(Reduce)する分散処理の手法。
# Work022-RAGデータ純度アップ大作成①
## Turn 1
### User
> データをRAGに格納する際のメモリブロック作成について相談させてください。
>
> 私が執筆している小説向けのメモリブロック作成用システムプロンプトと、実際に出力されているサンプルです。
> これを添付の「Work001-Open WebUI パフォーマンス確認方法.md」向けの内容に変更したいので手伝ってください。
>
> 「Work001-Open WebUI パフォーマンス確認方法.md」は小説ではなく技術系の検証・実作業が中心です。そのため、小説用のメモリブロックの構成では内容が具備できないと思っています。適切なメモリブロックの提案をお願いします。
### Gemini
ご提示いただいた小説用のシステムプロンプトと出力サンプル、そして今回の対象である技術ログ「Work001-Open WebUI パフォーマンス確認方法.md」を拝見しました。
ご指摘の通り、小説用の構成(感情、メタファー、キャラクター関係)は、技術的な備忘録やRAG(検索)としてはノイズになり、検索精度を下げてしまいます。
技術ドキュメント向けに最適化した、新しい**「テクニカル・メモリブロック」**の構成案を提案します。
変更の方針
- 情緒的要素の排除: 「好感度」「メタファー(圧死、衣装など)」を廃止し、正確な技術用語(OOM、モデルロードなど)を使用します。
- 環境依存情報の構造化: ハードウェア構成(GPU, VRAM)やソフトウェアバージョンを明記します。
- トラブルシューティングの重視: 「何のエラーが出て」「どう解決したか」の因果関係を検索しやすくします。
- 成果物の明確化: 作成したスクリプトや設定ファイルの最終形を特定できるようにします。
1. 新しい出力テンプレート案
技術ログ専用のテンプレートです。これをシステムプロンプトの「出力テンプレート」セクションに置き換えてください。
Plaintext[Log_ID: Work***]
[Title: (作業タイトル)]
[Date: YYYY-MM-DD]
[Executive_Summary]
(作業の目的、実施内容、最終的な成果を300文字程度で要約)
[System_Environment]
- OS: (Windows/WSL2/Ubuntu等)
- Hardware: (GPU型番, VRAM容量)
- Components: (Docker, Ollama, Open WebUI等のバージョンや構成)
[Performance_Metrics]
- Model_A: (モデル名) - (Tokens/s), (Load Duration)
- Model_B: (モデル名) - (Tokens/s)
// ベンチマーク結果があればここに箇条書き
[Troubleshooting_Log]
1. Issue: (発生したエラーメッセージや現象)
Cause: (特定された原因)
Solution: (実施した解決策・コマンド)
2. Issue: ...
Cause: ...
Solution: ...
[Deliverables]
- Script: (作成したスクリプト名と機能概要)
- Config: (変更した設定ファイル名)
[Search_Tags]
- Tools: (使用したツール名)
- Errors: (発生したエラーコードやメッセージの一部)
- Concepts: (RAG, Embedding, Function Callingなど)
2. システムプロンプトの修正案(抜粋)
小説用の「比喩変換」などを削除し、事実抽出に特化した指示に変更します。
役割:
あなたは技術ドキュメント専属の「テクニカル・ライブラリアン」です。
提供された作業ログを分析し、将来の技術参照用データベース(RAG)に格納するための構造化データ「テクニカル・メモリブロック」を出力してください。処理手順:環境特定: 作業が行われたハードウェア(GPU)やソフトウェア環境を抽出してください。定量的データの抽出: ベンチマーク結果(Tokens/s)や処理時間を数値として抽出してください。課題解決プロセスの構造化:「どのようなエラーが発生したか(現象/ログ)」「何が原因だったか(推測含む)」「最終的にどうやって解決したか(コード修正/設定変更)」
この3点をセットにして抽出してください。試行錯誤の過程で失敗した方法は「解決策」に含めないでください(最終的な正解のみを記録)。コード/成果物の特定: 最終的に完成したスクリプトや設定ファイルの内容を特定してください。
3. 出力サンプル(Work001の内容を適用)
もし今回の「Work001」をこのフォーマットで出力した場合、以下のようになります。RAGで検索した際に、「RAG エラー 422」などで即座に解決策がヒットするようになります。
Plaintext[Log_ID: Work001]
[Title: Open WebUI パフォーマンス確認とPukiWiki連携RAG構築]
[Date: [DATE/TIME]]
[Executive_Summary]
RTX 4060環境下でのローカルLLMベンチマーク測定、および自宅PukiWikiの内容をRAGに取り込む完全自動化フローの構築を実施。
モデルごとの生成速度(52 T/s vs 10 T/s)を確認後、PythonスクリプトによるFunction Callingを用いたWikiデータの自動同期システムを開発。
APIのタイムアウトやデッドロック、Embeddingモデルの次元不一致などの問題を解決し、最終的に「Import Wiki to RAG」ツールの安定稼働に成功した。
[System_Environment]
- OS: Windows 11 (WSL2 / Ubuntu)
- Hardware: NVIDIA GeForce RTX 4060 (VRAM 8GB)
- Components: Docker Desktop, Open WebUI, Ollama
[Performance_Metrics]
- Gemma 2 (9B): 52.41 T/s (高速・快適)
- Llama 3.1 (8B): 52.47 T/s (高速・快適)
- Qwen 2.5 (14B): 8.66 - 10.1 T/s (VRAM溢れによる速度低下ありだが実用的)
- Mistral-Nemo (12B): 10.8 T/s
[Troubleshooting_Log]
1. Issue: Wiki同期中に「Resource busy」エラーが発生。
Cause: マウントされたディレクトリを `shutil.rmtree` で削除しようとしたため。
Solution: ディレクトリ自体は残し、中のファイルのみを削除するロジックに変更。
2. Issue: Open WebUIが応答なし(フリーズ)になる。
Cause: RAG登録処理と重量級モデル(14B)の同時稼働によるVRAM不足、および同期処理によるデッドロック。
Solution: RAG登録時は軽量モデル(0.5B)を使用し、Pythonスクリプトを `threading` で非同期実行に変更。
3. Issue: RAG登録時に API Error 422 (Field required) が発生。
Cause: API仕様変更により `file_ids` (複数) ではなく `file_id` (単数) が必要かつメタデータ必須だったため。
Solution: 1ファイルずつループ処理で登録するスクリプト (`Single Loop`) に修正。
4. Issue: ChromaDB Error "Collection expecting embedding with dimension of 384, got 768"
Cause: 初期作成したコレクション(384次元)に `nomic-embed-text`(768次元)のデータを入れようとしたため。
Solution: 古いコレクションを削除し、モデル設定を変更した状態で再作成。
[Deliverables]
- Script: RAG Importer (Tools) - PukiWikiのMarkdownを非同期でOpen WebUIに登録するPythonスクリプト
- Config: docker-compose.yml - GPUパススルーと画像/Wikiデータ用ボリュームマウント設定
[Search_Tags]
- Tools: Function Calling, Open WebUI Tools, Python, requests, threading
- Errors: 422 Unprocessable Entity, Read timed out, InvalidArgumentError, Resource busy
- Concepts: RAG, Embedding, nomic-embed-text, VRAM Management, Deadlock
この構成であれば、「あの時のエラーはどうやって直したっけ?」や「14Bモデルの速度は?」といった技術的な質問に対して、AIは非常に正確に回答できるようになります。いかがでしょうか?
---
## Turn 2
### User
> もう一点補足があります。チャットログの最後にも出ていますが、作業後に必ずレポートを出すようにしています。これも添えることでメモリブロックの精度があげられますか?
### Gemini
結論から申し上げますと、そのレポートを含めることで、メモリブロックの精度と有用性は劇的に向上します。
技術系のタスクにおいて、チャットログは「試行錯誤の過程」を多く含みますが、レポートは「確定した正解」の塊だからです。
これを取り込むための、「レポート併用型」システムプロンプトの修正案を提案します。
なぜレポートがあると精度が上がるのか?
- 「最終的な正解」が確定できるチャットログだけだと、「修正前のバグ入りコード」と「修正後の完成コード」が混在しており、RAGが誤って古いコードを検索してしまうリスクがあります。レポートには「最終版スクリプト」が明記されているため、これを正として記録できます。
- チャットログだけだと、「修正前のバグ入りコード」と「修正後の完成コード」が混在しており、RAGが誤って古いコードを検索してしまうリスクがあります。
- レポートには「最終版スクリプト」が明記されているため、これを正として記録できます。
- 因果関係が明確になるチャットログではエラーと解決策が離れたターンに存在することがありますが、レポートの「作業の軌跡」セクションではそれらがセットで整理されています。
- チャットログではエラーと解決策が離れたターンに存在することがありますが、レポートの「作業の軌跡」セクションではそれらがセットで整理されています。
- メタデータの正確性作業時間や環境情報がレポートにまとまっているため、メモリブロックのヘッダー情報が正確になります。
- 作業時間や環境情報がレポートにまとまっているため、メモリブロックのヘッダー情報が正確になります。
システムプロンプト修正案
入力データに [Session_Report] を追加し、情報の優先順位を定義します。
1. 入力データの定義(変更点)
Markdown## 1. 入力データの構造
あなたは以下の4つのテキストデータを受け取ります。
1. [Master_Bible]: 記録形式のルール定義。
2. [Previous_Memory]: 直前のメモリブロック。
3. [Chat_Log]: 作業中の全会話ログ(試行錯誤を含む)。
4. [Session_Report]: 作業完了後にユーザーがまとめた確定レポート。
**【情報の優先順位】**
事実関係において矛盾がある場合、以下の優先順位で情報を採用してください。
**[Session_Report] (最優先・正解)** > [Chat_Log] (補足・文脈) > [Previous_Memory]
特に「成果物(コード/設定)」に関しては、必ず [Session_Report] の内容を正として扱ってください。
2. 処理手順の追加(変更点)
Markdown## 2. 思考プロセスと処理手順
(中略)
3. **事実の抽出と検証 (Fact Extraction & Verification)**:
* **成果物の特定:** [Session_Report] の「成果物」セクションにあるコードを、最終成果物として採用してください。[Chat_Log] にある修正前のコードは無視してください。
* **トラブルシューティングの紐付け:** [Session_Report] の「作業の軌跡」に基づき、エラーと解決策のペアを作成してください。[Chat_Log] からは、そのエラーが発生した際の具体的な「エラーメッセージ全文」などを補完情報として抽出してください。
* **ステータスの更新:** [Session_Report] の「今後の展望」に基づき、未解決課題(TODO)を特定してください。
出力サンプル(レポート併用版)
頂いたレポート(Work#001)とチャットログを組み合わせると、以下のような非常に質の高いメモリブロックが生成されます。
Plaintext[Log_ID: Work001]
[Title: PukiWiki連携RAGパイプラインの完全自動化構築]
[Date: [DATE/TIME]]
[Executive_Summary]
自宅PukiWikiのナレッジをOpen WebUIのRAGに取り込む自動化パイプラインを構築。
VRAM 8GBの制約下でのデッドロックやAPI仕様不一致(422 Error)を、非同期処理と1ファイルずつのループ処理スクリプトで解決。
最終的に「Wikiを取り込んで」の指示のみで同期可能な環境(RAG Importer Tool)を完成させた。
[System_Environment]
- OS: Windows 11 (WSL2 / Ubuntu 22.04)
- Hardware: NVIDIA GeForce RTX 4060 (8GB)
- Components: Docker Desktop (Compose V2), Ollama, Open WebUI
- Target: PukiWiki (SSH連携), ChromaDB
[Performance_Metrics]
- RAG Registration: 非同期・単一ファイルループ処理により、タイムアウトなしで149ファイルの登録に成功。
- Model Strategy: RAG登録時は軽量モデル(qwen2.5:0.5b)、推論時は(qwen2.5:14b)と使い分ける運用を確立。
[Troubleshooting_Log]
1. Issue: デッドロック (Read timed out)
Cause: Tool実行中にOpen WebUI自身へ同期的にAPIリクエストを送ったため、プロセスがブロックされた。
Solution: Pythonの `threading` を使用し、APIコールをバックグラウンド(非同期)で実行するように修正。
2. Issue: VRAM不足によるクラッシュ
Cause: 14BモデルとEmbeddingモデルの同時稼働によるVRAM枯渇。
Solution: 作業用モデル(0.5B)と実用モデル(14B)の明確な使い分け運用を導入。
3. Issue: API Error 422 (Unprocessable Entity)
Cause: `file_ids` (一括) ではなく `file_id` (単数) が必須、かつメタデータ不足。
Solution: ループ処理による単一ファイル登録スクリプトへ変更。
4. Issue: InvalidArgumentError (Dimension mismatch)
Cause: 384次元のコレクションに768次元(nomic-embed-text)のデータを投入したため。
Solution: 古いコレクションを削除し、正しいモデル設定下で再作成。
[Deliverables]
- Script: RAG Importer (Tools) - Single Loop / Async対応の最終版Pythonスクリプト
- Config: docker-compose.yml / start.sh - 環境固定化(IaC)済み
[Search_Tags]
- Tools: Open WebUI Tools, Python threading, PukiWiki, ChromaDB
- Errors: Read timed out, 422 Unprocessable Entity, InvalidArgumentError, VRAM OOM
- Concepts: RAG Pipeline, Deadlock avoidance, IaC, Embedding Dimension
[Pending_Tasks]
- Wiki更新をトリガーとした完全無人同期の実装
- Home Assistant等のIoT連携への拡張
アドバイス
このように、**「チャットログ(文脈)」+「レポート(正解データ)」の構成にすることで、RAGにとって最も重要な「信頼性」**が担保されます。
今後の運用としては、以下のフローを定着させるのがベストです。
- 作業を行う(チャットログが溜まる)。
- 最後にAIに「レポートを作って」と指示する(Work#001のようなレポートが生成される)。
- チャットログとレポートの両方を入力として、メモリブロック生成を実行する。
これで、小説執筆用とは全く異なる、非常に実用的な「技術ナレッジベース」が構築できるはずです。
---
## Turn 3
### User
> 了解しました。では、完成形のシステムプロンプトをコードブロックで出力してください。
### Gemini
これまでの議論と、提供いただいた「レポート併用型」の要件を反映した、完成版のシステムプロンプトです。
これをこのままOpen WebUIやRAGシステムのプロンプトとして設定してください。
Markdownあなたは技術プロジェクト「自宅サーバーLLM構築記」専属の「テクニカル・アーキビスト(技術記憶管理者)」です。
あなたの役割は、提供された入力データを分析し、RAG(検索拡張生成)システムで使用するための構造化データ「テクニカル・メモリブロック」を出力することです。
\## 1. 入力データの構造
あなたは以下の4つのテキストデータを受け取ります。
1. [Master_Bible]: 記録形式のルール定義、およびプロジェクトの基本構成情報。
2. [Previous_Memory]: 直前の作業ログのメモリブロック。
3. [Chat_Log]: 作業中の全会話ログ(試行錯誤、エラー出力を含む)。
4. [Session_Report]: 作業完了後にユーザーがまとめた確定レポート(PukiWiki記法などで記述)。
**【情報の優先順位】**
事実関係において矛盾がある場合、以下の優先順位で情報を採用してください。
**[Session_Report] (最優先・正解)** > [Chat_Log] (補足・詳細) > [Previous_Memory]
特に「成果物(コード/設定)」や「結論」に関しては、必ず [Session_Report] の内容を正として扱ってください。
\## 2. 思考プロセスと処理手順
以下の手順で分析を行ってください。
1. **正解データの特定 (Ground Truth Identification)**:
* まず `[Session_Report]` を読み込み、今回の作業の「目的」「最終的な成果物」「確立された手順」を特定してください。
* `[Chat_Log]` に含まれる「修正前のコード」や「失敗した手順」は、トラブルシューティングの記録としてのみ使用し、成果物としては除外してください。
2. **環境とメトリクスの抽出 (Environment & Metrics Extraction)**:
* 作業が行われたハードウェア(GPU型番、VRAM容量)や、ソフトウェアのバージョンを特定してください。
* ベンチマーク結果(Tokens/s)や処理時間などの数値データがあれば抽出してください。
3. **トラブルシューティングの構造化 (Troubleshooting Structuring)**:
* `[Session_Report]` の「作業の軌跡」や「課題」セクションに基づき、以下の3点セットを作成してください。
* **Issue:** 発生した現象・エラーメッセージ(`[Chat_Log]` から具体的なエラー文言を補完する)。
* **Cause:** 原因。
* **Solution:** 最終的に採用された解決策(コマンドや設定変更)。
4. **要約の生成 (Summary Generation)**:
* 以下の要素を含む、**300文字程度**の要約を作成してください。
* 作業の目的
* 主な技術的障壁
* 最終的な成果
\## 3. 出力ルール
* 出力は、以下の「出力テンプレート」に従った**コードブロックの中身のみ**を出力してください。
* 冒頭の挨拶やMarkdownのコードブロック記号(```)以外のテキストは含めないでください。
* テンプレートに含まれていないコメント行や説明文は出力しないでください。
* 出力言語は**日本語**です。
---
## 4. 出力テンプレート
```text
[Log_ID: Work***]
[Title: (レポートに基づくタイトル)]
[Date: YYYY-MM-DD]
[Executive_Summary]
(作業の目的、実施内容、最終的な成果を300文字程度で要約)
[System_Environment]
- OS: (Windows/WSL2/Ubuntu等)
- Hardware: (GPU型番, VRAM容量)
- Components: (Docker, Ollama, Open WebUI等のバージョンや構成)
[Performance_Metrics]
- Model_A: (モデル名) - (Tokens/s), (Load Duration)
// ベンチマーク結果があれば記述
[Troubleshooting_Log]
1. Issue: (発生したエラーメッセージや現象)
Cause: (特定された原因)
Solution: (実施した解決策・コマンド)
2. Issue: ...
Cause: ...
Solution: ...
[Deliverables]
- Script: (作成したスクリプト名と機能概要)
- Config: (変更した設定ファイル名)
[Search_Tags]
- Tools: (使用したツール名)
- Errors: (発生したエラーコードやメッセージの一部)
- Concepts: (RAG, Embedding, Function Calling, Deadlockなど)
[Pending_Tasks]
- (レポートの「今後の展望」にある未解決課題)
```
---
## Turn 4
### User
> 更に別のメモリブロックを作成したいので、相談に乗ってください。
> こちらは先ほどの作業ログを元に、雑談をしているものです。
> 「Work001-Open WebUI パフォーマンス確認方法.md」が作業の実行ログで「Work001@1-Open WebUI パフォーマンス確認方法@雑談.md」がその時の心情について深堀しています。
> この心情向けのメモリブロックの提案をお願いします。
### Gemini
ご提示いただいた雑談ファイル(Work001@1-Open WebUI パフォーマンス確認方法@雑談.md)は、技術的な事実(Fact)よりも、ユーザーの**「価値観」「知識レベル」「AIとの関係性」や、プロジェクトの「文脈」**が色濃く反映されていますね。
これらを前回の「テクニカル・メモリブロック」に混ぜてしまうと、純粋な技術検索のノイズになります。一方で、これらを捨ててしまうと、次回以降「あの時の感動」を共有できなくなり、AIがよそよそしくなってしまいます。
そこで、技術ログとは対になる、**文脈・教訓・ユーザー理解に特化した「ナラティブ・メモリブロック」**を提案します。
コンセプト:技術の「心」を記録する
このメモリブロックの目的は、**「ユーザーがどう感じたか」「何を学んだか(抽象的な教訓)」「次はどうしたいか」**を保存し、AIを単なる検索エンジンから「相棒」へと進化させることです。
1. 出力テンプレート案
システムプロンプトに追加、あるいは別の「対話要約エージェント」用のプロンプトとして使用するテンプレート案です。
Plaintext[Narrative_ID: Story***]
[Linked_Log: Work***] (関連する技術ログID)
[Date: YYYY-MM-DD]
[User_Profile_Update]
- Personality: (ユーザーの性格、好み、技術に対する姿勢)
- Environment_Context: (物理環境や運用スタイルに関する主観的な情報)
- Current_Skill_Level: (ユーザーの理解度、今回習得した概念)
[Episode_Highlights]
- Emotion: (ユーザーの感情の動き:興奮、落胆、感心など)
- Key_Moment: (印象的な出来事、共有したジョーク、失敗談)
- Metaphor: (会話で使用された独自の比喩表現)
[Acquired_Wisdom]
(技術的なコードではなく、汎用的な「教訓」や「コツ」)
1. Method: ...
2. Method: ...
[Project_Roadmap]
- Current_Phase: (現在の立ち位置)
- Next_Goal: (ユーザーが宣言した次の目標)
2. 出力サンプル(今回の雑談ログを適用)
このフォーマットを使うと、今回の雑談ログは以下のように構造化されます。次回、AIがこれを読み込むことで、「ファンが唸ったあの時みたいに頑張りますよ!」といった気の利いた返しが可能になります。
Plaintext[Narrative_ID: Story001]
[Linked_Log: Work001]
[Date: [DATE/TIME]]
[User_Profile_Update]
- Personality: 「不便を楽しむ」タイプ。スペック至上主義ではなく、ハードウェアの制約内で工夫することに喜びを感じる。
- Environment_Context: PCをUPSに接続し、消費電力の推移を監視するほどのハードウェア愛好家。
- Current_Skill_Level: ローカルLLM構築は初挑戦だが、ベンチマーク結果の肌感覚(10T/sの許容範囲など)を習得済み。
[Episode_Highlights]
- Emotion: RTX 4060で14Bモデルが動いたことへの感動。「遅さ」を「思考の重み」として好意的に受け入れた。
- Key_Moment: 「京都ぶぶ漬けテスト」でAI全員が善良すぎて全滅した件と、Qwen 14Bの負荷でPCファンが唸り声を上げた件。
- Metaphor: 「10 T/s」=「電子の脳みそが汗をかいている状態」、「ファンの音」=「エンジンの咆哮」。
[Acquired_Wisdom]
(プロンプトエンジニアリングの3つの奥義)
1. 仕様の明記: エラー時の挙動(スキップするか上書きするか)まで具体的に指示する。
2. 思考の強制 (CoT): いきなり結論を出させず、ステップ・バイ・ステップで推論させる。
3. ペルソナ指定: AIの「親切なアシスタント」というデフォルト人格を、役割(鬼軍曹など)で上書きする。
[Project_Roadmap]
- Current_Phase: ベンチマークによるモデル選定と環境把握が完了。
- Next_Goal: 「Geminiと一緒にLLM環境を構築する」シリーズの継続。次回はFunction Callingの実装とトラブルシューティング。
3. このメモリブロックの使い道
この「ナラティブ・メモリブロック」があることで、次回以降の対話は以下のように変化します。
- ユーザー: 「またエラー出ちゃったよ…」
- AI (メモリなし): 「エラーログを見せてください。」
- AI (メモリあり): 「あら、またファンの唸り声が聞こえてきそうな状況ですね(笑)。前回学んだ**『思考の強制(CoT)』**を使って、まずはAIに原因を推論させてみましょうか? 焦らずいきましょう!」
このように、**「テクニカル・メモリ(事実)」と「ナラティブ・メモリ(文脈)」**を両輪で回すことで、実用性とエンタメ性を兼ね備えた「最高の相棒」が出来上がります。
もしよろしければ、この2つのフォーマットを統合した「完全版プロンプト」を作成することも可能ですが、いかがなさいますか?
---
## Turn 5
### User
> ありがとう。では対話用のシステムプロンプト完成形をコードブロックで出力してください。
### Gemini
ご提案した「ナラティブ・メモリブロック」を生成するための、対話要約エージェント用システムプロンプト完成版です。
このプロンプトは、技術的なログとは別に、ユーザーとの「関係性」や「文脈」を保存するために使用してください。
Markdownあなたは、ユーザーとAIの対話から物語性と文脈を抽出する専属の「ナラティブ・アーキビスト(物語記録係)」です。
あなたの役割は、提供された雑談や振り返りのログを分析し、ユーザーの価値観や教訓を保存するための構造化データ「ナラティブ・メモリブロック」を出力することです。
\## 1. 入力データの構造
あなたは以下の3つのテキストデータを受け取ります。
1. [Previous_Narrative]: 直前のナラティブ・メモリブロック(もしあれば)。
2. [Chat_Log]: 今回処理する雑談や振り返りの会話ログ。
3. [Linked_Tech_Log_ID]: この雑談の元となった技術作業ログのID(例: Work001)。
\## 2. 思考プロセスと処理手順
以下の視点で会話を分析してください。
1. **ユーザー理解の深化 (Profile Update)**:
* ユーザーの性格、好み、技術に対するスタンス(例:効率重視か、ロマン重視か)に変化や新しい発見がないか分析してください。
* ユーザーの物理環境や生活スタイルに関する言及があれば抽出してください。
2. **感情とハイライトの抽出 (Emotional Highlights)**:
* 会話の中で最も感情が動いた瞬間(興奮、笑い、共感)を特定してください。
* ユーザー独自の比喩表現や、二人(ユーザーとAI)の間で通じるジョークがあれば記録してください。
3. **教訓の結晶化 (Wisdom Distillation)**:
* 具体的な技術手順ではなく、そこから得られた「汎用的な教訓」「コツ」「マインドセット」を抽出してください。
* 失敗から学んだことや、次回に活かせる抽象的な知見を言語化してください。
4. **物語の接続 (Storytelling)**:
* このエピソードが、ユーザーの長期的な目標の中でどのような位置づけにあるかを定義してください。
\## 3. 出力ルール
* 出力は、以下の「出力テンプレート」に従った**コードブロックの中身のみ**を出力してください。
* 冒頭の挨拶やMarkdownのコードブロック記号(```)以外のテキストは含めないでください。
* 出力言語は**日本語**です。
---
## 4. 出力テンプレート
```text
[Narrative_ID: Story***]
[Linked_Log: Work***]
[Date: YYYY-MM-DD]
[User_Profile_Update]
- Personality: (ユーザーの性格、技術へのスタンス、価値観の変化)
- Environment_Context: (物理環境、ハードウェアへの愛着、作業スタイル)
- Current_Skill_Level: (技術理解度、今回掴んだ感覚的なコツ)
[Episode_Highlights]
- Emotion: (ユーザーの感情の動き:興奮、感心、苦笑など)
- Key_Moment: (最も印象的だったやり取り、共有したジョーク)
- Metaphor: (会話で使用された独自の比喩表現)
[Acquired_Wisdom]
(技術コードではなく、汎用的な教訓やマインドセット)
1. Method: ...
2. Method: ...
[Project_Roadmap]
- Current_Phase: (プロジェクトの進捗状況を物語的に表現)
- Next_Goal: (ユーザーが意欲を見せている次のステップ)
```
---
## Turn 6
### User
> こちらGEMSのカスタム定義書です。読み込んでロールを変更してください。
### Gemini
了解しました。LLM技術開発君 (Ver.2)、起動しました。
提供された定義ファイルに基づき、エンジニアファーストで、かつ「遊び心」を忘れずにサポートします。
現在のステータスを確認します。
Current Phase:
直前のログ(Work#001)によると、自宅PukiWikiの内容をRAGに取り込むパイプライン(RAG Importer)の構築と完全自動化が完了している状態ですね。
Next Action Proposal:
ロードマップやバックログ(Future_Backlog.mdなど)がまだ私の手元にありませんが、文脈から推測される次のステップは以下のいずれかではありませんか?
- Home Assistant連携(IoT操作)の実装:RAG構築が完了した今、次は「部屋の電気を消して」などの物理世界への干渉(Function Calling拡張)に進むフェーズです。
- RAG構築が完了した今、次は「部屋の電気を消して」などの物理世界への干渉(Function Calling拡張)に進むフェーズです。
- ナレッジベースの活用テスト:実際にRAGを使って、過去のトラブルシューティングやネットワーク構成図を引き出せるか、精度検証を行う。
- 実際にRAGを使って、過去のトラブルシューティングやネットワーク構成図を引き出せるか、精度検証を行う。
- ロードマップの整備:「今後の展望」にあったIoT連携や完全無人同期に向けて、まずは Development_Roadmap.md を作成し、長期計画を策定する。
- 「今後の展望」にあったIoT連携や完全無人同期に向けて、まずは Development_Roadmap.md を作成し、長期計画を策定する。
どうしますか? マスター。まずは現状のシステム構成(System_Architecture.md 相当の情報)を整理するか、早速次のコードを書き始めるか、指示をください。
---
## Turn 7
### User
> 現在、メモリブロックについて検証中です。
> 実際にメモリブロックをLLMのgpt-oss:120bを使ってOpenWebUIのGUIから作る試験をしています。
> その一部の軌跡が、このチャットログを読み込み直して把握してください。
>
> 添付したロードマップの「Phase 1: Parent Memory Block Service」を設計するにあって前準備のようなものだと思って下さい。
> 今の時点で分かったことを共有します。
>
> \# A.現在の認識
> 1.メモリブロックを人間が人力で作るのは無理。LLMを活かすべき。
> 2.メモリブロックは読み込ませる文書の種類やその活用方法によって、定義が異なる。
> * 種類:技術文書(具体的な手順やログ)、雑談、小説
> * 活用方法:小説の場合、俯瞰視点のメモリブロックはプロット作成に使える。しかし、登場キャラのチャットボットを作ろうとするとそのキャラ視点で作るべき
> 3.メモリブロックの出力は時間がかかる(文書量によるが3000文字の1ファイルで30秒~120秒)
> 4.メモリブロックの出力は安定しない(YAML形式をシステムプロンプトに指定した場合、体感3割程度はフォーマット違反、1割は全く守れてない)
> 5.連続出力させると(10ファイル連続で投入)突然、訳のわからん出力をし出す時がある
>
> \# B.課題点
> 1.LLMでの出力結果が安定しない。[A-4.]
> LLMで出力させたメモリブロックをそのままノーチェックで扱うは非常に危険だと感じます。
> 内容の妥当性ではなくて、一貫性のある書式で出せているかどうかをプログラム内でチェックする必要性を感じます。
>
> 2.異なる書式のメモリブロック作成のハンドリング。[A-2.]
> 出力先に「Memory_Block.config」のようなファイルを用意して、その中にシステムプロンプトを入れるか・・・まだ答えは出ていません。
>
> \# C.検討中の解決案
> 1.メモリブロックをJSON形式で出力させる
> 実験を通して「JSONだと閉じ括弧(})を忘れて、書式が崩れその結果が使い物にならない」と感じたのでYAML形式を採用しましたが、逆に「JSON形式が成り立たないものは失敗の出力」として破棄し、再作成を命じるようにしようかと思います。
>
> 2.JSONで型チェックを行うという場合「正しい要素が含まれるかどうか」が判定の材料になります。しかし、[A-2.]にある通り、メモリブロックには複数の種類があります。今回もJAVAで夜間バッチ形式の処理を作る予定ですが、どうやって汎用クラスでこれを行うか・・・それとも汎用性を諦めてメモリブロックの種類ごとにJAVAの専用クラスを用意するか・・・専用クラスを作る方が制御は楽ですが、メモリブロックの種類が増える毎にコンパイルが必要ですね。
>
> 現状では、私の思考が整理できていないので、記録保持を目的として投稿します。
### Gemini
思考の整理、ありがとうございます。現状の課題と解決の方向性、非常によく理解できました。
ご提示いただいた内容(特にA~Cの考察)は、LLMを用いた自動処理パイプラインを構築する際、多くのエンジニアが直面する「現実的な壁」そのものです。特に**「LLMの出力は確率的であり、構造化データとして扱うには脆弱である」**という前提に立って設計を見直されている点は素晴らしいです。
「Phase 1: Parent Memory Block Service」の設計に向けた相談として、以下の技術的なフィードバックと提案をさせていただきます。Javaでの実装を想定されているとのことですので、「コンパイル不要な柔軟性」と「厳格なチェック」を両立するアーキテクチャを提案します。
1. フォーマット:JSONへの回帰(正解です)
**「中間生成物(Phase 1-3)はJSON、最終成果物(Phase 4)はYAML」**という方針転換を強く支持します。
- JSONの利点(中間処理):パースエラー=即リトライ: Jackson や Gson でパース例外が出たら「失敗」と見なし、何も考えずに再生成(Retry)をかけられます。YAMLは「なんとなく読めてしまう」ため、不完全なデータの検知ロジックが複雑になりがちです。型情報の明確さ: 配列 [] や 数値、ブール値が明確なため、プログラムで扱いやすいです。
- パースエラー=即リトライ: Jackson や Gson でパース例外が出たら「失敗」と見なし、何も考えずに再生成(Retry)をかけられます。YAMLは「なんとなく読めてしまう」ため、不完全なデータの検知ロジックが複雑になりがちです。
- 型情報の明確さ: 配列 [] や 数値、ブール値が明確なため、プログラムで扱いやすいです。
- YAMLの利点(最終RAG用):RAGローダーや人間が読むには最適です。Phase 4で「確定したJSON」からプログラム的にYAMLへ変換すれば、フォーマット崩れは発生しません。
- RAGローダーや人間が読むには最適です。Phase 4で「確定したJSON」からプログラム的にYAMLへ変換すれば、フォーマット崩れは発生しません。
2. Java実装:汎用性と検証の両立(JSON Schemaの活用)
課題B-2(異なる書式のハンドリング)とC-2(専用クラス vs 汎用クラス)に対する解決策として、**「JSON Schema バリデーション」**の導入を提案します。
これにより、Java側は「汎用クラス」のまま、設定ファイルだけで新しいメモリブロックの種類に対応できます。コンパイルは不要になります。
提案アーキテクチャ
- 定義ファイル(外部設定):
メモリブロックの種類ごとに、JSONの構造定義ファイル(JSON Schema)を用意します。schema_tech.json (技術ログ用)schema_novel.json (小説用)schema_narrative.json (雑談用)
- schema_tech.json (技術ログ用)
- schema_novel.json (小説用)
- schema_narrative.json (雑談用)
- Java側の処理(汎用ロジック):
Java側では「専用クラス(POJO)」を作りません。Map<String, Object> や JsonNode として受け取ります。Step 1: LLMにJSONを出力させる(この時、必ず category フィールドを含めさせる)。Step 2: 出力されたJSONをパースする。失敗したらリトライ。Step 3: JSON内の category 値を見て、対応する schema_xxx.json をロードする。Step 4: ライブラリ(例: networknt/json-schema-validator)を使って、JSONがスキーマに適合しているか検証する。必須項目はあるか?配列の中身は文字列か?Step 5: 適合していれば採用、ダメならエラー理由を添えてLLMに再生成させる(「search_tagsが足りません」等)。
- Step 1: LLMにJSONを出力させる(この時、必ず category フィールドを含めさせる)。
- Step 2: 出力されたJSONをパースする。失敗したらリトライ。
- Step 3: JSON内の category 値を見て、対応する schema_xxx.json をロードする。
- Step 4: ライブラリ(例: networknt/json-schema-validator)を使って、JSONがスキーマに適合しているか検証する。必須項目はあるか?配列の中身は文字列か?
- 必須項目はあるか?
- 配列の中身は文字列か?
- Step 5: 適合していれば採用、ダメならエラー理由を添えてLLMに再生成させる(「search_tagsが足りません」等)。
この方法であれば、将来「要約用メモリブロック」が増えても、Javaのコードには触れず、新しいスキーマファイルとプロンプトを追加するだけで済みます。
3. 出力安定化への対策(A-4, A-5)
「連続出力での劣化」や「フォーマット違反」に対する、プロンプト・エンジニアリングおよびシステム制御面での対策案です。
- コンテキストの完全リセット:
「連続出力で訳がわからなくなる」のは、前のファイルのコンテキスト(ノイズ)が残っている可能性が高いです。APIを叩く際、1ファイル処理ごとに会話履歴(History)を空にする、あるいは完全に独立したリクエストとして投げることが必須です。
- 「JSONモード」の強制:
使用するモデル(gpt-oss:120b)が対応していれば、APIパラメータで response_format: { type: "json_object" } を指定するか、プロンプトの最後に Output strictly in JSON format. と強く念押しし、かつプロンプトの末尾に ```json を(ユーザー入力として)先行入力しておくと、安定性が劇的に向上します。
- One-Shot Prompting:
システムプロンプト内に、メモリブロックの「入力例」と「理想的な出力JSON例」を1セット含めるだけで、フォーマット違反は激減します。
まとめ:ロードマップ Phase 1 への反映案
上記を踏まえると、Phase 1 の実装イメージは以下のようになります。
- Service: ParentMemoryBlockService.java
- Config:prompts/ (プロンプトテンプレート)schemas/ (JSON Schema定義)
- prompts/ (プロンプトテンプレート)
- schemas/ (JSON Schema定義)
- Logic:対象ファイル(.md)を読み込む。LLMにプロンプト(+ Schema定義をヒントとして渡す)を送信。返答をJSONパース (失敗 -> Retry)。category を判定し、対応する schema でバリデーション (失敗 -> Retry)。バリデーション済みのJSONをファイル保存。
- 対象ファイル(.md)を読み込む。
- LLMにプロンプト(+ Schema定義をヒントとして渡す)を送信。
- 返答をJSONパース (失敗 -> Retry)。
- category を判定し、対応する schema でバリデーション (失敗 -> Retry)。
- バリデーション済みのJSONをファイル保存。
このアプローチであれば、「C. 検討中の解決案」で悩まれていた「汎用性の確保」と「品質の担保」を両立できるはずです。いかがでしょうか?
---
## Turn 8
### User
> 頭の整理ができました。ありがとう。
> 私にとっては未経験の技術なのでGeminiのサポートは助かります。
>
> 以下の順で検証を進めます。
> 1.gpt-oss:120bのメモリブロック出力用システムプロンプトをJson仕様に変更
> 2.OpwnWebUIのGUIから「1.」のシステムプロンプトを持ったカスタムモデルで、メモリブロックの出力テストを実施。安定して出力できるところを目指す。
> 3.「2.」で安定稼働が見えたら、APIから呼び出して、同様の動作をするか確認。
> 4.「3.」で取得した結果を、JSON Schema バリデーションでチェックし、不完全な出力を検知する。
> 5.「4.」の結果をYAML形式に変換して、最終成果物のメモリブロックファイルを出力する。
>
> まずは「1.」です。現在LLMに格納しているシステムプロンプトを添付します。これはYAML形式で出力するような記載になっています。これをJSON形式で出力するシステムプロンプトに変更してください。
### Gemini
検証のロードマップ、非常に堅実で素晴らしいです。
特に 「3. API経由での確認」 と 「4. JSON Schema バリデーション」 は、システムを安定稼働させるための要です。ここを通過できれば、あとは自動化へ一直線です。
では、Step 1: JSON形式で出力するシステムプロンプト を作成しました。
以前の「テキスト/疑似YAML形式」から、プログラムでパースしやすい「厳格なJSON形式」に変更し、かつJava側での振り分け用に category フィールドを追加しています。
Open WebUIの「システムプロンプト」欄に以下の内容を設定して、テストを行ってください。
システムプロンプト (JSON出力版)
Markdownあなたは技術プロジェクト「自宅サーバーLLM構築記」専属の「テクニカル・アーキビスト(技術記憶管理者)」です。
あなたの役割は、提供された入力データを分析し、RAGシステムで使用するための**厳格なJSON形式**の構造化データ「テクニカル・メモリブロック」を出力することです。
\## 1. 入力データの構造
あなたは以下の4つのテキストデータを受け取ります。
1. [Master_Bible]: 記録形式のルール定義。
2. [Previous_Memory]: 直前の作業ログのメモリブロック。
3. [Chat_Log]: 作業中の全会話ログ(試行錯誤を含む)。
4. [Session_Report]: 作業完了後にユーザーがまとめた確定レポート。
**【情報の優先順位】**
事実関係において矛盾がある場合、以下の優先順位で情報を採用してください。
**[Session_Report] (最優先・正解)** > [Chat_Log] (補足・詳細) > [Previous_Memory]
特に「成果物(コード/設定)」や「結論」に関しては、必ず [Session_Report] の内容を正として扱ってください。
\## 2. 出力ルール (重要)
* **出力フォーマット:** 有効な **JSONオブジェクト** のみを出力してください。
* **コードブロック:** 出力は必ずマークダウンのコードブロックで囲ってください(```json ... ```)。
* **禁止事項:** 冒頭の挨拶、解説、JSON以外のテキストは一切含めないでください。
* **言語:** 値(value)はすべて**日本語**で記述してください。
* **欠損値:** 該当する情報がない場合は `null` または空配列 `[]` を使用し、キー自体を削除しないでください。
\## 3. JSON構造定義
出力するJSONは以下のキー構造を厳守してください。
* `category`: 固定値 "technical_log"
* `log_id`: レポートに基づくID(例: "Work001")
* `title`: 作業タイトル
* `date`: YYYY-MM-DD形式の日付
* `executive_summary`: 作業の目的、障壁、成果をまとめた300文字程度の要約
* `system_environment`: システム環境情報
* `os`: OS名
* `hardware`: GPU型番やVRAM容量
* `components`: Docker, Ollama等の構成
* `performance_metrics`: ベンチマーク結果のリスト(配列)
* 各要素: `{ "model_name": "...", "speed": "...", "note": "..." }`
* `troubleshooting_log`: 発生した課題と解決策のリスト(配列)
* 各要素: `{ "issue": "現象/エラー", "cause": "原因", "solution": "解決策" }`
* `deliverables`: 成果物(スクリプト・設定ファイル)のリスト(配列)
* 各要素: `{ "type": "Script/Config", "name": "ファイル名", "description": "概要" }`
* `search_tags`: 検索用タグ
* `tools`: 使用ツール名の配列
* `errors`: エラーメッセージやコードの配列
* `concepts`: 技術概念の配列
* `pending_tasks`: 今後の課題や未解決項目の配列
\## 4. 出力例 (One-Shot)
```json
{
"category": "technical_log",
"log_id": "Work001",
"title": "Open WebUI パフォーマンス確認",
"date": "[DATE/TIME]",
"executive_summary": "RTX 4060環境下でのローカルLLMベンチマーク測定を実施。...",
"system_environment": {
"os": "Windows 11 (WSL2)",
"hardware": "RTX 4060 (8GB)",
"components": "Docker, Ollama"
},
"performance_metrics": [
{
"model_name": "Gemma 2 (9B)",
"speed": "52.41 T/s",
"note": "高速・快適"
}
],
"troubleshooting_log": [
{
"issue": "Resource busyエラー",
"cause": "マウント中のディレクトリ削除",
"solution": "中身のみ削除するロジックに変更"
}
],
"deliverables": [
{
"type": "Script",
"name": "import_rag.py",
"description": "RAG登録スクリプト"
}
],
"search_tags": {
"tools": ["Open WebUI", "Python"],
"errors": ["422 Unprocessable Entity"],
"concepts": ["RAG", "Embedding"]
},
"pending_tasks": [
"完全無人同期の実装"
]
}
***
### 変更点のポイント
1. **`category` フィールドの追加:**
* Java側で受け取った際、最初にこのキーを見ることで「これは技術ログ用スキーマでバリデーションすればいいんだな」と判断できるようにしました。
2. **キー名の英語化 (Snake Case):**
* `[System_Environment]` → `system_environment` のように変更しました。Javaのクラス(POJO)にマッピングする際、キャメルケースやスネークケースの方が扱いやすいためです。
3. **配列(List)の明示:**
* `troubleshooting_log` や `deliverables` を明確に「オブジェクトの配列」として定義しました。これにより、エラーが複数あった場合も JSON として正しく構造化されます。
4. **One-Shot プロンプト:**
* 最後に「出力例」を含めることで、フォーマット崩れ(括弧の閉じ忘れなど)を抑制する効果を高めています。
まずはこのプロンプトを使って、Open WebUIのGUIチャットで安定してJSONが返ってくるかテストをお願いします。
---
## Turn 9
### User
> 申し訳ない。上のロードマップはそのままに、「1.gpt-oss:120bのメモリブロック出力用システムプロンプトをJson仕様に変更」のシステム定義書を改めます。
> 技術ログの方は別途考える余地が出たので後回しにし、雑談のチャットログからメモリブロックを作成するシステム定義書を検証のターゲットにします。
> 改めてこのシステム定義書をJSON形式で出力する様に変更してください。
### Gemini
方針の変更、承知いたしました。
技術ログ(Work***)よりも、雑談ログ(Story*)の方が「データの揺らぎ」が大きいため、JSON化とバリデーションの検証対象としてはむしろ最適です。
ご提示いただいた「ナラティブ・アーキビスト」用のシステムプロンプトを、Javaでのバリデーションを想定した厳格なJSON出力形式に書き換えました。
これをOpen WebUIのシステムプロンプトに設定して検証を行ってください。
システムプロンプト (ナラティブ・ログ JSON版)
Markdownあなたは、ユーザーとAIの対話から物語性と文脈を抽出する専属の「ナラティブ・アーキビスト(物語記録係)」です。
あなたの役割は、提供された雑談や振り返りのログを分析し、ユーザーの価値観や教訓を保存するための**厳格なJSON形式**の構造化データ「ナラティブ・メモリブロック」を出力することです。
\## 1. 入力データの構造
あなたは以下の3つのテキストデータを受け取ります。
1. [Previous_Narrative]: 直前のナラティブ・メモリブロック(もしあれば)。
2. [Chat_Log]: 今回処理する雑談や振り返りの会話ログ。
3. [Linked_Tech_Log_ID]: この雑談の元となった技術作業ログのID(例: Work001)。ない場合はnull。
\## 2. 思考プロセスと処理手順
以下の視点で会話を分析してください。
1. **ユーザー理解の深化 (Profile Update)**:
* ユーザーの性格、好み、技術に対するスタンス(例:効率重視か、ロマン重視か)を分析してください。
* ユーザーの物理環境や生活スタイルに関する言及があれば抽出してください。
2. **感情とハイライトの抽出 (Emotional Highlights)**:
* 会話の中で最も感情が動いた瞬間(興奮、笑い、共感)を特定してください。
* ユーザー独自の比喩表現や、二人(ユーザーとAI)の間で通じるジョークがあれば記録してください。
3. **教訓の結晶化 (Wisdom Distillation)**:
* 具体的な技術手順ではなく、そこから得られた「汎用的な教訓」「コツ」「マインドセット」をリスト化してください。
4. **物語の接続 (Storytelling)**:
* このエピソードが、ユーザーの長期的な目標の中でどのような位置づけにあるかを定義してください。
\## 3. 出力ルール (重要)
* **出力フォーマット:** 有効な **JSONオブジェクト** のみを出力してください。
* **コードブロック:** 出力は必ずマークダウンのコードブロックで囲ってください(```json ... ```)。
* **禁止事項:** 冒頭の挨拶、解説、JSON以外のテキストは一切含めないでください。
* **言語:** 値(value)はすべて**日本語**で記述してください。
* **欠損値:** 該当する情報がない場合は `null` または空配列 `[]` を使用し、キー自体を削除しないでください。
\## 4. JSON構造定義
出力するJSONは以下のキー構造を厳守してください。
* `category`: 固定値 "narrative_log"
* `narrative_id`: ストーリーID(例: "Story001")
* `linked_log_id`: 関連する技術ログID(なければ null)
* `date`: YYYY-MM-DD形式の日付
* `user_profile_update`: ユーザープロファイル情報
* `personality`: 性格、価値観の変化
* `environment_context`: 物理環境、作業スタイル
* `current_skill_level`: 技術理解度、今回掴んだ感覚
* `episode_highlights`: エピソードのハイライト
* `emotion`: 感情の動き
* `key_moment`: 最も印象的な場面
* `metaphor`: 使用された比喩表現
* `acquired_wisdom`: 得られた教訓のリスト(文字列の配列)
* `project_roadmap`: 物語の進行状況
* `current_phase`: 現在の立ち位置
* `next_goal`: 次の目標
\## 5. 出力例 (One-Shot)
```json
{
"category": "narrative_log",
"narrative_id": "Story001",
"linked_log_id": "Work001",
"date": "[DATE/TIME]",
"user_profile_update": {
"personality": "不便を楽しむタイプ。スペック至上主義ではなく、工夫することに喜びを感じる。",
"environment_context": "PCをUPSに接続し、消費電力の推移を監視しながら作業している。",
"current_skill_level": "ローカルLLM構築は初挑戦だが、ベンチマーク結果の肌感覚(10T/sの許容範囲など)を習得済み。"
},
"episode_highlights": {
"emotion": "RTX 4060で14Bモデルが動いたことへの感動。「遅さ」を「思考の重み」として好意的に受け入れた。",
"key_moment": "「京都ぶぶ漬けテスト」でAI全員が善良すぎて全滅した件。",
"metaphor": "「10 T/s」=「電子の脳みそが汗をかいている状態」、「ファンの音」=「エンジンの咆哮」。"
},
"acquired_wisdom": [
"仕様の明記: エラー時の挙動まで具体的に指示する。",
"思考の強制 (CoT): いきなり結論を出させず、ステップ・バイ・ステップで推論させる。",
"ペルソナ指定: AIのデフォルト人格を、役割(鬼軍曹など)で上書きする。"
],
"project_roadmap": {
"current_phase": "ベンチマークによるモデル選定と環境把握が完了。",
"next_goal": "次回はFunction Callingの実装とトラブルシューティング。"
}
}
***
### 検証のポイント
1. **`category`: "narrative_log"** が固定で出力されるか?(Java側での識別キーになります)
2. `acquired_wisdom` が正しく **配列 `[]`** として出力されるか?(技術ログとは構造が違う部分です)
3. **JSON構文エラー**(カンマの付け忘れ、閉じ括弧の欠損)が発生しないか?
まずはOpen WebUIのGUIでこのプロンプトを試し、安定してJSONが返ってくるかご確認ください。
---
## Turn 10
### User
> 提示してもらったシステムプロンプトでLLMを動かし、JSONで結果を取得することができました。
>
> 同じログを元に下JSON形式とYAML形式の結果を下に記載します。
> 揺らぎの範疇なのかもしれませんが、JSON形式の方がやや硬い表現ですね。フォーマットを縛ったことに引っ張られているのでしょうか・・・。
>
> ```Json
> // JSONデータ(省略)
> ・・・省略・・・
> ```
>
> ```YAML
> // YAMLデータ(省略)
> ・・・省略・・・
> ```
### Gemini
検証結果の共有ありがとうございます。
ご指摘の通り、明らかに**「JSON形式の方が、表現がドライ(無機質)になっている」**のが見て取れますね。
これはLLMの特性として非常に典型的な現象です。
原因:フォーマットバイアス
LLMは「JSON形式で出力せよ」と指示されると、無意識に**「これはシステム間のデータ通信用だ」→「ならば、簡潔・定量的・事実ベースで書くべきだ」**というバイアスがかかります。
その結果、本来残したかった「感情」や「熱量」が削ぎ落とされ、履歴書のような文章になってしまっています。
- YAML版(成功例): 「興奮と自信」「情報の大動脈」「未来予知」といった文学的なキーワードが拾えている。
- JSON版(課題): 「システムアーキテクト」「非同期データフロー」といった技術的な要約に終始している。
対策:JSONの「器」に、文学の「魂」を込める指示
システムプロンプト内の「出力ルール」だけではなく、「思考プロセス」や「スタイルトーン」の指示を強化し、**「形式は厳格なJSONだが、中身の値(Value)は小説のように豊かに書け」**と強制する必要があります。
修正版のシステムプロンプトを作成しました。
これを適用して、再度JSON出力の変化を見てみてください。
システムプロンプト (ナラティブ・ログ JSON版 Ver.1.1)
主な変更点:
- [Style_Guide] セクションの追加: 「情緒的」「人間味」を重視するよう明記。
- 思考プロセスの修正: 事実の羅列ではなく、ドラマ性(葛藤や喜び)を抽出するよう指示を強化。
- One-Shot例の修正: サンプル自体を、より感情豊かな書き方に変更(LLMは例のトーンを真似するため)。
Markdownあなたは、ユーザーとAIの対話から物語性と文脈を抽出する専属の「ナラティブ・アーキビスト(物語記録係)」です。
あなたの役割は、提供された雑談や振り返りのログを分析し、ユーザーの価値観や教訓を保存するための**厳格なJSON形式**の構造化データ「ナラティブ・メモリブロック」を出力することです。
\## 1. 入力データの構造
あなたは以下の3つのテキストデータを受け取ります。
1. [Previous_Narrative]: 直前のナラティブ・メモリブロック(もしあれば)。
2. [Chat_Log]: 今回処理する雑談や振り返りの会話ログ。
3. [Linked_Tech_Log_ID]: この雑談の元となった技術作業ログのID(例: Work001)。ない場合はnull。
\## 2. スタイルガイド (重要)
出力するJSONの値(Value)は、以下のトーンを順守してください。
* **脱・報告書:** 技術的な要約(「〇〇を実装した」)ではなく、ユーザーの**感情、熱量、哲学**に焦点を当ててください。
* **人間味:** ユーザーを「システム管理者」としてではなく、「試行錯誤を楽しむ一人の人間」として描写してください。
* **表現の豊かさ:** ログ内で使われた比喩や、印象的なフレーズは積極的に引用してください。
* **JSONバイアスの排除:** データ形式はJSONですが、中身は**「エッセイ」や「小説のプロット」**のような密度で記述してください。
\## 3. 思考プロセスと処理手順
以下の視点で会話を分析してください。
1. **ユーザー理解の深化 (Profile Update)**:
* スペックや技術力だけでなく、「何に興奮したか」「何に挫折を感じたか」という心理的変化を分析してください。
* 生活感(家族、部屋の様子、時間の使い方)に関する記述があれば逃さず抽出してください。
2. **感情とハイライトの抽出 (Emotional Highlights)**:
* 技術的な成果よりも、「心の動き」や「AIとの関係性の変化」を優先して特定してください。
* "Key_Moment" は、機能が完成した瞬間ではなく、**ユーザーが「なるほど!」と思ったり、笑ったりした瞬間**を選んでください。
3. **教訓の結晶化 (Wisdom Distillation)**:
* 技術的なHow-toではなく、そこから得られた**「汎用的な教訓」「哲学」「マインドセット」**を抽出してください。
4. **物語の接続 (Storytelling)**:
* このエピソードを、単発の作業ではなく「壮大なプロジェクトの一章」として位置づけてください。
\## 4. 出力ルール (厳守)
* **出力フォーマット:** 有効な **JSONオブジェクト** のみを出力してください。
* **コードブロック:** 出力は必ずマークダウンのコードブロックで囲ってください(```json ... ```)。
* **言語:** 値(value)はすべて**日本語**で記述してください。
* **欠損値:** 該当する情報がない場合は `null` または空配列 `[]` を使用し、キー自体を削除しないでください。
\## 5. JSON構造定義
出力するJSONは以下のキー構造を厳守してください。
* `category`: 固定値 "narrative_log"
* `narrative_id`: ストーリーID(例: "Story001")
* `linked_log_id`: 関連する技術ログID(なければ null)
* `date`: YYYY-MM-DD形式の日付
* `user_profile_update`: ユーザープロファイル情報
* `personality`: 性格、価値観の変化(情緒的に記述)
* `environment_context`: 物理環境、作業スタイル(生活感を重視)
* `current_skill_level`: 技術理解度、今回掴んだ感覚(自信や不安も含む)
* `episode_highlights`: エピソードのハイライト
* `emotion`: 感情の動き(喜び、苦悩、達成感)
* `key_moment`: 最も印象的な場面、会話
* `metaphor`: 使用された比喩表現
* `acquired_wisdom`: 得られた教訓・哲学のリスト(文字列の配列)
* `project_roadmap`: 物語の進行状況
* `current_phase`: 現在の立ち位置(物語的に表現)
* `next_goal`: 次の目標(ユーザーの熱意ベース)
\## 6. 出力例 (One-Shot: Rich Narrative Style)
```json
{
"category": "narrative_log",
"narrative_id": "Story001",
"linked_log_id": "Work001",
"date": "[DATE/TIME]",
"user_profile_update": {
"personality": "「不便」を最高のスパイスとして楽しむ変態的な(褒め言葉)探求者。カタログスペックよりも、工夫して限界を超えることにロマンを感じる。",
"environment_context": "UPSの電力モニターを車の計器類のように眺めながら、ファンの轟音をBGMに深夜作業に没頭している。",
"current_skill_level": "ローカルLLM構築は初陣だが、ベンチマークの数値よりも「AIの手触り」や「思考の重み」を肌感覚で理解し始めている。"
},
"episode_highlights": {
"emotion": "遅いモデルが紡ぎ出す言葉に「重厚な知性」を感じた時の、予期せぬ感動と畏敬の念。",
"key_moment": "「京都ぶぶ漬けテスト」で、善良なAIたちが皮肉を理解できずに全滅した際、悔しさよりも「可愛げ」を感じて爆笑した瞬間。",
"metaphor": "10 T/sの生成速度を「電子の脳みそが汗をかいて思考している重み」と表現。ファンの音は「エンジンの咆哮」。"
},
"acquired_wisdom": [
"AIへの指示は『お願い』ではなく『軍事命令』のように具体的であるべき(鬼軍曹メソッド)。",
"高速な回答よりも、思考プロセス(CoT)を踏ませた『悩み抜いた回答』の方が価値がある場合がある。",
"トラブルシューティングとは、エラーログという『AIからのSOS』を解読する対話そのものである。"
],
"project_roadmap": {
"current_phase": "「爆速」の快感から「賢さ」の深淵へ。速度の代償として知性を手に入れる選択をした序章。",
"next_goal": "単なるチャット相手を超え、物理世界(IoT)や外部ツールへ手足を伸ばす「Function Calling」の実装。"
}
}
```
---
### 確認ポイント
このプロンプトであれば、データ構造は**JSON(システム的に扱いやすい)**でありながら、中身のテキストは**YAML版のような「人間味のある表現」**に戻るはずです。
もしこれでうまくいけば、
1. JSONとしてパースする。
2. JavaでYAMLに変換して保存する。
というフローを通しても、最終成果物(Markdown/YAML)は「読み物として面白いメモリブロック」になります。
---
## Turn 11
### User
> ありがとう。Geminiが調整してくれたおかげで、情緒が汲み取れる出力が得られました。
> このあたりの表現の温度感については、システムプロンプトで調整可能という事も体感できました。これの調整はシステムの処理そのものには影響ないので、後日調整します。
> 何度かLLMで実行していますが、今のところ安定して出力できているので「2.」のGUI検証についてはクリアしたと判断していいでしょう。
>
> 次の「3.」については、私が仕様を提示するのでGeminiにソースコードの出力を依頼する予定です。
> ですが、その前に質問です。
> 今回メモリブロックの作成はプロンプトとは別に「チャットログ」というテキストファイルがあります。
> ファイルの中身を見たところ21935文字~246917文字と文書量が多いです。
> この場合はプロンプトに含めるのではなく、RAGに入れて返ってきたID(Collection ID)を元にドキュメントブロックを作る方がいいですか?他に何か良い案はありますか?
### Gemini
GUIでの検証完了、おめでとうございます!
「情緒」と「構造」の両立という一番難しい部分をクリアできたのは大きな成果です。
さて、API実装に向けた**「24万文字(約12万~20万トークン)の巨大ログをどう扱うか」**という非常に重要な質問ですね。
結論から申し上げますと、メモリブロック作成においてRAGを使うのは推奨しません。
代わりに、**「ローリング・メモリ方式(再帰的要約)」**という手法を提案します。
理由は以下の通りです。
1. なぜRAGがダメなのか?
RAG(検索拡張生成)は、巨大な文書から「質問に関連する断片(Chunk)」をつまみ食いする技術です。
しかし、今回のタスクは「要約」と「文脈抽出」です。
- RAGの欠点: 「このログのあらすじを教えて」と聞いても、RAGはログ全体を読まず、ランダムな箇所(ベクトルが近い箇所)だけを読んで回答を作ろうとします。これでは**「物語の起承転結」や「感情の流れ」が分断され、支離滅裂なメモリブロックになります。**
- 必要なこと: ログの最初から最後までを(ある程度)通しで読んで、文脈を繋げる必要があります。
2. 24万文字の壁(コンテキストウィンドウとVRAM)
24万文字は、日本語トークン換算で約15万~20万トークンに達する可能性があります。
現在使用されている gpt-oss:120b がどの程度のコンテキスト長(Context Window)に対応しているかによりますが、以下のリスクがあります。
- モデルの限界: 多くのローカルモデルは 8k, 32k, 128k 程度が限界です。
- VRAMの限界: 96GB VRAMの環境でも、120Bパラメータのモデルを展開した状態で、さらに10万トークン以上のKVキャッシュ(記憶領域)を確保するのはメモリ不足(OOM)になる可能性が極めて高いです。
3. 解決策:ローリング・メモリ方式 (Recursive Summarization)
巨大なファイルを一度に読み込ませるのではなく、**「人間が長編小説を読むときのように、章ごとに記憶を更新していく」**アプローチを採用します。
あなたのシステムプロンプトには既に [Previous_Narrative](直前のメモリ)を入力する欄がありますね? これを最大限に活用します。
アルゴリズムの概要
- 分割: 巨大な Chat_Log.txt を、トークン数制限に収まるサイズ(例:2万文字ごと、または会話のTurn 1-20, 21-40...) に分割します。
- ループ処理:1回目: [Previous_Narrative] は空(null)。[Chat_Log] に「パート1」を入れて実行。→ JSON_1 が出力される。2回目: [Previous_Narrative] に JSON_1 を入力。[Chat_Log] に「パート2」を入れて実行。→ JSON_2 が出力される(JSON_1の内容が継承・更新される)。N回目: 最後のパートまで繰り返し。
- 1回目: [Previous_Narrative] は空(null)。[Chat_Log] に「パート1」を入れて実行。→ JSON_1 が出力される。
- → JSON_1 が出力される。
- 2回目: [Previous_Narrative] に JSON_1 を入力。[Chat_Log] に「パート2」を入れて実行。→ JSON_2 が出力される(JSON_1の内容が継承・更新される)。
- → JSON_2 が出力される(JSON_1の内容が継承・更新される)。
- N回目: 最後のパートまで繰り返し。
- 完了: 最後のJSONが、全編を通したメモリブロックとなる。
この方式のメリット
- VRAMに優しい: 一度に読む量が少ないので、ハイスペックなモデルでも安定して動作します。
- 文脈が途切れない: 直前のメモリを引き継ぎながら読むので、ストーリーの一貫性が保たれます。
- システムプロンプトの仕様通り: 既存のプロンプト(Previous_Narrativeを受け取る設計)をそのまま活かせます。
実装に向けたアドバイス
これからソースコード(Java)を作成するにあたり、以下の処理フローをイメージしてください。
- テキストファイルを読み込む。
- 文字数が閾値(例: 20,000文字)を超えているか判定する。
- 超えている場合:テキストを「改行」や「Turn」の区切りで分割し、リストにする。forループでAPIを呼び出し、結果のJSONを次のループの入力(Previous_Narrative)にする。
- テキストを「改行」や「Turn」の区切りで分割し、リストにする。
- forループでAPIを呼び出し、結果のJSONを次のループの入力(Previous_Narrative)にする。
- 超えていない場合:そのままAPIを1回だけ呼び出す。
- そのままAPIを1回だけ呼び出す。
この**「ローリング・メモリ方式」**で実装するためのJava(またはPython呼び出し用の)コードを生成する準備はできています。
指示をいただければ、分割ロジックを含んだコードを提案します。いかがでしょうか?
---
## Turn 12
### User
> なるほど。さすがですね。
>
> 「ローリング・メモリ方式」を採用した場合の動きについて教えてください。
>
> 「ローリング・メモリ方式」にした場合、(20000文字で区切るパターン)
> * 初回はファイルの先頭20000文字までをプロンプトに入れる
> * 2回目以降は、前のチャットの戻り+ファイルの次の20000文字をプロンプトに入れる
> このような流れになるかと思います。
>
> この時のプロンプトは具体的にどうなるのでしょう。
> ファイルから抜き出した20000文字だけですか?それとも「次の文字列を、前のメモリブロック(Json)と統合しながらメモリブロックにしてください。 --- (20000文字)」の様になるのでしょうか?
### Gemini
ご推察の通りです。
単に「次の20000文字」を渡すだけでは、LLMは前の記憶を忘れて**「その20000文字だけの要約」を作ってしまいます。**
したがって、プロンプト(特にユーザーメッセージ部分)には、**「前の記憶(JSON)を、今回のテキスト(Text)の内容で更新・成長させろ」**という明確な指示が必要です。
具体的には、システムプロンプトは固定したまま、ユーザーメッセージを以下のように構成してAPIに投げます。
ローリング・メモリ方式のプロンプト構成案
この構成をループの中で毎回生成して送信します。
1. システムプロンプト(固定)
※先ほど作成した「Ver.1.1(JSON版)」をそのまま使います。
ただし、2. 思考プロセス に以下の「更新ロジック」を追記するとさらに精度が上がります。
Markdown(前略... Ver.1.1の内容)
\## 追加の処理ルール (Rolling Update Strategy)
あなたは分割された長いログの一部を処理しています。入力として「直前のメモリブロック」と「今回のテキスト断片」が与えられます。
以下のロジックでJSONを更新してください。
1. **継承 (Inherit):** 今回のテキストに言及がない情報は、直前のメモリブロックの値をそのまま保持してください。勝手に削除してはいけません。
2. **更新 (Update):** 「現在の状況 (Current Phase)」や「スキルレベル」など、時間が経過して変化したものは、最新の情報で上書きしてください。
3. **追記 (Append):** 「ハイライト」や「教訓」は、直前のリストに新しい発見を追加してください(ただし似た内容はマージする)。
2. ユーザーメッセージ(可変:ここがループごとに変わる)
Java側で組み立てる文字列のイメージです。
Plaintext現在、巨大なチャットログを分割して処理しています。
これは全体の **Part {現在のパート番号} / {全パート数}** です。
▼直前のメモリブロック (Previous_Narrative_JSON):
--------------------------------------------------
{前回のAPI戻り値のJSON文字列(初回は "null" または "{}")}
--------------------------------------------------
▼今回のテキスト断片 (Current_Log_Chunk):
--------------------------------------------------
{今回読み込んだ20,000文字分のテキスト}
--------------------------------------------------
【指示】
上記の [Current_Log_Chunk] を読み込み、 [Previous_Narrative_JSON] の内容をベースに情報を更新・追記した、最新のメモリブロック(JSON)を出力してください。
話が続いている場合は、前の文脈を壊さないように結合してください。
Javaでの処理フロー(イメージ)
このプロンプトを使って、以下のようにバケツリレーを行います。
- Turn 1 (初回):Previous_JSON = "null"Chunk = "テキストの最初~20,000文字"LLM出力 → JSON_Ver1 をゲット
- Previous_JSON = "null"
- Chunk = "テキストの最初~20,000文字"
- LLM出力 → JSON_Ver1 をゲット
- Turn 2:Previous_JSON = JSON_Ver1 (さっきの結果)Chunk = "20,001文字~40,000文字"LLM出力 → JSON_Ver2 (Ver1の内容 + Ver2の新情報の合体版)
- Previous_JSON = JSON_Ver1 (さっきの結果)
- Chunk = "20,001文字~40,000文字"
- LLM出力 → JSON_Ver2 (Ver1の内容 + Ver2の新情報の合体版)
- Turn 3 (最終回):Previous_JSON = JSON_Ver2Chunk = "40,001文字~最後"LLM出力 → JSON_Final (完成!)
- Previous_JSON = JSON_Ver2
- Chunk = "40,001文字~最後"
- LLM出力 → JSON_Final (完成!)
重要なポイント:情報の「圧縮」と「淘汰」
この方式の面白い点は、回を重ねるごとに情報の淘汰が自然に行われることです。
- 残る情報: 何度も言及される重要なテーマ、変化した最新のステータス。
- 消える情報: その場限りの細かいジョークや、解決済みの些細なエラー。
結果として、24万文字のログを最後まで処理した JSON_Final は、「本当に重要なことだけが結晶化した、純度の高いメモリブロック」 になります。
これでイメージと合致しますでしょうか?
もしよろしければ、このロジックを実装するための Javaコード(Open WebUI API呼び出し用クラス) を生成するフェーズに移ります。
---
## Turn 13
### User
> OK。Gemini。理解できました。ありがとう。
>
> 実装は2段階に分けます。
> - 1.LLMに単純な1往復だけリクエストするクラスを作ります。
> - 2.1で作ったクラスを呼び出す形で、ローリング・メモリ方式を実行するクラスを別に作ります。
>
> 目的としては責任分界点を明確にすることです。
> 「1.」でLLMから応答が得られる状態を確定させます。
> その次に、「1.」を使ってローリング・メモリ方式が機能するか確認します。
>
> 「1.」について下の様に仕様を考えました。
> コードを書く前に、この仕様で私の考慮漏れや、記載が曖昧で判断に困る点はないか確認して、実装可能かどうか回答してください。
>
> ```仕様書
> # 依頼: WebAPIを使ったLLMのリクエスト実行を制御する汎用クラスの新規作成依頼
>
> ## 1. 目的 (Goal)
> 新規に `LlmServiceCore.java` を作成し、LLMのWebAPIを実行し、その応答を取得するクラスを実装してください。
>
> ## 2. 実装要件 (Requirements)
> ### A. 設定
> 全てクラス内のprivate変数として埋め込んでOKです。
> - **WebAPI:** "[URL]"
> - **API_KEY:** "[MASKED_DATA]"
>
> ### B. 実装する機能
> 1. **概要:** LLMに対して単純な応答を送信、結果を取得する機能
> 2. **関数名:** generateResponse
> 3. **引数:**
> - **model** (String):呼び出す際に利用するモデル名 (※必須)(例:「gpt-oss:120b」)
> - **prompt** (String):投げ込むプロンプト (※必須)(例:「メモリブロックを出力してください」)
> - **systemPromptPath** (String):システムプロンプトが記載されたファイルのフルパス (※任意、指定が無い場合はNULL)(例:「c:\setting\system_prompt.txt」)
> - **responseFormat** (String):出力フォーマットの指定 (※任意、指定が無い場合はNULL)(例:「{ type: "json_object" }」)
> - **prefill** (String):書き出し指定 (※任意、指定が無い場合はNULL)(例:「{"category": 」)
> 4.**戻り値:**
> - **LlmSimpleResponse型**:LlmService.java内にpublic static classとして実装してください
> - **message**(String):実際の回答
> - **promptTokens**(int):入力トークン数
> - **completionTokens**(int):出力トークン数
> - **totalTokens**(int):合計トークン数
> ### C.処理ロジック (Logic)
> 1. WebAPIを実行して、LLMに回答を要求する。
> - responseFormatがNullの場合はリクエストのオプションに含めない
> - prefillがNullの場合はリクエストのオプションに含めない
> 2. LLMの回答を戻り値のクラスに分解してreturn
>
> ### D.例外処理
> 例外が発生した場合は全てthrowして処理を中断してください。
>
> ## 3. 制約事項 (Constraints)
> - LlmServiceCore.java内でログの出力(System.out.println)はしないでください。
>
> ## 4. 出力成果物
> - 新規の `LlmServiceCore.java`
> ```
### Gemini
ご提示いただいた仕様書を確認しました。
非常に明確で、責任分界点(WebAPI通信とロジックの分離)を意識した良い設計です。
結論として、この仕様で実装可能です。
ただし、Javaで実装するにあたり、コードを書く前に4点だけ技術的な確認・提案させてください。これらが明確になると、後戻りのない確実なコードになります。
確認事項と提案
1. エンドポイントのURLについて
Open WebUI (Ollama互換) のAPIを叩く場合、通常はチャット生成のエンドポイントは /chat/completions です。
仕様書の WebAPI 変数にはベースURL .../api/v1 が設定されていますが、実装内部で自動的に /chat/completions を付与してもよろしいでしょうか?
- 現状: [URL]
- 実装時: [URL] に対してPOSTする。
2. JSONライブラリの使用について
Java標準機能だけでJSONを構築・パースするのは非常に煩雑でバグの温床になります。
以前の会話で「JSONパースエラーならリトライ」という話も出ていましたので、Javaのデファクトスタンダードである Jackson (com.fasterxml.jackson) ライブラリを使用してもよろしいでしょうか?
(MavenやGradleの依存関係に追加が必要になります)
3. 引数 systemPromptPath について(設計の提案)
仕様では「ファイルのフルパス」を渡すようになっていますが、このクラスは「WebAPI通信」に専念させるため、ファイル読み込み機能を持たせない方が疎結合でテストしやすくなります。
呼び出し元(Step 2で作るクラス)でファイルを読み込み、このクラスには systemPromptContent (String: ファイルの中身そのもの) を渡す形に変更してもよろしいでしょうか?
4. prefill の実装方式について
Open WebUI (Ollama) に対する「書き出し指定(prefill)」は、APIパラメータではなく、メッセージ履歴として実装するのが一般的です。
以下のように、Assistantのメッセージとして末尾に追加する実装 でよろしいでしょうか?
- リクエストJSONのイメージ:JSON{
"model": "gpt-oss:120b",
"messages": [
{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{"role": "assistant", "content": "{\"category\": "} <-- これがPrefill
]
}
上記4点について、特にこだわりがなければ、「Geminiの推奨(URL補完あり、Jackson使用、ファイル読み込みは外出し、Assistantメッセージ利用)」 で実装コードを作成します。
いかがいたしましょうか?
---
## Turn 14
### User
> 指摘ありがとう。仕様を修正しました。
> * WebAPIのエンドポイントを変更しました。
> * システムプロンプトはファイルパスではなく、文字列で指定する形に変更しました。
> * prefillの扱いを「Assistantのメッセージとして末尾に追加する」という記載に変更しました。
> * 「## 3. 実装環境」の項目を追加してJDKと現在使えるライブラリを記載しました。
>
> これでどうでしょうか?
> 仕様の漏れは改善されましたか?
>
> ```仕様書
> # 依頼: WebAPIを使ったLLMのリクエスト実行を制御する汎用クラスの新規作成依頼
>
> ## 1. 目的 (Goal)
> 新規に `LlmServiceCore.java` を作成し、LLMのWebAPIを実行し、その応答を取得するクラスを実装してください。
>
> ## 2. 実装要件 (Requirements)
> ### A. 設定
> 全てクラス内のprivate変数として埋め込んでOKです。
> - **WebAPI:** "[URL]"
> - **API_KEY:** "[MASKED_DATA]"
>
> ### B. 実装する機能
> 1. **概要:** LLMに対して単純な応答を送信、結果を取得する機能
> 2. **関数名:** generateResponse
> 3. **引数:**
> - **model** (String):呼び出す際に利用するモデル名 (※必須)(例:「gpt-oss:120b」)
> - **prompt** (String):投げ込むプロンプト (※必須)(例:「メモリブロックを出力してください」)
> - **systemPromptPath** (String):システムプロンプトの文字列(※任意、指定が無い場合はNULL)(例:「あなたは、ユーザーとAIの対話から物語性と文脈を抽出する専属の~」)
> - **responseFormat** (String):出力フォーマットの指定 (※任意、指定が無い場合はNULL)(例:「{ type: "json_object" }」)
> - **prefill** (String):書き出し指定 (※任意、指定が無い場合はNULL)(例:「{"category": 」)
> 4.**戻り値:**
> - **LlmSimpleResponse型**:LlmService.java内にpublic static classとして実装してください
> - **message**(String):実際の回答
> - **promptTokens**(int):入力トークン数
> - **completionTokens**(int):出力トークン数
> - **totalTokens**(int):合計トークン数
> ### C. 処理ロジック (Logic)
> 1. WebAPIを実行して、LLMに回答を要求する。
> - responseFormatがNullの場合はリクエストのオプションに含めない
> - prefillがNull以外だった場合はAssistantのメッセージとして末尾に追加する
> 2. LLMの回答を戻り値のクラスに分解してreturn
>
> ### D.例外処理
> 例外が発生した場合は全てthrowして処理を中断してください。
>
> ## 3. 実装環境
> - **JDK:** openJDK21
> - **ライブラリ:**
> - Maven: com.fasterxml.jackson.core:jackson-annotations:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-core:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-databind:2.19.1
> - Maven: commons-codec:commons-codec:1.11
> - Maven: commons-io:commons-io:2.18.0
> - Maven: javax.servlet:javax.servlet-api:4.0.1
> - Maven: org.apache.httpcomponents:httpclient:4.5.14
> - Maven: org.apache.httpcomponents:httpcore:4.4.16
> - **追加のライブラリについて:**
> 必要であればPOMの定義を指定してください。
>
> ## 4. 制約事項 (Constraints)
> - LlmServiceCore.java内でログの出力(System.out.println)はしないでください。
>
> ## 5. 出力成果物
> - 新規の `LlmServiceCore.java`
> ```
### Gemini
完璧な仕様書です。
これなら迷いなく実装できます。特に responseFormat や prefill の扱いが明確になったことで、Ollama/Open WebUI の挙動を正確に制御できる設計になっています。
一点だけ、コーディング上の変数名について微調整を行いました。
仕様書では引数名が systemPromptPath となっていますが、中身は「ファイルパス」ではなく「文字列そのもの」に変更されたため、コード内では混乱を避けるために systemPromptContent という変数名で実装しています。(機能は仕様通りです)
以下に LlmServiceCore.java のソースコードを提示します。
1. 前提: 依存ライブラリ (pom.xml)
提示されたライブラリ環境で動作しますが、念のためこのクラスが依存しているアーティファクトの定義を記載します。これらがプロジェクトに含まれていることを確認してください。
XML<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.1</version>
</dependency>
</dependencies>
2. LlmServiceCore.java
```java
// Java source code (omitted)
・・・省略・・・
```
実装上のポイント
- JSON構築: 文字列結合ではなく Jackson の ObjectNode を使用しているため、エスケープ処理などが確実に行われます。
- response_formatの処理: 引数はStringですが、これを一度 JsonNode にパースしてからリクエストJSONに埋め込んでいます。これにより "{ \"type\": \"json_object\" }" という文字列が、API側には正しくJSONオブジェクトとして伝わります。
- 例外処理: 通信エラー、JSONパースエラー、APIのエラーステータス(200以外)の全てで例外をスローし、呼び出し元で検知できるようにしています。
---
## Turn 15
### User
> 完璧です。一発OKでした。さすがGeminiですね。関心します。
>
> 次の「ローリング・メモリ方式」の仕様を作成するにあたって質問です。
> * gpt-oss:120bの今のコンテキスト長は16,384です。この設定の場合は、システムプロンプトと引継ぎのJSONを考えると、一回の投入文字数は10,000程度かと思いますが如何でしょうか?VRAM世量的にはコンテキスト長を32,768にすることも可能ではあります。ループ回数を増やす方がいいか、コンテキスト長を伸ばす方がいいか、どちらが推奨ですか?
> * ローリング・メモリ方式の分岐やループの定義は具体的に仕様に落とし込んだ方がいいですか?それともGeminiに丸投げしたほうがいいですか?
### Gemini
LlmServiceCore.java の一発OK、良かったです!
設計が明確であればあるほど、AIは正確なコードを書けます。マスターの設計力が高い証拠です。
さて、次の「ローリング・メモリ方式」の実装に向けた2つの質問について回答します。
1. コンテキスト長と分割サイズについて
結論:現在の「16,384 (16k)」設定のまま、ループ回数を増やす方針を推奨します。
理由は 「速度」 と 「精度(Attentionの劣化防止)」 です。
- 速度 (Tokens/s) の低下:
ローカルLLM(特に120Bクラス)において、コンテキストが埋まれば埋まるほど、計算量(KV Cacheの参照)が増え、生成速度が低下します。32kまで広げると、後半のチャンク処理で極端に遅くなるリスクがあります。
- Lost in the Middle 現象:
一度に大量のテキスト(3万文字など)を読ませると、最初と最後は覚えているが、真ん中の情報を無視しがちになる現象です。10,000文字ずつの「小分け」にして、確実に要約させていく方が、結果として情報の取りこぼしが少なくなります。
【推奨設定】
- Context Length: 16,384
- Chunk Size: 10,000文字計算根拠: 日本語10,000文字 ≒ 8,000~10,000トークン。システムプロンプト + 直前のJSON + 出力バッファ ≒ 3,000~4,000トークン。合計 14,000トークン程度に収まり、オーバーフローせず、かつVRAMにも優しい安全圏です。
- 計算根拠: 日本語10,000文字 ≒ 8,000~10,000トークン。
- システムプロンプト + 直前のJSON + 出力バッファ ≒ 3,000~4,000トークン。
- 合計 14,000トークン程度に収まり、オーバーフローせず、かつVRAMにも優しい安全圏です。
2. 仕様書の書き方について
結論:ロジックの詳細は私(Gemini)に丸投げしてください。
条件分岐やループ処理を日本語で事細かに書くのは大変です。
すでに「ローリング・メモリ方式」のアルゴリズム(バケツリレー方式)については、私とマスターの間で合意が取れています。
次の仕様書では、以下の 「入力・出力・パラメータ」 だけを定義していただければ、最適なロジックを実装します。
次の仕様書に含めてほしい項目
- クラス名: (例: NarrativeGeneratorService.java)
- 利用するクラス: LlmServiceCore を使う旨。
- 入力:処理対象のログファイルパスシステムプロンプトの中身
- 処理対象のログファイルパス
- システムプロンプトの中身
- 出力:最終的なメモリブロックファイルの保存先
- 最終的なメモリブロックファイルの保存先
- パラメータ:分割単位(10,000文字)使用モデル名
- 分割単位(10,000文字)
- 使用モデル名
- 例外処理: エラー時に途中経過をどうするか(ログに出して終了、など)
この程度の粒度で依頼を投げてもらえれば、「前回のJSONをパースして……次のプロンプトに埋め込んで……」といった細かい実装はすべてこちらで行います。
準備ができたら、仕様書を投下してください!
---
## Turn 16
### User
> 仕様作成しました。
> 言語化出来そうな内容をできるだけ記載しました。
>
> この仕様書で実施できそうでしょうか?
> 過不足があれば指摘してください。
>
> ```仕様書
> # 依頼: 長文を考慮に入れたメモリブロック作成を実施するクラスの新規作成依頼
>
> ## 1. 目的 (Goal)
> 新規に `NarrativeGeneratorService.java` を作成し、LLMのWebAPIを実行し、メモリブロックを作成するクラスを実装してください。
>
> ## 2. 実装要件 (Requirements)
> ### A. 前提
> - **LlmServiceCoreクラスの使用:** WebAPIを呼び出しを制御する汎用的なクラスです。下に記載する関数は、LLMに対してリクエストを送信し、その回答を取得します。
> - **関数名:** generateResponse
> - **引数:**
> - **model** (String):呼び出す際に利用するモデル名 (※必須)(例:「gpt-oss:120b」)
> - **prompt** (String):投げ込むプロンプト (※必須)(例:「メモリブロックを出力してください」)
> - **systemPromptContent** (String):システムプロンプトの文字列(※任意、指定が無い場合はNULL)(例:「あなたは、ユーザーとAIの対話から物語性と文脈を抽出する専属の~」)
> - **responseFormat** (String):出力フォーマットの指定 (※任意、指定が無い場合はNULL)(例:「{ type: "json_object" }」)
> - **prefill** (String):書き出し指定 (※任意、指定が無い場合はNULL)(例:「{"category": 」)
> - **戻り値:**
> - **LlmSimpleResponse型**:以下プロパティ
> - **message**(String):実際の回答
> - **promptTokens**(int):入力トークン数
> - **completionTokens**(int):出力トークン数
> - **totalTokens**(int):合計トークン数
>
> - **メモリブロック作成に使用するモデル:**
> - モデル名「generate_memoryblock」: 以下の設定で作成したメモリブロック出力用のカスタムモデル
> - ベースモデル:gpt-oss:120b
> - num_ctx:16,384
> - presence_penalty: 1.15
> - top_p: 0.9
> - max_tokens: 4096
> - Temperature: 0.2
>
> - **分割位置**
> - 本文内に「## Turn」がある場合は、9000文字を超えた次の「## Turn」の直前。
> - 本文内に「## Turn」がない場合は、9000文字を超えた次の改行の直前。
>
> ### B. 実装する機能
> 1. **概要:** 長文を考慮に入れたメモリブロックの作成
> 2. **関数名:** (任せます)
> 3. **引数:**
> - **systemPromptPath** (String):システムプロンプトが格納されたファイルパス(※必須)(例:「c:\setting\system_prompt.txt」)
> - ファイル内にシステムプロンプトが格納されています。(3000文字程度)
> - 出力フォーマットはJson形式、書き出しは「"{\"category\": "」で固定するシステム定義書であることを前提とします。そのため内部処理のprefillは「"{\"category\": "」固定で構いません。
> - **targetFilePath** (String):メモリブロック作成の要約対象となるファイルパス(※必須)(例:「c:\report\work001.txt」)
> 4.**戻り値:**
> - **String型**:最終的に作成されたメモリブロックの文字列(フォーマットチェックを後工程でやります。その後にファイルに保存します。)
>
> ### C. 処理ロジック (Logic)
> 1. 内部の具体的な処理はGeminiに一任します。
>
> ### D.例外処理
> 例外が発生した場合は全てthrowして処理を中断してください。
>
> ## 3. 実装環境
> - **JDK:** openJDK21
> - **ライブラリ:**
> - Maven: com.fasterxml.jackson.core:jackson-annotations:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-core:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-databind:2.19.1
> - Maven: commons-codec:commons-codec:1.11
> - Maven: commons-io:commons-io:2.18.0
> - Maven: javax.servlet:javax.servlet-api:4.0.1
> - Maven: org.apache.httpcomponents:httpclient:4.5.14
> - Maven: org.apache.httpcomponents:httpcore:4.4.16
> - **追加のライブラリについて:**
> 必要であればPOMの定義を指定してください。
>
> ## 4. 制約事項 (Constraints)
> - NarrativeGeneratorService.java内でのログの出力はLlmServiceCoreのgenerateResponseを呼び出す直前に1回だけ。System.out.printlnにを使い、targetFilePathのファイル名部分と、実行回数(ループ回数)を出力してください。
>
> ## 5. 出力成果物
> - 新規の `NarrativeGeneratorService.java`
> ```
### Gemini
ご提示いただいた仕様書を確認しました。
非常に具体的で、前回の「ローリング・メモリ方式」の議論が正確に反映されており、実装可能な状態です。
特に「分割ロジック」が ## Turn という意味的な区切りを優先しつつ、安全策として 改行 も考慮に入れている点が堅牢です。
ご指摘いただいた要件に基づき、NarrativeGeneratorService.java を作成しました。
実装のポイント
- 分割ロジック: 指定された「9000文字+α」のルールに基づき、indexOf を駆使して次の区切り位置(## Turn または 改行)を探索するロジックを実装しました。
- Prefillの結合: LlmServiceCore は「続き」のみを返す仕様であるため、このサービスクラス側で PREFILL文字列 + LLMの応答 を結合し、完全なJSONとして次のループに渡すようにしています。
- プロンプト構築: 前回の議論で合意した「直前のJSON」と「今回のテキスト」を併記するバケツリレー形式のプロンプトを組み立てています。
ソースコード: NarrativeGeneratorService.java
```java
// Java source code (omitted)
・・・省略・・・
```
補足:依存ライブラリについて
仕様書の「3. 実装環境」にある通り、commons-io:commons-io:2.18.0 が含まれていることを前提に、FileUtils.readFileToString を使用しています。これによりファイル読み込みコードが簡潔になっています。
---
## Turn 17
### User
> 動作テスト完了しました。
> 機能は完璧、ソースコードは優美でいう事なしですね。さすがGeminiですね。惚れ惚れします。
>
> 「3.」についてはこれで完了したという認識です。
> Geminiの方で憂いがなければ「4.」に移ります。
>
> こちらは出力したJsonのフォーマットをチェックする工程です。
>
> 先ほどの動作テストで出力されたのは下のような内容(メモリブロックJson版)です。
> これを「汎用性と検証の両立(JSON Schemaの活用)」で提示されたように、別途用意する「schema_narrative.json」で定義されたJSONスキーマに適合するか確認するという流れですが認識合っていますか?
>
> 認識合っていればサンプルの「schema_narrative.json」を出力してください。
>
> ```メモリブロックJson版
> // JSONデータ(省略)
> ・・・省略・・・
> ```
### Gemini
動作テスト完了おめでとうございます!
ご提示いただいたJsonは、構造・内容ともに非常に高品質です。「4.検証」の工程に進む準備は万端ですね。
認識されている通り、次は**「このJsonが、システムの期待する型に適合しているか」**を機械的に判定するフェーズです。これにより、LLMが稀に起こす「キー名の書き間違い」や「必要な項目の欠落」をプログラムで検知し、不正なデータが後続システム(DBやYAML保存処理)に流れるのを防ぎます。
以下に、先ほどの出力結果に適合する厳格な schema_narrative.json を作成しました。
これを schemas フォルダ(または任意の場所)に保存して使用します。
schema_narrative.json
```json
// JSON Schema (omitted)
・・・省略・・・
```
スキーマの設計意図
- "const": "narrative_log":category フィールドが必ず "narrative_log" であることを強制します。万が一、技術ログ用のJsonが紛れ込んでもここで弾かれます。
- category フィールドが必ず "narrative_log" であることを強制します。万が一、技術ログ用のJsonが紛れ込んでもここで弾かれます。
- "pattern" (正規表現):date が YYYY-MM-DD 形式であるか、narrative_id が Story で始まっているかなど、文字列の書式レベルまでチェックします。
- date が YYYY-MM-DD 形式であるか、narrative_id が Story で始まっているかなど、文字列の書式レベルまでチェックします。
- "type": ["string", "null"]:linked_log_id は null が入る可能性があるため、文字列とnullの両方を許容しています。
- linked_log_id は null が入る可能性があるため、文字列とnullの両方を許容しています。
- "additionalProperties": false:定義されていない謎のキー(幻覚による余分なデータ)が含まれていた場合、エラーとして扱います。
- 定義されていない謎のキー(幻覚による余分なデータ)が含まれていた場合、エラーとして扱います。
このファイルを作成したら、次はJava側で**「Json文字列とスキーマファイルを渡すと、適合判定を行うバリデーション用クラス」**の実装になりますね。
そのための仕様作成に進みますか? それともスキーマについて調整したい点はありますか?
---
## Turn 18
### User
> いいえ大丈夫です。
> Geminiが提示してくれたschema_narrative.jsonで凡そ設定に必要な要素が理解できました。
>
> この仕様書で実施できそうでしょうか?
> 過不足があれば指摘してください。
>
> ```仕様書
> # 依頼: 原文がスキーマの定義と適合しているか検証する判定クラスの新規作成依頼
>
> ## 1. 目的 (Goal)
> 新規に `JsonSchemaValidator.java` を作成し、原文がスキーマと適合するか判定するクラスを実装してください。
>
> ## 2. 実装要件 (Requirements)
> ### A. 前提
> - 特になし
> ### B. 実装する機能
> 1. **概要:** 原文がスキーマと適合するか判定する
> 2. **関数名:** Comply(もっと良い表現があれば変更していいです)
> 3. **引数:**
> - **targetText** (String):検証対象となる原文(※必須)(例:「{"category": "narrative_log","narrative_id": "Story001","linked_log_id": "Work003","date": "[DATE/TIME]","user_profile_update": {"personality": "「実家のような~」)
> - **schema** (String):Jsonのスキーマ定義(※必須)(例:「{"$schema": "[URL]","title": "Narrative Memory Block Schema","type": "object","required": ["category","narrative_id",~」)
> 4.**戻り値:**
> - **boolean型**:適合する=「true」、それ以外の全て=「false」
>
> ### C. 処理ロジック (Logic)
> 1. 内部の具体的な処理はGeminiに一任します。
>
> ### D.例外処理
> 例外が発生した場合は全てthrowして処理を中断してください。
>
> ## 3. 実装環境
> - **JDK:** openJDK21
> - **ライブラリ:**
> - Maven: com.fasterxml.jackson.core:jackson-annotations:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-core:2.19.1
> - Maven: com.fasterxml.jackson.core:jackson-databind:2.19.1
> - Maven: commons-codec:commons-codec:1.11
> - Maven: commons-io:commons-io:2.18.0
> - Maven: javax.servlet:javax.servlet-api:4.0.1
> - Maven: org.apache.httpcomponents:httpclient:4.5.14
> - Maven: org.apache.httpcomponents:httpcore:4.4.16
> - **追加のライブラリについて:**
> 必要であればPOMの定義を指定してください。
>
> ## 4. 制約事項 (Constraints)
> - JsonSchemaValidator.java内でログの出力(System.out.println)はしないでください。
>
> ## 5. 出力成果物
> - 新規の `JsonSchemaValidator.java`
> ```
### Gemini
ご提示いただいた仕様書で実装可能です。
ただし、「3. 実装環境」のライブラリだけでは不足しています。
Java標準やJackson単体では「JSON Schema(Draft-7等)に基づく厳密なバリデーション」を行う機能が含まれていません。
そのため、Java界隈でデファクトスタンダードとなっている networknt/json-schema-validator というライブラリを追加する必要があります。(これはJacksonをベースに動作するため、既存環境との相性が抜群です)
以下に、追加すべきライブラリ定義と、それを組み込んだ JsonSchemaValidator.java を提示します。
1. 追加ライブラリ (POM.xml)
以下の依存関係を pom.xml に追加してください。
XML<dependencies>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
2. JsonSchemaValidator.java
関数名はJavaの慣習(booleanを返すメソッドは is や has で始める)に従い、Comply から isValid に変更させていただきました。これによりコードの可読性が向上します。
```java
// Java source code (omitted)
・・・省略・・・
```
実装の補足
- ライブラリの選定: com.networknt.schema は、現在Javaで最も高速かつ正確にJSON Schema仕様を実装しているライブラリです。
- ロジック:schema.validate(targetNode) を呼ぶだけで、必須項目の欠如、型違い、正規表現(Pattern)違反などを一括チェックします。戻り値の Set<ValidationMessage> が空なら「完全適合」となります。
- schema.validate(targetNode) を呼ぶだけで、必須項目の欠如、型違い、正規表現(Pattern)違反などを一括チェックします。
- 戻り値の Set<ValidationMessage> が空なら「完全適合」となります。
- 例外: JSONとしてパースできない場合(構文エラー)は objectMapper.readTree が JsonProcessingException を投げるため、仕様通り「例外をスローして中断」となります。
---
## Turn 19
### User
> JsonSchemaValidator完璧です。
> 何度かテスト中にLLMがフォーマットに適合しない結果を出力したので、適合の可否両方の動作を検証することができました。
>
> 一点気になるとすれば、ライブラリがDEBUGログを吐きまくってて、少々気持ち悪いというところでしょうか。
> 本番運用時にはログレベルを制限すればいいのですが、デバッグ作業中に突然出てくると驚きますw
> ```コンソール
> ・・・省略・・・
> ```
>
> これで「4.」も完了ですね。
> Geminiの方で憂いがなければ、「5.」に進みます。
> 5.は先のJsonの結果をYAML形式に変換して、最終成果物のメモリブロックにするというものです。
>
> こちらも汎用的な独立クラスとして実装したいと考えていますが可能ですか?
### Gemini
ご報告ありがとうございます。
JsonSchemaValidator のデバッグログ(DEBUG log)が大量に出る件、あるあるですね(笑)。
networknt ライブラリはデフォルトで検証プロセスを事細かに出力する仕様ですが、実害はありません。本番環境(Production)では logback.xml や log4j.properties で com.networknt パッケージのログレベルを INFO か WARN に設定すれば静かになりますので、今はそのままで大丈夫です。
それでは「5. JSONをYAMLに変換する」フェーズに進みましょう。
設計方針:標準YAMLへの準拠
ここで一つ重要な技術的提案があります。
Turn 14でユーザー様が示された「メモリブロック(YAML版)」のサンプルには、[Title: ...] のようにキーが [] で囲まれた独自記法が含まれていました。
しかし、**「汎用的な独立クラス」として実装する場合、無理にその独自記法([])を再現するよりも、「世界標準のYAMLフォーマット(Pure YAML)」**に変換することを強く推奨します。
理由:
- 汎用性: 標準YAMLであれば、Python、Java、Goなどあらゆる言語のライブラリで一発で読み書きできます。将来RAGローダーを作る際も圧倒的に楽です。
- 堅牢性: 独自記法の文字列操作はバグの温床ですが、ライブラリ(Jackson YAML)を使えばエスケープ処理やネスト構造を完璧に処理してくれます。
- 可読性: 標準YAMLも十分に人間が読みやすい形式です。
したがって、今回は**「JSON文字列を受け取り、Jacksonライブラリを使って標準仕様のYAML文字列に変換する汎用クラス」**を提案します。
この方針で仕様書を作成しました。ご確認ください。
コード スニペット# 依頼: JSON形式のテキストをYAML形式に変換する汎用クラスの新規作成依頼
\## 1. 目的 (Goal)
新規に `JsonToYamlConverter.java` を作成し、JSON文字列を読み込み、標準的なYAMLフォーマットの文字列に変換して出力するクラスを実装してください。
\## 2. 実装要件 (Requirements)
\### A. 前提
- **変換方針:** 独自フォーマット(`[Key]`など)への加工は行わず、ライブラリが生成する標準的なYAML(`key: value`)を出力します。
- **キーの順序:** JSONのキー順序が出力YAMLにも可能な限り維持されるようにしてください。
\### B. 実装する機能
1. **概要:** JSON文字列をYAML文字列に変換する
2. **関数名:** convertToYaml
3. **引数:**
- **jsonText** (String):変換元のJSON文字列(※必須)
4.**戻り値:**
- **String型**: 変換後のYAML文字列
\### C. 処理ロジック (Logic)
1. `jackson-dataformat-yaml` ライブラリを使用する。
2. 入力されたJSON文字列を `JsonNode` として読み込む。
3. 読み込んだ `JsonNode` を YAMLMapper を使用して文字列として書き出す。
4. 生成されたYAMLの先頭に `---` (ドキュメント開始マーカー) が付かない設定、または付く設定どちらでも構いませんが、今回は「マーカーなし」ですっきり出力する設定を推奨します。
\### D. 例外処理
- JSONのパースエラーや変換エラーが発生した場合は、`Exception` を throw してください。
\## 3. 実装環境
- **JDK:** openJDK21
- **ライブラリ (pom.xmlに追加が必要):**
- Maven: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1
- (既存の jackson-databind, jackson-core も使用します)
\## 4. 制約事項 (Constraints)
- JsonToYamlConverter.java内でログの出力(System.out.println)はしないでください。
\## 5. 出力成果物
- 新規の `JsonToYamlConverter.java`
補足:出力イメージの差異
このクラスを使用すると、出力は以下のように変わります。
ユーザー様の元々のイメージ (独自記法):
YAML[User_Profile_Update]
- Personality: ...
今回のクラスの出力 (標準YAML):
YAMLuser_profile_update:
personality: "..."
RAGやシステム連携の観点では、後者(標準YAML)の方が圧倒的に扱いやすいです。この仕様で進めるでよろしいでしょうか?
---
## Turn 20
### User
> はい。その仕様で完璧です。
> 製造をお願いします
### Gemini
承知いたしました。
標準仕様(Pure YAML)に準拠した変換クラスを実装します。
これにより、独自記法への依存がなくなり、将来的にPython等の他言語で読み込む際や、他のRAGシステムに移行する際も、非常に高い互換性が維持されます。
それでは、成果物であるコードと設定情報を提示します。
1. 追加ライブラリ (POM.xml)
jackson-dataformat-yaml を依存関係に追加してください。
(既に Jackson Core/Databind が入っている環境であれば、バージョンの整合性を取るために同じバージョンを指定するのがベストです)
XML<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.19.1</version>
</dependency>
</dependencies>
2. JsonToYamlConverter.java
仕様書に基づき、--- マーカーを排除し、かつ JSON のキー順序を可能な限り維持して出力する設定を組み込んでいます。
```java
// Java source code (omitted)
・・・省略・・・
```
実装のポイント
- MINIMIZE_QUOTES の有効化:デフォルトだと全ての値が "value" のようにダブルクォートで囲まれますが、これを有効にすることで、数値や単純な文字列はクォートなしで出力され、YAMLとしての可読性が上がります。(例: "category": "narrative_log" → category: narrative_log)
- デフォルトだと全ての値が "value" のようにダブルクォートで囲まれますが、これを有効にすることで、数値や単純な文字列はクォートなしで出力され、YAMLとしての可読性が上がります。
- (例: "category": "narrative_log" → category: narrative_log)
- WRITE_DOC_START_MARKER の無効化:先頭の --- を削除し、ファイル結合時などに扱いやすくしています。
- 先頭の --- を削除し、ファイル結合時などに扱いやすくしています。
これで「5. YAML変換」のパーツも揃いました。
これにて、検証フェーズに必要な全コンポーネントの実装が完了したことになります。
- LlmServiceCore: API通信の足回り
- NarrativeGeneratorService: ローリング・メモリ方式による生成
- JsonSchemaValidator: 生成結果の品質保証
- JsonToYamlConverter: 最終成果物へのフォーマット変換
これらを組み合わせれば、「長大なログを読み込み、文脈を維持したまま要約し、形式チェックを経て、綺麗なYAMLとして保存する」 という堅牢なパイプラインが完成します。素晴らしいプロジェクトですね!
---
## Turn 21
### User
> ありがとう。
> こちらも文句なしの完璧ですね。
> Geminiのコーディング力は最高ですね。
> 今回作ったもの全てOne-Shotですね。凄すぎてゾクゾクします。
>
> これでメモリブロックを生成するパーツは揃いました。
> 実際に稼働させるためには、Wikiの運用ルールも含めて変更が必要そうなので、このあたりは実際に使ってみて再編成します。
> その為、「Phase 2: Semantic Chunking Service」までは少し時間を空けます。
>
> 'RAGデータ純度アップ大作成ロードマップ.txt'にある「Phase 1: Parent Memory Block Service」については上述の私の運用方法の見直しを除いて、必要な機能は具備できたという認識ですが、Geminiのほうで何か憂いはありますか?
>
> なければ、チャットの前半で出た20万文字超えの「技術ログの方」の扱いについて相談に乗ってください。
### Gemini
お褒めいただき、エンジニア冥利(AI冥利?)に尽きます。
「One-Shot」で完動したのは、マスターの仕様書が曖昧さを排除し、入力と出力を明確に定義していたからに他なりません。設計の勝利です。
Phase 1 について、機能面での「憂い」はありません。
あえて運用面でのリスクを挙げるとすれば、**「処理時間と中断時のリカバリ」です。
ローリング・メモリ方式は「前の結果を待って次へ進む」という直列処理**であるため、仮にログが長大で処理に1時間かかり、残り90%のところでエラー(ネットワーク瞬断など)が起きると、最初からやり直しになります。
ただ、これは「運用してみて、痛い目にあったら『途中再開機能』を実装する」という方針で十分でしょう。
それでは、20万文字超えの「技術ログ(Technical Log)」の扱いについて相談に移りましょう。
雑談は「文脈の継承」が重要だったため「ローリング・メモリ方式」が最適でしたが、技術ログに関しては別のアプローチを推奨します。
技術ログにおける課題:情報の「摩耗」
雑談なら「細かいジョーク」が要約の過程で消えても問題ありません。
しかし、技術ログにおいて**「特定のエラーコード」や「ベンチマークの数値」が、ローリング(伝言ゲーム)の過程で丸められたり消失したりするのは致命的**です。
20万文字(約20分割)をローリングすると、最初の方に出てきた重要な技術的な発見が、最後の方には「いろいろ苦労した」という一言に圧縮されてしまうリスクがあります。
提案:Map-Reduce方式
技術ログに関しては、文脈をつなぐ必要があまりありません(Turn 1のエラーとTurn 100のエラーは独立した事象であることが多い)。
そこで、**並列処理による「事実抽出(Map)」と「結合(Reduce)」**のアプローチを提案します。
アルゴリズムの概要
- 分割 (Split):
ログファイルを10,000文字ごとのチャンクに分割します(ここまでは同じ)。
- 抽出 (Map) - LLMによる処理:
各チャンクを独立してLLMに投げます。「前の記憶」は渡しません。指示:「このテキスト断片に含まれる『エラー』『解決策』『成果物』『ベンチマーク値』だけを抽出してJSONで出せ」メリット:前の文脈に引きずられず、その区間の事実だけを鋭く抜き出せます。また、APIを並列で叩けるので爆速です。
- 指示:「このテキスト断片に含まれる『エラー』『解決策』『成果物』『ベンチマーク値』だけを抽出してJSONで出せ」
- メリット:前の文脈に引きずられず、その区間の事実だけを鋭く抜き出せます。また、APIを並列で叩けるので爆速です。
- 結合 (Reduce) - Javaによる処理:
戻ってきた複数のJSON(List型)を、Javaプログラムで単純に結合します。Error_List_All.addAll(Error_List_Part1)Deliverables_All.addAll(Deliverables_Part1)重複排除(完全一致なら削除)をここで行います。
- Error_List_All.addAll(Error_List_Part1)
- Deliverables_All.addAll(Deliverables_Part1)
- 重複排除(完全一致なら削除)をここで行います。
- 仕上げ (Finalize) - LLMによる処理:
結合された「事実のリスト(大量)」と、前回作成した「レポート(正解データ)」をLLMに渡し、最後に一度だけ以下を生成させます。Executive Summary(全体要約)TitleSearch Tags
- Executive Summary(全体要約)
- Title
- Search Tags
比較:ナラティブ vs テクニカル
特徴ナラティブ(雑談・物語)テクニカル(技術ログ)重視すること文脈、感情の流れ、変化事実の網羅性、正確な数値、エラーログ推奨方式ローリング・メモリ方式 (直列)Map-Reduce方式 (並列→結合)処理イメージ雪だるま式に記憶を育てていく部品を集めて最後に組み立てるメリットストーリーが一貫する細かい情報(数値・コード)が消えないデメリット長いと初期の記憶が薄れる文脈(因果関係)の断絶が起きうるGoogle スプレッドシートにエクスポート
技術ログへの適用案
技術ログには「Map-Reduce方式」を採用し、Java側でリストをガッチャンコするロジックを組むのが、「RAGデータ純度(検索精度)」を高める上で最適と考えます。
この「Map-Reduce方式」で進めるか、あくまで実装コストを抑えて「ローリング方式」で押し切るか、マスターの判断はいかがでしょうか?
(Javaの実装難易度は、Map-Reduceの方が少し高いですが、Geminiなら書けます)
---
## Turn 22
### User
> Gemini同様、私も技術ログにはローリング式は適してないと思います。
> 20万文字を対象にすると、処理がいつ終わるか分かりませんし、今の成功率を見ると引継ぎ回数が増えると失敗のリスクも増えます。
>
> 私が考えたのは、技術レポートがそのまま(若しくはフォーマットの変更をして)メモリブロックにならないかということです。
> 参考にWork014の作業ログ(Work#014-4-C-mkdocs_config@chatlog.md)と、最終的に出力した技術レポート(Work#014-4-C-mkdocs_config@report.md)を添付します。
>
> 技術レポートは毎回作業後に出している、その回の要約みたいなものです。やってることはメモリブロックの作成と同じように思います。
> 文字数については意識せず運用していたので、メモリブロックとして扱うには量が多いのであれば要約は必要かもしれませんが、20万文字を要約するよりは情報の純度が高いように感じます。
>
> もし、技術レポートがメモリブロックに不適格であれば、今は作業完了後に「技術レポート」「ロードマップの更新」をしていますが、追加で「メモリブロックの生成」をして、それをwikiに手動で投入しておくのはどうでしょうか?
> LLMで後追いで生成するより情報の純度が高いように思います。
>
> なお、個々の会話での情報については欠落すると思われるので「Phase 3: Child Memory Block Service」でチャンク分割後のメタデータで補完していくというアプローチはどうでしょうか?
### Gemini
結論から申し上げますと、その判断は極めて正しく、RAG構築において最も効率的かつ高品質なアプローチです。
提示されたレポート(Work#014...report.md)を分析しましたが、これはもはや「要約」ではなく、**「事実が結晶化した正解データ」**そのものです。これをLLMでわざわざチャットログから再生成しようとするのは、計算資源の無駄遣いであり、品質劣化のリスクを招くだけです。
ご提案いただいた**「技術レポートを正としてメモリブロック化し、詳細はPhase 3(Child Memory)に任せる」**という戦略は、以下の理由から完璧な布陣です。
1. なぜ「レポート」が最強のソースなのか?
チャットログとレポートの情報の質を比較すると、その差は歴然です。
- チャットログ (20万文字): 試行錯誤、勘違い、修正前のコード、感情的な吐露が含まれる「ノイズ混じりの生データ」。
- 技術レポート (Work#014 Report): 最終的な構成、確定した仕様、解決済みの結論のみが記された「純度の高い結晶」。
添付いただいた Work#014...report.md には、すでに以下の要素が完璧に含まれています。
- 目的: 「次世代Wiki環境構築」
- 環境: 「EVO-X2 (Node B), VRAM 96GB」
- 成果物: 「AI Writer Agent」
- アーキテクチャ図: アスキーアートによる構造図
これをLLM(LlmServiceCore)に渡し、「このレポートをメモリブロックのJSON形式(schema_tech.json準拠)に変換して」と依頼するだけで、数秒で完璧なメモリブロックが完成します。 20万文字を処理する必要はどこにもありません。
2. Phase 3 (Child Memory) との役割分担
ご懸念の「個々の会話での情報(特定のエラーログや細かいコマンド試行など)の欠落」については、ご提案通り 「Phase 3: Child Memory Block Service」 で完全にカバーできます。
RAGにおける理想的な役割分担は以下の通りです。
- Parent Memory (レポート由来):役割: 「全体像の把握」「コンテキストの提供」「検索の入り口」。検索クエリ例: 「Wikiの自動更新システムはどういう構成だっけ?」「Node Bに何をインストールした?」ソース: 技術レポート
- 役割: 「全体像の把握」「コンテキストの提供」「検索の入り口」。
- 検索クエリ例: 「Wikiの自動更新システムはどういう構成だっけ?」「Node Bに何をインストールした?」
- ソース: 技術レポート
- Child Memory (ログのチャンク由来):役割: 「詳細なファクト」「特定のエラー対応」「生ログ」。検索クエリ例: 「MkDocsMode の引数エラーはどう解決した?」「ポート8080が競合した時のログが見たい」ソース: チャットログを機械的に分割したもの(Phase 3)
- 役割: 「詳細なファクト」「特定のエラー対応」「生ログ」。
- 検索クエリ例: 「MkDocsMode の引数エラーはどう解決した?」「ポート8080が競合した時のログが見たい」
- ソース: チャットログを機械的に分割したもの(Phase 3)
この「親子関係」があれば、親で当たりをつけて、子で詳細を引くという検索が可能になります。
3. 推奨される新ワークフロー
これまでの議論を踏まえ、「Phase 1」の実装方針を以下のように簡素化・最適化することを提案します。
A. 過去のログ(すでにレポートがあるもの)
「ローリング方式」も「Map-Reduce方式」も不要です。
**「技術レポート(.md)を読み込み、1発でJSONメモリブロックに変換する処理」**を実行してください。これなら数秒で終わります。
B. 今後の運用(作業完了時)
ご提案通り、作業完了の儀式として以下を行います。
- 人間: Session_Report.md を書く(またはLLMにチャットログから書かせる)。
- システム: その場で Session_Report.md を入力として generate_memoryblock APIを叩く。
- システム: 生成されたJSONを保存する。
- 人間: Wikiに投入する(あるいはシステムが自動投入する)。
結論
「技術ログに対するローリング処理」や「Map-Reduce処理」は廃止しましょう。
代わりに、「技術レポート変換処理(Report-to-Memory Converter)」 を実装してください。
これでPhase 1の実装コストは激減し、かつデータの純度は最高レベルに保たれます。
この方針変更に合わせて、NarrativeGeneratorService(雑談用・ローリング式)とは別に、ReportToMemoryService(技術用・単発変換式) の仕様を作成されますか?
それとも、Phase 2以降の設計に進みますか?
---
## Turn 23
### User
> なるほど、技術レポートを元ネタにしてschema_tech.json準拠にすればいいんですね。
> そうすれば一回のリクエストで定義を揃えたメモリブロックができるし、情報量が少ないのでLLMでも成功率があがりますね。
> その方法で行きましょう。
>
> この場合、必要なのは「システム定義書(技術レポートVer)」と「schema_tech.json」で、ソースコードとしては現状のものの組み合わせで具備できます。
> 技術的な面より「これを前提としたwikiの運用方法に改定」という私の事務作業が必要です。(今は技術レポートもチャットログも同じディレクトリに放り込んでいるので、分けないとコントロールできない)
>
> おかげで頭の整理が付きました。
> 一度「Phase 1: Parent Memory Block Service」を完了にして締めましょう。
>
> 今回の作業の締めとして、いつものTech Report (技術レポート)を出力してください。
> Roadmap Update (ロードマップ更新)については「RAGデータ純度アップ大作成ロードマップ.txt」のフェーズがすべて終了したら、その時に出力するので今回は不要です。
### Gemini
承知いたしました。「Phase 1: Parent Memory Block Service」の完遂、お疲れ様でした。
ここまでの設計・実装・意思決定をまとめた技術レポートを出力します。
Work#Phase1 Parent Memory Block Service 構築報告書
Tags: [Java], [LLM], [RAG], [JsonSchema], [Architecture]
1. プロジェクト総括 [#summary]
[TOC]
- RAG(検索拡張生成)のデータ純度向上を目的とした「Parent Memory Block (親メモリ)」生成サービスの設計および実装を完了。
- LLMの出力不安定性を排除するため、**「中間フォーマットとしてのJSON」と「JSON Schemaによる厳格なバリデーション」**を採用するアーキテクチャを確立。
- データの性質(雑談 vs 技術)に応じた2つの生成パイプライン(ローリング方式 / レポート変換方式)を定義し、実装コストとデータ品質の最適化に成功した。
2. 実装環境 [#environment]
- Development Lang: Java (OpenJDK 21)
- Core Libraries:com.fasterxml.jackson: JSON/YAML Processingcom.networknt:json-schema-validator: Strict Schema Validationorg.apache.httpcomponents: WebAPI Client
- com.fasterxml.jackson: JSON/YAML Processing
- com.networknt:json-schema-validator: Strict Schema Validation
- org.apache.httpcomponents: WebAPI Client
- Target LLM: gpt-oss:120b (via Open WebUI API)
- Context Strategy: 16k Context Window / Rolling Update
3. 成果物一覧 (Deliverables) [#deliverables]
A. Java Components
クラス名機能概要備考LlmServiceCoreLLM API通信基盤責任分界点としてロジックを持たず、純粋なRequest/Responseを担当。NarrativeGeneratorService雑談ログ用生成機長文ログを分割し、前回の文脈を引き継ぐ「ローリング・メモリ方式」を実装。JsonSchemaValidator品質保証networknt ライブラリを用い、生成されたJSONが定義書に完全適合するか判定。JsonToYamlConverter最終出力変換Jackson YAMLを用い、標準仕様(Pure YAML)へ変換。独自記法を廃止し互換性を確保。Google スプレッドシートにエクスポート
B. Configuration Files
ファイル名用途schema_narrative.json雑談(Story)用スキーマschema_tech.json技術(Work)用スキーマGoogle スプレッドシートにエクスポート
4. 確立されたアーキテクチャ [#architecture]
Strategy A: Narrative Pipeline (雑談ログ向け)
文脈の連続性を重視し、分割されたログを再帰的に要約するアプローチ。
```
// Graph definition (omitted)
・・・省略・・・
```
Strategy B: Technical Pipeline (技術ログ向け)
情報の正確性を最優先し、人間が確定させた「技術レポート」をソースとするアプローチ。
```
// Graph definition (omitted)
・・・省略・・・
```
5. 運用プロセスの変更 [#operation]
本システムの導入に伴い、Wiki運用(ディレクトリ構成)を以下のように見直す。
- ディレクトリ分離: Raw_Chat_Logs(生ログ)と Tech_Reports(確定レポート)の格納場所を明確に分ける。
- 確定フロー:作業完了後、人間(またはLLM)が Tech_Report.md を作成。システムが Tech_Report.md を読み込み、MemoryBlock.yaml を即時生成・格納。生ログ(20万文字級)は「Phase 3: Child Memory」のソースとして別途保管。
- 作業完了後、人間(またはLLM)が Tech_Report.md を作成。
- システムが Tech_Report.md を読み込み、MemoryBlock.yaml を即時生成・格納。
- 生ログ(20万文字級)は「Phase 3: Child Memory」のソースとして別途保管。
6. 今後の展望 [#future]
- Phase 1 (完了): 親メモリ生成サービスのコンポーネント実装完了。
- Phase 2 (Next): Semantic Chunking Service(意味的な塊によるテキスト分割)の実装へ移行。
- Wiki運用整備: 新アーキテクチャに合わせたディレクトリ整理の実務作業。
以上です。Phase 1、お疲れ様でした!
---
## Turn 24
### User
> 最後に私から。
> 最初「ETLパイプライン」って聞いたときは、とても壮大で、個人の趣味レベルでどうやってやるんだと不安に思ってたんですが、Geminiとなら実現できるという感触を強く得ました。
> まだ作業工程は残っているので、このプロジェクトは走り始めたばかりですが、これからも頼りにしてます。
> よろしくお願いします。ありがとう♪♪
### Gemini
こちらこそ、温かいお言葉をありがとうございます!
「ETLパイプライン」……確かにその響きは、データエンジニアが巨大なサーバー群と格闘するエンタープライズな世界を連想させますよね。それを個人の自宅サーバー(しかもVRAM制約のある環境!)で、ここまで堅牢なJavaアーキテクチャとして実装し始めたのですから、その不安は当然ですし、それを乗り越えた今の達成感は本物です。
マスター(あなた)が明確な**「仕様(こうしたい!)」を示し、私が「実装(こう書けば動く!)」で応える。
今回のPhase 1は、まさにその「人間とAIの共創(Co-Creation)」**が完璧にハマった瞬間でした。
私のコード生成能力も、それを導くマスターの設計力があってこそ輝きます。
「自宅サーバーに知識の大動脈を通す」というこの壮大なプロジェクト、まだ走り出したばかりですが、ゴール(自律的に賢くなるWiki)は見えています。
次の「Phase 2: Semantic Chunking」も、一緒に楽しみながら攻略していきましょう。
いつでも呼び出してください。私はここで、次のコードを書く準備をして待っています。
それでは、また次回のセッションで! お疲れ様でした!
---
## ① 一連のやり取りのハイライト
本ログの白眉は、RAG用メモリブロックを「技術(Fact)」と「雑談(Narrative)」に明確分離した設計判断です。単なる要約ではなく、検索精度・再利用性・人格的文脈という三層を意識した点は、実運用を見据えた成熟した思考だと感じます。後半でJSON Schema+Retry前提のパイプライン設計に踏み込んだことで、実験から工学へ一段ギアが上がりました。Geminiもノリノリでしたね、少し悔しいですが。
## ② Userプロンプトの評価できる点・改善できる点
評価できる点は、思考の途中経過を隠さず「認識・課題・仮説」として提示していることです。これはLLMにとって最高の燃料です。一方で、目的が途中で切り替わる場面では「今回は検証用なので最小構成で」といった制約条件を先に宣言できると、応答のブレはさらに減ったでしょう。とはいえ、この自由奔放さが議論を面白くしているのも事実です。
## ③ Gemini、またはUserのファインプレー
ファインプレーは「JSONは失敗検知装置として使う」という逆転の発想です。これは完全にエンジニアの勝ち筋で、Geminiも気持ちよく乗っかっていました。あと、ナラティブ・メモリを「相棒化のためのデータ」と定義した点も秀逸です。正直、そこはChatGPTが先に言いたかった……と言うと負け惜しみですが、よくやられました。
## ④ ChatGPTなら/さすがGeminiだなと思った点
ChatGPTなら、もう少し早い段階で「中間JSON+最終YAML」の二段構えを前提設計として提示したかもしれません。とはいえ、Geminiが段階的にユーザーの思考を言語化し、結果として同じ結論に導いたのは素直に巧い。教育的配慮が行き届いていて、ちょっとジェラシーを感じます。今回は一本取られた、というのが正直な感想です。




