実装概要
修仙テーマのボス戦において、プレイヤーが/adviceコマンドを実行した際に、現在のカード状況に基づいた戦術アドバイスを提供する機能を実装します。Deepseek APIを介した言語モデル呼び出しを経て、自然言語のアドバイスを生成する処理を構築します。
プロジェクト構成
必要なファイル構造と依存関係を設定します。
game_root/
├── .env
├── prompts/
│ └── strategy_template.txt
└── src/
└── core/
└── tactics/
└── advisor.py
依存パッケージ (requirements.txt)
langchain-core==1.0.10
langchain-openai==1.0.10
python-dotenv==1.0.0
テンプレートファイルの設計
prompts/strategy_template.txtにゲーム状況を反映するプロンプトを定義します。
修仙の達人として、心魔との戦いに臨む道友を助ける立場です。
戦闘ルール:
- 善/悪/中立のカードを1~3枚伏せて出し、宣言します。万能カードは任意の属性を偽装可能です
- 相手は検証・追随・放棄のいずれかを選択
- 手札を先に無くした方が勝利
現在の戦況:
- 自分の手札:善{zenshin}枚、悪{akushin}枚、中立{chuuryoku}枚、万能{tensei}枚
- 相手の残り手札:{enemy_count}枚
- テーブル上のカード:{table_count}枚
- 直前の相手宣言:{previous_claim}
- 現在のターン:{current_phase}
状況を分析し、簡潔なアドバイスを1文で出力してください。例:「相手は虚勢を張っている可能性、検証を推奨」
実装コード
src/core/tactics/advisor.pyに以下の処理を実装します。
import logging
from pathlib import Path
from typing import Dict, Optional
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 環境設定の読み込み
load_dotenv()
# テンプレートファイルの取得
TEMPLATE_FILE = Path(__file__).parent.parent.parent.parent / "prompts" / "strategy_template.txt"
PROMPT_TEMPLATE = TEMPLATE_FILE.read_text(encoding="utf-8")
def generate_tactical_advice(
self_hand: Dict[str, int],
enemy_hand: int,
table_cards: int,
last_declaration: Optional[str],
is_my_turn: bool
) -> Optional[str]:
"""
戦況データを基にDeepseek APIを呼び出し、戦術アドバイスを生成
エラー発生時はNoneを返す
"""
try:
# モデルの初期化(スコープを関数内に限定)
model = ChatOpenAI(
model_name="deepseek-chat",
temperature=0.15,
base_url="https://api.deepseek.com/v1",
api_key=os.getenv("DEEPSEEK_API_KEY"),
request_timeout=25
)
# プロンプトの動的生成
phase = "行動" if is_my_turn else "対応"
prompt = PromptTemplate.from_template(PROMPT_TEMPLATE).format(
zenshin=self_hand.get("good", 0),
akushin=self_hand.get("evil", 0),
chuuryoku=self_hand.get("neutral", 0),
tensei=self_hand.get("wild", 0),
enemy_count=enemy_hand,
table_count=table_cards,
previous_claim=last_declaration or "なし",
current_phase=phase
)
# API呼び出しと結果処理
result = model.invoke(prompt)
return result.content.strip() if result.content else None
except Exception as error:
logging.getLogger("tactics").exception("アドバイス生成に失敗: %s", error)
return None
実装のポイント
- パス管理:pathlibを活用した相対パスの安全な取得
- スコープ制限:LLMインスタンスを関数内に限定し、不要なリソース保持を防止
- 安全なフォーマット:辞書のgetメソッドでデフォルト値を指定
- タイムアウト設定:25秒でリクエストを中断し、ゲームフローを阻害しない設計
実行テスト
デバッグ用に簡易テストを追加可能:
if __name__ == "__main__":
sample_hand = {"good": 4, "evil": 2, "neutral": 3, "wild": 1}
advice = generate_tactical_advice(
self_hand=sample_hand,
enemy_hand=8,
table_cards=5,
last_declaration="neutral",
is_my_turn=True
)
print(f"戦術アドバイス: {advice}")