0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAI Function Callingで実現するAIオラクルカード占いシステムの開発

Posted at

はじめに

オラクルカード占いとAI技術の融合により、従来の占い体験を大きく進化させることができます。本記事では、OpenAIのFunction Calling機能を活用して、インテリジェントなオラクルカード占いシステムを開発する方法を解説します。

実装例はこちらでご覧いただけます。

オラクルカードとは?

オラクルカードは、タロットカードとは異なり、より直感的で自由度の高い占いツールです。固定された78枚の構造を持つタロットに対し、オラクルカードは枚数やテーマが自由で、日常的な瞑想や内なる知恵の啓発に適しています。

なぜFunction Callingが必要か?

従来のチャットボットでは、以下の課題がありました:

# 従来の方法:出力が不安定
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "user", "content": "恋愛について占ってください"}
    ]
)
# 結果:構造化されていない自由形式のテキスト

Function Callingを使用することで:

  • カード選択のタイミングを正確に制御
  • 構造化されたデータの取得
  • ユーザーの意図に応じた動的な処理

システムアーキテクチャ

実装詳細

1. Function定義

from openai import OpenAI
from typing import List, Dict
import json

client = OpenAI(api_key="your-api-key")

# オラクルカード関連のFunction定義
oracle_tools = [
    {
        "type": "function",
        "function": {
            "name": "drawOracleCard",
            "description": "ユーザーが導きや占いを求めた時にオラクルカードを引く",
            "parameters": {
                "type": "object",
                "properties": {
                    "question": {
                        "type": "string",
                        "description": "ユーザーの質問や悩み"
                    },
                    "cardCount": {
                        "type": "integer",
                        "description": "引くカードの枚数(デフォルト: 1)",
                        "default": 1
                    }
                },
                "required": ["question"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "createPersonalOracleCard",
            "description": "ユーザーのために個人的なオラクルカードを作成",
            "parameters": {
                "type": "object",
                "properties": {
                    "cardName": {
                        "type": "string",
                        "description": "カードの名前"
                    },
                    "cardMeaning": {
                        "type": "string",
                        "description": "カードの意味とメッセージ"
                    },
                    "visualDescription": {
                        "type": "string",
                        "description": "カードの視覚的描写(画像生成用)"
                    },
                    "keywords": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "カードに関連するキーワード"
                    }
                },
                "required": ["cardName", "cardMeaning", "visualDescription"]
            }
        }
    }
]

2. オラクルカードデータベース

# オラクルカードのデータ構造
oracle_cards_jp = [
    {
        "id": 1,
        "name": "新しい始まり",
        "message": "新たな冒険があなたを待っています。過去を手放し、新鮮な視点で前進する時です。",
        "keywords": ["新スタート", "チャンス", "勇気", "解放"],
        "element": ""
    },
    {
        "id": 2,
        "name": "内なる知恵",
        "message": "答えはすでにあなたの中にあります。静寂の中で内なる声に耳を傾けましょう。",
        "keywords": ["直感", "瞑想", "自己信頼", "洞察"],
        "element": ""
    },
    {
        "id": 3,
        "name": "豊かさの流れ",
        "message": "宇宙の豊かさがあなたに向かって流れています。受け取る準備をしましょう。",
        "keywords": ["豊穣", "感謝", "受容", "繁栄"],
        "element": ""
    },
    # ... 他のカードデータ
]

def get_oracle_card_by_energy(question: str, cards: List[Dict]) -> Dict:
    """
    質問のエネルギーに基づいてカードを選択
    実際の実装では、埋め込みベクトルなどを使用してより高度なマッチングが可能
    """
    import random
    
    # 質問のキーワード抽出(簡易版)
    love_keywords = ["恋愛", "", "パートナー", "結婚", "関係"]
    work_keywords = ["仕事", "キャリア", "転職", "成功", "目標"]
    spiritual_keywords = ["成長", "", "使命", "目的", "スピリチュアル"]
    
    # キーワードに基づいてカードをフィルタリング
    filtered_cards = cards
    
    if any(keyword in question for keyword in love_keywords):
        filtered_cards = [c for c in cards if any(k in ["", "関係", "調和"] for k in c.get("keywords", []))]
    elif any(keyword in question for keyword in work_keywords):
        filtered_cards = [c for c in cards if any(k in ["成功", "行動", "目標"] for k in c.get("keywords", []))]
    
    # フィルタリングされたカードから選択、なければ全体から
    return random.choice(filtered_cards if filtered_cards else cards)

3. メインの処理フロー

async def process_oracle_reading(user_message: str, conversation_history: List[Dict]) -> Dict:
    """
    オラクルカード占いのメイン処理
    """
    try:
        # OpenAI APIにリクエスト
        response = await client.chat.completions.create(
            model="gpt-4-turbo-preview",
            messages=[
                {
                    "role": "system",
                    "content": """あなたは経験豊富なオラクルカードリーダーです。
                    ユーザーの質問や悩みに対して、適切なタイミングでカードを引き、
                    深い洞察と実践的なアドバイスを提供します。
                    カードの解釈は、ユーザーの具体的な状況に合わせてパーソナライズしてください。"""
                },
                *conversation_history,
                {"role": "user", "content": user_message}
            ],
            tools=oracle_tools,
            tool_choice="auto"
        )
        
        message = response.choices[0].message
        
        # Function Callの処理
        if message.tool_calls:
            tool_responses = []
            
            for tool_call in message.tool_calls:
                function_name = tool_call.function.name
                function_args = json.loads(tool_call.function.arguments)
                
                if function_name == "drawOracleCard":
                    # カードを引く
                    drawn_card = await draw_oracle_card(
                        question=function_args.get("question"),
                        count=function_args.get("cardCount", 1)
                    )
                    
                    tool_responses.append({
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "content": json.dumps(drawn_card, ensure_ascii=False)
                    })
                
                elif function_name == "createPersonalOracleCard":
                    # パーソナルカードを作成
                    personal_card = await create_personal_card(
                        card_name=function_args.get("cardName"),
                        card_meaning=function_args.get("cardMeaning"),
                        visual_description=function_args.get("visualDescription"),
                        keywords=function_args.get("keywords", [])
                    )
                    
                    tool_responses.append({
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "content": json.dumps(personal_card, ensure_ascii=False)
                    })
            
            # ツールの結果を含めて再度APIを呼び出し
            final_response = await client.chat.completions.create(
                model="gpt-4-turbo-preview",
                messages=[
                    *conversation_history,
                    {"role": "user", "content": user_message},
                    message,
                    *tool_responses
                ],
                temperature=0.7
            )
            
            return {
                "message": final_response.choices[0].message.content,
                "tools_used": [tc.function.name for tc in message.tool_calls]
            }
        
        return {
            "message": message.content,
            "tools_used": []
        }
        
    except Exception as e:
        print(f"Error in oracle reading: {e}")
        raise

4. カード引きの実装

async def draw_oracle_card(question: str, count: int = 1) -> Dict:
    """
    オラクルカードを引く処理
    """
    drawn_cards = []
    
    for _ in range(count):
        # エネルギーマッチングによるカード選択
        card = get_oracle_card_by_energy(question, oracle_cards_jp)
        
        # カードの向き(正位置/逆位置)を決定
        is_upright = random.random() > 0.3  # 70%の確率で正位置
        
        drawn_cards.append({
            "card": card,
            "position": "正位置" if is_upright else "逆位置",
            "interpretation_hint": get_interpretation_hint(card, is_upright, question)
        })
    
    return {
        "cards": drawn_cards,
        "spread_type": get_spread_type(count),
        "timestamp": datetime.now().isoformat()
    }

def get_interpretation_hint(card: Dict, is_upright: bool, question: str) -> str:
    """
    カードの解釈のヒントを生成
    """
    if not is_upright:
        return f"{card['name']}の逆位置は、{card['message']}の裏面を示唆しています。障害や遅延、内面的な課題に注目してください。"
    
    return f"{card['message']} 特に「{question}」という質問に対して、{card['keywords'][0]}の視点から考えてみましょう。"

5. パーソナルカード作成

async def create_personal_card(
    card_name: str,
    card_meaning: str,
    visual_description: str,
    keywords: List[str]
) -> Dict:
    """
    AIを使用してパーソナルなオラクルカードを作成
    """
    try:
        # DALL-E 3で画像生成
        image_response = await client.images.generate(
            model="dall-e-3",
            prompt=f"""
            オラクルカードのイラストを作成してください:
            タイトル: {card_name}
            スタイル: 神秘的で幻想的な水彩画、金色のアクセント、神聖幾何学模様
            要素: {visual_description}
            雰囲気: 瞑想的、スピリチュアル、希望に満ちた
            """,
            size="1024x1024",
            quality="standard",
            n=1
        )
        
        image_url = image_response.data[0].url
        
        # カードデータを構造化
        personal_card = {
            "id": f"personal_{int(datetime.now().timestamp())}",
            "name": card_name,
            "message": card_meaning,
            "keywords": keywords,
            "image_url": image_url,
            "created_at": datetime.now().isoformat(),
            "type": "personal"
        }
        
        # データベースに保存(実装は省略)
        # await save_personal_card(personal_card)
        
        return {
            "success": True,
            "card": personal_card,
            "message": f"{card_name}」のパーソナルカードを作成しました。"
        }
        
    except Exception as e:
        return {
            "success": False,
            "error": str(e),
            "message": "カードの作成中にエラーが発生しました。"
        }

6. ストリーミング対応

async def stream_oracle_reading(user_message: str, conversation_history: List[Dict]):
    """
    ストリーミングレスポンス対応
    """
    stream = await client.chat.completions.create(
        model="gpt-4-turbo-preview",
        messages=[
            {"role": "system", "content": "オラクルカードリーダーのシステムプロンプト"},
            *conversation_history,
            {"role": "user", "content": user_message}
        ],
        tools=oracle_tools,
        tool_choice="auto",
        stream=True
    )
    
    collected_messages = []
    async for chunk in stream:
        delta = chunk.choices[0].delta
        
        if delta.content:
            yield {"type": "content", "data": delta.content}
        
        if delta.tool_calls:
            for tool_call in delta.tool_calls:
                if tool_call.function.name == "drawOracleCard":
                    # カード引きの準備を通知
                    yield {
                        "type": "tool_preparing",
                        "data": {"tool": "drawOracleCard", "status": "preparing"}
                    }

実装時の注意点

1. エラーハンドリング

class OracleReadingError(Exception):
    """オラクルリーディング専用のエラークラス"""
    pass

def validate_card_request(question: str) -> bool:
    """カードリクエストの検証"""
    if not question or len(question.strip()) < 3:
        raise OracleReadingError("質問が短すぎます。もう少し詳しく教えてください。")
    
    if len(question) > 500:
        raise OracleReadingError("質問が長すぎます。要点を絞って質問してください。")
    
    return True

2. レート制限とキャッシング

from functools import lru_cache
import asyncio

class RateLimiter:
    def __init__(self, max_requests: int, time_window: int):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests = []
    
    async def check_rate_limit(self, user_id: str) -> bool:
        current_time = time.time()
        self.requests = [
            (uid, t) for uid, t in self.requests 
            if current_time - t < self.time_window
        ]
        
        user_requests = sum(1 for uid, _ in self.requests if uid == user_id)
        if user_requests >= self.max_requests:
            return False
        
        self.requests.append((user_id, current_time))
        return True

# 使用例
rate_limiter = RateLimiter(max_requests=10, time_window=3600)  # 1時間に10回

3. 多言語対応

translations = {
    "ja": {
        "welcome": "オラクルカードへようこそ",
        "draw_card": "カードを引く",
        "your_card": "あなたのカード",
        "interpretation": "解釈"
    },
    "en": {
        "welcome": "Welcome to Oracle Cards",
        "draw_card": "Draw a Card",
        "your_card": "Your Card",
        "interpretation": "Interpretation"
    }
}

def get_localized_cards(locale: str = "ja") -> List[Dict]:
    """ロケールに応じたカードデータを取得"""
    card_files = {
        "ja": "oracle_cards_jp.json",
        "en": "oracle_cards_en.json",
        "es": "oracle_cards_es.json"
    }
    
    file_path = card_files.get(locale, card_files["ja"])
    with open(file_path, "r", encoding="utf-8") as f:
        return json.load(f)

パフォーマンス最適化

1. 非同期処理

async def batch_process_readings(requests: List[Dict]) -> List[Dict]:
    """複数のリーディングリクエストを並列処理"""
    tasks = []
    for request in requests:
        task = process_oracle_reading(
            request["message"],
            request["history"]
        )
        tasks.append(task)
    
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    return [
        result if not isinstance(result, Exception) else {"error": str(result)}
        for result in results
    ]

2. カードデータのインデックス化

class OracleCardIndex:
    def __init__(self, cards: List[Dict]):
        self.cards = cards
        self.keyword_index = self._build_keyword_index()
        self.element_index = self._build_element_index()
    
    def _build_keyword_index(self) -> Dict[str, List[int]]:
        """キーワードによるインデックスを構築"""
        index = {}
        for i, card in enumerate(self.cards):
            for keyword in card.get("keywords", []):
                if keyword not in index:
                    index[keyword] = []
                index[keyword].append(i)
        return index
    
    def search_by_keyword(self, keyword: str) -> List[Dict]:
        """キーワードでカードを検索"""
        indices = self.keyword_index.get(keyword, [])
        return [self.cards[i] for i in indices]

まとめ

OpenAIのFunction Callingを活用することで、従来の静的なオラクルカード占いを、AIによる動的で知的なシステムへと進化させることができました。

主な利点:

  • ユーザーの意図を正確に理解し、適切なタイミングでカードを引く
  • 個人の状況に合わせたパーソナライズされた解釈
  • AIによるオリジナルカードの作成機能
  • 構造化されたデータ処理による安定した動作

このシステムは、スピリチュアルな体験とテクノロジーを融合させた新しい形のサービスとして、実際に稼働しています

今後の展望

  • 音声インターフェース: Whisper APIを使用した音声入力対応
  • AR体験: カードを3D空間に表示するAR機能
  • コミュニティ機能: ユーザー作成カードの共有プラットフォーム
  • AI解釈の進化: より深い心理学的洞察を提供するファインチューニング

オラクルカード占いとAI技術の融合は、まだ始まったばかりです。Function Callingという強力なツールを使いこなすことで、より豊かなユーザー体験を創造できる可能性が広がっています。


参考リンク

タグ: #OpenAI #FunctionCalling #オラクルカード #AI占い #Python #GPT4

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?