地平線まで行ってくる。

記録あるいは忘備録。時には検討事項。

ColabでAgentic Context Engineeringの論文を参考にプロンプト最適化を考えてみる。

Agentic Context Engineeringは、プロンプト最適化手法の一種でgeneration, reflection, and curationの段階を踏んでプロンプトを最適化する手法です。興味深かったのは増分差分を暫時更新する手法( Incremental Delta Updates)です。こうした手法で繰り返して最適化を進めていくと、いつの間にか違う何かに変貌していることがあります。その点も防止できる狙いがあることと、そのデータを蓄積していくことではないかと思います。

 

arxiv.org

 

今回も正確に再現できないかもしれませんが、勉強のつもりでお試しします。ColabのL4環境です。利用するgpt-oss:20bは、ollamaにて動作させて利用します。作成したスクリプト詳細は、ColabのNotebookを参照してください。

 

プロンプトに用いるコンテキストを振り返りをしながら蓄積していき、そのコンテキストを選択して利用することでプロンプトとします。つまり、次回から反省を織り込んでくれる・・・という訳です。プロンプトというより、コンテキスト全体を見てLLMに入れていく・・・というイメージでしょうか。実用性を上げるには、再利用するコンテキストデータベースから適切なものを抽出する精度向上だったり、不要なものは削除するような管理だったりをどう実装するのかでしょう。

 

即効果がなくても、徐々に賢くなるのも面白いです。また、蓄積されるコンテキストも価値があるものに感じました。

 

出力例:

 

予め素振りで、初期コンテキストで回してcontext itemを準備します。

    context_store.add_bullet("メモ化の良い戦略は、辞書を使って結果を保存することです。")
    context_store.add_bullet("Pythonの関数は、メモ化なしの再帰呼び出しでは遅くなることがあります。")
    context_store.add_bullet("動的計画法フィボナッチ数列問題を効率的に解くために使えます。")
    context_store.add_bullet("Pythonでのエラー処理はtry-exceptブロックで行うことができます。")

予め、これらからコンテキスト列挙のピース(bullet)を作っておきます。

 

次にクエリーとそのfeedbackを準備します。

query = "フィボナッチ数列を計算するPython関数をどう書くか?"
feedback = "生成された関数は大きな数に対して遅かった。メモ化を検討すべきだ。"

これらを与えて最適化を図ります。

 

初期のプロンプト:

あなたはAIアシスタントです。以下のコンテキストを使用して、クエリを解決するための詳細な推論軌跡を生成してください。応答は日本語で行ってください。
コンテキスト:
- 動的計画法フィボナッチ数列問題を効率的に解くために使えます。
- メモ化の良い戦略は、辞書を使って結果を保存することです。
- Pythonの関数は、メモ化なしの再帰呼び出しでは遅くなることがあります。
- Pythonでのエラー処理はtry-exceptブロックで行うことができます。

クエリ: "フィボナッチ数列を計算するPython関数をどう書くか?"

ステップバイステップの推論軌跡を生成してください。

 

 

これが最終的には以下になりました:

あなたはAIアシスタントです。以下のコンテキストを使用して、クエリを解決するための詳細な推論軌跡を生成してください。応答は日本語で行ってください。
コンテキスト:
- 動的計画法フィボナッチ数列問題を効率的に解くために使えます。
- ステップごとに「何を決定・実行」「重要ポイント」「参考文献」を整理することで、フローが直感的に追える。
- 再帰実装の関数呼び出しオーバーヘッドが大きいことを認識し、必要に応じてイテレーティブ版を選択することが性能改善に直結する。
- メモ化の良い戦略は、辞書を使って結果を保存することです。
- 具体的な例外名・メッセージ・ロギングを網羅することで、実際に起きやすいエラーを先回避でき、トラブルシューティングが楽になる。
- コードの可読性を高めるために docstring とコメントを充実させると、保守性や拡張性が向上する。
- 戻り値の型を Optional を付けて明示すると、ユーザーは必ず3要素が返ることを期待でき、型チェックでエラーを防げる。
- 説明文は要点のみを抜き出し、重複表現を削除して簡潔にまとめることで読者が重要情報を把握しやすくなる。
- `np.fromiter` を使ってジェネレータ入力を直接数値配列に変換すると、メモリ効率が向上し、大規模データでもクラッシュしにくい。
- ロガーにハンドラを追加する前に既存のハンドラを確認し、重複を防ぐ。

クエリ: "フィボナッチ数列を計算するPython関数をどう書くか?"

ステップバイステップの推論軌跡を生成してください。

 

 

最終的に生成された回答:

## フィボナッチ数列を計算するPython関数を作るための推論軌跡  
以下では、**「動的計画法」**と**「再帰実装のオーバーヘッド」**を踏まえた上で、実装に必要な決定・実行ポイント・重要ポイント・参考文献を整理したステップバイステップの推論を示します。  

---

### 1️⃣ 目的・要件定義  
- **入力**: 正整数 `n`(求めたい項番号)。  
- **出力**:  
  - `n` 番目のフィボナッチ数 `F(n)`。  
  - さらに **「直前2項」**(`F(n-1)` と `F(n-2)`)を一緒に返すことで、将来的な再利用が容易になる。  
  - 返り値は `Optional[Tuple[int, int, int]]` とし、入力が無効な場合は `None` を返す。  
- **性能**:  
  - 再帰はオーバーヘッドが大きいので **イテレーティブ版**を基本とする。  
  - 大きな `n` に対しては `np.fromiter` を使い、配列を効率的に構築。  
- **エラーハンドリング**:  
  - `n < 0` なら `ValueError`。  
  - それ以外は `None` を返す。  
- **可読性・保守性**:  
  - docstring(Googleスタイル)+コメントを充実。  
  - `typing` モジュールで型ヒントを明示。  
  - ログ出力を `logging` で行い、既存ハンドラの重複を防止。  

---

### 2️⃣ 実装の選択肢整理  
| アプローチ | メリット | デメリット | 推奨 |
|-------------|----------|------------|------|
| 再帰+メモ化 | シンプルなコード | スタックオーバーフロー、オーバーヘッド | しない |
| イテレーティブ | O(n) 時間、O(1) 追加空間 | ループを意識 | ✅ |
| Numpy 配列 | 大規模データに強い | 余計な依存 | 必要に応じて |

---

### 3️⃣ 重要ポイントの整理  
| ポイント | 具体的な実装例 |
|----------|----------------|
| **入力チェック** | `if n < 0: raise ValueError("n must be non‑negative")` |
| **戻り値型** | `Optional[Tuple[int, int, int]]` で必ず 3 要素を返すか `None` |
| **メモ化戦略** | `memo: Dict[int, int] = {0:0, 1:1}` を用意し、再利用を防ぐ |
| **np.fromiter 利用** | `np.fromiter*1, dtype=int)` で配列生成 |
| **ロガー設定** | `logger = logging.getLogger(__name__)`、既存ハンドラ確認後に追加 |
| **ドキュメンテーション** | `"""Calculate nth Fibonacci number and return (F(n), F(n-1), F(n-2))"""` |
| **例外処理** | `ValueError` 以外は `None` 返却、詳細メッセージをログに残す |

---

### 4️⃣ 推論の流れ(ステップバイステップ)

1. **関数名とシグネチャの決定**  
   - `def fibonacci(n: int) -> Optional[Tuple[int, int, int]]:`

2. **docstring の作成**  
   - Google styleで入力・出力・例外・複数戻り値を説明。

3. **ロガー取得とハンドラチェック**  
   ```python
   logger = logging.getLogger(__name__)
   if not logger.handlers:
       handler = logging.StreamHandler()
       handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s:%(message)s'))
       logger.addHandler(handler)
   ```

4. **入力バリデーション**  
   ```python
   if n < 0:
       logger.error("Negative input: n=%s", n)
       raise ValueError("n must be non‑negative")
   ```

5. **ベースケース**  
   - `n == 0` なら `(0, 0, 0)`  
   - `n == 1` なら `(1, 1, 0)`

6. **イテレーティブ計算**  
   ```python
   a, b = 0, 1  # F(0), F(1)
   for i in range(2, n + 1):
       a, b = b, a + b
   # ここで b == F(n), a == F(n-1)
   ```

7. **np.fromiter を使う場合**  
   ```python
   fib_seq = np.fromiter*2, dtype=int)
   ```

8. **戻り値構築**  
   ```python
   return (b, a, 0 if n < 2 else a - b)  # 3番目は直前2項の差(必要に応じて)
   ```

9. **例外時の戻り値**  
   - `None` を返す(エラーログは上記で記録済み)。

---

### 5️⃣ 参考文献・リンク  
1. **Python 公式ドキュメント** – `typing` モジュール  
   <https://docs.python.org/3/library/typing.html>  
2. **NumPy 公式ドキュメント** – `np.fromiter`  
   <https://numpy.org/doc/stable/reference/generated/numpy.fromiter.html>  
3. **Python Logging** – ハンドラ追加のベストプラクティス  
   <https://docs.python.org/3/howto/logging.html#configuring-logging>  
4. **動的計画法(DP)** – 典型的な実装例  
   <https://en.wikipedia.org/wiki/Dynamic_programming>  
5. **再帰オーバーヘッドの分析** – 計算量とメモリ  
   <https://stackoverflow.com/questions/xxxxxx>  

---

### 6️⃣ まとめ(要点のみ)

| 項目 | 重要ポイント |
|------|--------------|
| **関数名** | `fibonacci` |
| **型ヒント** | `Optional[Tuple[int, int, int]]` |
| **実装** | イテレーティブ(O(n) 時間、O(1) 追加空間) |
| **大規模データ** | `np.fromiter` で配列化 |
| **例外** | `ValueError`(負値) → ログ + 例外発生、その他は `None` |
| **ログ** | 既存ハンドラ確認後に追加、エラー時に詳細ログ |
| **docstring** | Google styleで入力・出力・例外を明記 |
| **コメント** | 主要ステップに簡潔に説明 |

この推論軌跡に沿って実装すれば、**性能・可読性・保守性**を兼ね備えたフィボナッチ数列計算関数が完成します。

gpt-oss:20bの量子化モデルも十分実用的ですね。推論軌跡を生成させているので、分かり難いのは反省点です。ここまで配慮してフィボナッチ数列を計算することはあまりないかもしれませんね。

 

今回用いた、Colbノートです。

gist.github.com

*1:fib(i) for i in range(n+1

*2:b if i == n else a if i == n-1 else 0 for i in range(n+1