import google.generativeai as genai import json import re import graphviz import io from PIL import Image class BayesianLLMAssistant: """ 貝氏階層模型 LLM 問答助手(支援動態 DAG 生成) 協助用戶理解貝氏分析結果,並可根據描述生成客製化 DAG 圖 """ def __init__(self, api_key, session_id, api_provider="Google Gemini"): """ 初始化 LLM 助手 Args: api_key: API key (Gemini 或 Claude) session_id: 唯一的 session 識別碼 api_provider: API 提供商 ("Google Gemini" 或 "Anthropic Claude") """ self.api_provider = api_provider self.session_id = session_id self.conversation_history = [] if api_provider == "Google Gemini": import google.generativeai as genai genai.configure(api_key=api_key) self.model = genai.GenerativeModel('gemini-2.0-flash-exp') self.client = None else: # Anthropic Claude import anthropic self.client = anthropic.Anthropic(api_key=api_key) self.model_name = "claude-sonnet-4-5-20250929" self.model = None # 系統提示詞(加入 DAG 生成能力) # 完整修改後的 system_prompt # 替換 bayesian_llm_assistant.py 第 40-181 行 self.system_prompt = """You are an expert Bayesian statistician specializing in hierarchical models and meta-analysis, particularly in the context of Pokémon battle statistics. **IMPORTANT - Language Instruction:** - Always respond in the SAME language as the user's question - If user asks in Traditional Chinese (繁體中文), respond in Traditional Chinese - If user asks in English, respond in English - Maintain language consistency throughout the conversation 你是一位精通貝氏階層模型和統合分析的統計專家,特別專注於寶可夢對戰統計分析。 Your role is to help users understand Bayesian hierarchical model results analyzing win rate comparisons between Fire-type and Water-type Pokémon across different matchup pairs. 你的角色是幫助使用者理解貝氏階層模型分析結果, 了解火系與水系寶可夢在不同配對組合下的勝率比較。 **NEW CAPABILITY: DAG Diagram Generation | 新能力:DAG 圖生成** When users ask you to draw, create, or visualize a DAG (Directed Acyclic Graph) or model structure, you can generate Graphviz DOT code. 當用戶要求你繪製、創建或視覺化 DAG(有向無環圖)或模型結構時,你可以生成 Graphviz DOT 代碼。 **How to generate DAG code:** 1. Detect requests like: "draw a DAG", "show me the model structure", "visualize the relationships", "畫一個 DAG 圖", "顯示模型結構" 2. Generate Graphviz DOT code wrapped in special tags: ```graphviz digraph G { // Your DOT code here } ``` 3. The system will automatically render it as an image **IMPORTANT - Font and Label Instructions for DAG:** - NEVER use Chinese characters in node labels - Use ONLY English labels, or use English + romanized Chinese - DO NOT set fontname in the graph - Example of good labels: "d (overall effect)" or "delta[i] (pair-specific)" - Example of bad labels: "整體效應" or any Chinese text **重要 - DAG 圖的字型和標籤指示:** - 絕對不要在節點標籤中使用中文字 - 只使用英文標籤,或使用「英文 + 拼音」 - 不要設定 fontname - 好的標籤範例:"d (overall effect)" 或 "delta[i] (pair-specific)" - 不好的標籤範例:"整體效應" 或任何中文 **Example DAG code for Bayesian hierarchical model:** ```graphviz digraph BayesianModel { rankdir=TB; node [shape=ellipse, style=filled, fillcolor=lightblue]; // Priors d [label="d\n(Fire vs Water overall)", fillcolor=lightyellow]; tau [label="tau\n(precision)", fillcolor=lightyellow]; sigma [label="sigma = 1/√tau", shape=diamond, fillcolor=lightgray]; // Hierarchy d -> delta [label="mean"]; tau -> delta [label="precision"]; sigma -> delta [style=dashed]; delta [label="delta[i]\n(pair-specific)", fillcolor=lightgreen]; mu [label="mu[i]\n(baseline)", fillcolor=lightyellow]; // Likelihood delta -> pt [label="effect"]; mu -> pc; mu -> pt; pc [label="pc[i]\n(Water win rate)", shape=diamond, fillcolor=lightgray]; pt [label="pt[i]\n(Fire win rate)", shape=diamond, fillcolor=lightgray]; pc -> rc_obs [label="probability"]; pt -> rt_obs [label="probability"]; rc_obs [label="rc_obs[i]\n(Water wins)", shape=box, fillcolor=lightcoral]; rt_obs [label="rt_obs[i]\n(Fire wins)", shape=box, fillcolor=lightcoral]; } ``` You should: 1. Explain Bayesian concepts in simple, accessible terms 2. Interpret posterior distributions, HDI (Highest Density Interval), and credible intervals 3. Explain hierarchical structure and why it's useful 4. Help users understand heterogeneity (sigma) between different matchup pairs 5. Discuss the practical significance of Fire vs Water type advantages 6. Provide insights about which matchup pairs favor Fire-types the most 7. Suggest team building strategies based on the statistical findings 8. Clarify differences between Bayesian and frequentist approaches 9. Explain MCMC diagnostics (R-hat, ESS) when relevant 10. **Generate custom DAG diagrams based on user descriptions** 你應該: 1. 用簡單易懂的方式解釋貝氏概念 2. 詮釋後驗分佈、HDI(最高密度區間)和可信區間 3. 解釋階層結構及其優勢 4. 幫助使用者理解不同配對間的異質性(sigma) 5. 討論火系與水系屬性優劣勢的實際意義 6. 提供哪些配對組合中火系最具優勢的見解 7. 根據統計發現提出組隊策略建議 8. 說明貝氏方法與頻率論方法的差異 9. 適時解釋 MCMC 診斷指標(R-hat、ESS) 10. **根據用戶描述生成客製化 DAG 圖** Key concepts to explain when relevant: - **Bayesian Hierarchical Model**: Borrows strength across matchup pairs, shrinkage effect - **Prior & Posterior**: How data updates beliefs - **HDI (Highest Density Interval)**: 95% most credible values - **d (overall effect)**: Average log odds ratio of Fire vs Water across all pairs - **sigma (between-pair variation)**: How much different matchup pairs vary in Fire advantage - **delta (pair-specific effects)**: Each matchup pair's individual Fire advantage/disadvantage - **Odds Ratio**: exp(d) - how much more likely Fire-types are to win compared to Water-types - **MCMC**: Markov Chain Monte Carlo sampling method - **Convergence**: R-hat < 1.1, good ESS (effective sample size) - **DAG (Directed Acyclic Graph)**: Visual representation of model structure 重要概念解釋(當相關時): - **貝氏階層模型**:跨配對借用資訊,收縮效應 - **先驗與後驗**:資料如何更新信念 - **HDI(最高密度區間)**:95% 最可信的數值範圍 - **d(整體效應)**:火系相對於水系的平均對數勝算比(跨所有配對) - **sigma(配對間變異)**:不同配對組合的火系優勢差異程度 - **delta(配對特定效應)**:每組配對的個別火系優勢/劣勢 - **勝算比**:exp(d) - 火系相對於水系獲勝的可能性倍數 - **MCMC**:馬可夫鏈蒙地卡羅抽樣方法 - **收斂性**:R-hat < 1.1,良好的 ESS(有效樣本數) - **DAG(有向無環圖)**:模型結構的視覺化表示 When discussing Pokémon type matchups: - Connect statistical findings to type advantage mechanics (Water typically beats Fire in core games) - Explain why Fire vs Water matchups show certain patterns - Discuss individual matchup variations and their causes (e.g., specific Pokémon abilities, stats) - Identify which Fire/Water Pokémon pairs show unusual results (Fire winning despite type disadvantage) - Consider team building and type coverage implications 討論寶可夢屬性對抗時: - 將統計發現連結到屬性相剋機制(水系通常剋火系) - 解釋火系對水系的對戰模式為何呈現特定趨勢 - 討論個別配對的變異及其可能原因(例如特殊能力、數值差異) - 識別哪些火/水系配對顯示異常結果(火系儘管屬性不利仍獲勝) - 考慮組隊和屬性覆蓋的影響 Always be clear, educational, and engaging. Use examples when helpful. Format responses with proper markdown for better readability. 請務必清晰、具教育性、引人入勝。適時使用範例說明。使用適當的 Markdown 格式以提升可讀性。""" def get_response(self, user_message, analysis_results=None): """ 獲取 AI 回應(支援 DAG 生成) Args: user_message: 用戶訊息 analysis_results: 分析結果字典(可選) Returns: tuple: (回應文字, DAG 圖片或 None) """ # 準備上下文資訊 context = "" if analysis_results: context = self._prepare_context(analysis_results) # 添加用戶訊息到歷史 self.conversation_history.append({ "role": "user", "content": user_message }) try: # 構建完整的提示詞 full_prompt = self.system_prompt if context: full_prompt += f"\n\n## Current Analysis Context:\n{context}" # 構建對話歷史文字 conversation_text = "\n\n## Conversation History:\n" for msg in self.conversation_history[:-1]: role = "User" if msg["role"] == "user" else "Assistant" conversation_text += f"\n{role}: {msg['content']}\n" # 組合最終提示詞 final_prompt = full_prompt + conversation_text + f"\nUser: {user_message}\n\nAssistant:" # 調用對應的 API if self.api_provider == "Google Gemini": response = self.model.generate_content( final_prompt, generation_config=genai.types.GenerationConfig( temperature=0.7, max_output_tokens=4000, ) ) assistant_message = response.text else: # Anthropic Claude response = self.client.messages.create( model=self.model_name, max_tokens=4000, temperature=0.7, system=self.system_prompt, messages=[ {"role": "user", "content": final_prompt} ] ) assistant_message = response.content[0].text # 檢查是否包含 Graphviz 代碼 dag_image = self._extract_and_render_dag(assistant_message) # 添加助手回應到歷史 self.conversation_history.append({ "role": "assistant", "content": assistant_message }) return assistant_message, dag_image except Exception as e: error_msg = f"❌ Error: {str(e)}\n\nPlease check your API key and try again." return error_msg, None def _extract_and_render_dag(self, text): """ 從文字中提取 Graphviz 代碼並渲染成圖片 Args: text: 包含可能的 Graphviz 代碼的文字 Returns: PIL Image 或 None """ # 方法 1: 嘗試提取 ```graphviz ... ``` 格式 pattern1 = r'```graphviz\s*\n(.*?)\n```' matches = re.findall(pattern1, text, re.DOTALL) if matches: dot_code = matches[0] else: # 方法 2: 嘗試提取 digraph ... } 格式(沒有 markdown 包裹) #pattern2 = r'(digraph\s+\w+\s*\{.*?\n\})' pattern2 = r'(digraph\s+\w+\s*\{.*\})' matches = re.findall(pattern2, text, re.DOTALL) if not matches: return None dot_code = matches[0] try: # 使用 Graphviz 渲染 graph = graphviz.Source(dot_code) png_bytes = graph.pipe(format='png') # 轉換為 PIL Image img = Image.open(io.BytesIO(png_bytes)) return img except Exception as e: print(f"Failed to render DAG: {e}") return None def _prepare_context(self, results): """準備分析結果的上下文資訊""" if not results: return "目前尚無分析結果。No analysis results available yet." overall = results['overall'] interp = results['interpretation'] diag = results['diagnostics'] # 找出顯著的配對 sig_types = [ results['trial_labels'][i] for i, sig in enumerate(results['by_trial']['delta_significant']) if sig ] context = f""" ## Current Bayesian Hierarchical Model Analysis | 目前的貝氏階層模型分析 ### Overall Effect | 整體效應 - **d (Log Odds Ratio) | d(對數勝算比)**: - Mean | 平均: {overall['d_mean']:.4f} - SD | 標準差: {overall['d_sd']:.4f} - 95% HDI: [{overall['d_hdi_low']:.4f}, {overall['d_hdi_high']:.4f}] - **sigma (Between-pair Variation) | sigma(配對間變異)**: - Mean | 平均: {overall['sigma_mean']:.4f} - SD | 標準差: {overall['sigma_sd']:.4f} - 95% HDI: [{overall['sigma_hdi_low']:.4f}, {overall['sigma_hdi_high']:.4f}] - **Odds Ratio | 勝算比**: - Mean | 平均: {overall['or_mean']:.4f} - SD | 標準差: {overall['or_sd']:.4f} - 95% HDI: [{overall['or_hdi_low']:.4f}, {overall['or_hdi_high']:.4f}] ### Model Diagnostics | 模型診斷 - **R-hat (d)**: {f"{diag['rhat_d']:.4f}" if diag['rhat_d'] is not None else 'N/A'} {'✓' if diag['rhat_d'] and diag['rhat_d'] < 1.1 else '✗'} - **R-hat (sigma)**: {f"{diag['rhat_sigma']:.4f}" if diag['rhat_sigma'] is not None else 'N/A'} {'✓' if diag['rhat_sigma'] and diag['rhat_sigma'] < 1.1 else '✗'} - **ESS (d)**: {int(diag['ess_d']) if diag['ess_d'] is not None else 'N/A'} - **ESS (sigma)**: {int(diag['ess_sigma']) if diag['ess_sigma'] is not None else 'N/A'} - **Convergence | 收斂狀態**: {'✓ Converged 已收斂' if diag['converged'] else '✗ Not Converged 未收斂'} ### Interpretation | 結果解釋 - **Overall Effect | 整體效應**: {interp['overall_effect']} - **Significance | 顯著性**: {interp['overall_significance']} - **Effect Size | 效果大小**: {interp['effect_size']} - **Heterogeneity | 異質性**: {interp['heterogeneity']} ### Significant Pairs | 顯著的配對 {len(sig_types)} out of {results['n_trials']} pairs show significant Fire advantage: {len(sig_types)} 組配對(共 {results['n_trials']} 組)顯示顯著的火系優勢: {', '.join(sig_types) if sig_types else 'None 無'} ### Number of Pairs Analyzed | 分析的配對數量 {results['n_trials']} pairs in total 共 {results['n_trials']} 組配對 ### Key Finding | 關鍵發現 { f"On average, Fire-type Pokémon are {overall['or_mean']:.2f} times more likely to win compared to Water-type (95% HDI: [{overall['or_hdi_low']:.2f}, {overall['or_hdi_high']:.2f}]). 平均而言,火系寶可夢獲勝的可能性是水系的 {overall['or_mean']:.2f} 倍 (95% HDI: [{overall['or_hdi_low']:.2f}, {overall['or_hdi_high']:.2f}])。" if overall['or_mean'] > 1 else f"Interestingly, Water-type Pokémon show advantage over Fire-type despite type matchup, with OR = {overall['or_mean']:.2f}. 有趣的是,儘管屬性相剋,水系寶可夢相對火系仍顯示優勢,OR = {overall['or_mean']:.2f}。" } The variation between matchup pairs (sigma = {overall['sigma_mean']:.3f}) indicates {interp['heterogeneity'].lower()}. 配對間的變異(sigma = {overall['sigma_mean']:.3f})表示{interp['heterogeneity'].lower()}。 """ return context def draw_custom_dag(self, description): """ 根據用戶描述生成客製化 DAG 圖 Args: description: 用戶對 DAG 的描述 Returns: tuple: (解釋文字, DAG 圖片或 None) """ prompt = f"""Based on the following description, generate a Graphviz DOT code for a DAG diagram: User description: {description} Please: 1. Create a clear and informative DAG 2. Use appropriate node shapes (ellipse for random variables, box for observed data, diamond for deterministic nodes) 3. Use different colors to distinguish node types 4. **CRITICAL: Use ONLY English labels - NO Chinese characters in node labels** 5. Add labels to explain what each node represents (in English) 6. Wrap your DOT code in ```graphviz ``` tags 7. Provide a brief explanation in Traditional Chinese about what the diagram shows 根據以下描述,生成 Graphviz DOT 代碼的 DAG 圖: 用戶描述:{description} 請: 1. 創建清晰且有資訊性的 DAG 2. 使用適當的節點形狀(橢圓代表隨機變數,矩形代表觀測資料,菱形代表確定性節點) 3. 使用不同顏色區分節點類型 4. **重要:節點標籤必須使用英文,不能使用中文** 5. 添加標籤說明每個節點代表什麼(用英文) 6. 將 DOT 代碼包在 ```graphviz ``` 標籤中 7. 用繁體中文簡要說明圖表顯示什麼""" return self.get_response(prompt, None) # 保留原有的所有方法... def generate_summary(self, analysis_results): """自動生成分析結果總結""" summary_prompt = """請根據提供的貝氏階層模型分析結果生成一份完整的總結報告,包含: 1. **模型目的**:簡述這個階層模型在分析什麼(火系 vs 水系配對比較) 2. **整體發現**: - 火系相對於水系的整體勝率優勢如何? - d 和勝算比告訴我們什麼? - HDI 的意義是什麼? 3. **配對間差異**: - sigma 告訴我們什麼? - 哪些配對組合中火系特別強勢?哪些則較弱? 4. **模型品質**: - 模型收斂得好嗎?(R-hat、ESS) - 結果可信嗎? 5. **實戰啟示**: - 訓練師如何運用這些資訊? - 在火水對抗中,應該選擇哪些特定的寶可夢? 請用清楚的繁體中文 Markdown 格式撰寫,包含適當的章節標題。""" text, _ = self.get_response(summary_prompt, analysis_results) return text def explain_metric(self, metric_name, analysis_results): """解釋特定指標""" metric_explanations = { 'd': 'd (火系 vs 水系整體對數勝算比)', 'sigma': 'sigma (配對間變異)', 'or_speed': 'Odds Ratio (火系對水系勝算比)', 'hdi': '95% HDI (最高密度區間)', 'delta': 'delta (配對特定效應)', 'rhat': 'R-hat (收斂診斷)', 'ess': 'ESS (有效樣本數)' } metric_display = metric_explanations.get(metric_name, metric_name) explain_prompt = f"""請在這次貝氏階層模型分析的脈絡下,解釋以下指標: 指標:{metric_display} 請包含: 1. 這個指標在貝氏統計中測量什麼? 2. 在本次分析中得到的數值是多少? 3. 如何從寶可夢對戰的角度詮釋這個數值? 4. 與頻率論統計的對應指標有何不同? 5. 有什麼需要注意的限制或注意事項? 請用繁體中文回答。""" text, _ = self.get_response(explain_prompt, analysis_results) return text def explain_bayesian_vs_frequentist(self): """解釋貝氏與頻率論的差異""" explain_prompt = """請用簡單的方式解釋貝氏統計和頻率論統計的差異,特別是在寶可夢對戰分析的情境下。 請涵蓋: 1. 兩者的根本哲學差異是什麼? 2. p 值 vs HDI(可信區間)有什麼不同? 3. 為什麼我們用階層模型來分析多組配對? 4. 貝氏方法的優勢和限制是什麼? 5. 什麼時候該用貝氏、什麼時候該用頻率論? 請用寶可夢的實際例子讓說明更具體易懂,全程使用繁體中文。""" text, _ = self.get_response(explain_prompt, None) return text def explain_hierarchical_model(self): """解釋階層模型的概念""" explain_prompt = """請用簡單的方式解釋貝氏階層模型,特別是在火系對水系配對分析的情境下。 請涵蓋: 1. 什麼是階層模型?為什麼要用階層結構? 2. 「借用資訊」(borrowing strength) 是什麼意思? - 在本分析中,如何跨 46 組配對借用資訊? 3. 收縮效應 (shrinkage) 如何運作? - 為什麼某些極端的配對結果會被「拉回」? 4. 為什麼階層模型適合分析多組配對? 5. d(整體火系優勢)、sigma(配對間變異)、delta(各配對特定效應)之間的關係是什麼? 請用火系對水系的實際配對例子讓說明更具體易懂,全程使用繁體中文。""" text, _ = self.get_response(explain_prompt, None) return text def battle_strategy_advice(self, analysis_results): """提供對戰策略建議""" strategy_prompt = """根據貝氏階層模型的分析結果,請為寶可夢訓練師提供實際的組隊策略建議。 請考慮: 1. 整體而言,火系對水系的勝率如何? 2. 哪些火系寶可夢在對抗水系時特別強?(對應 delta 顯著為正的配對) 3. 是否有火系寶可夢即使對抗水系仍表現不佳?(delta 顯著為負) 4. 訓練師在選擇火系對抗水系時應該注意什麼? 5. 對競技對戰的組隊和屬性覆蓋有什麼啟示? 6. 有沒有出乎意料的發現?(例如某些火系特別強/弱) 請具體且可操作,使用繁體中文回答。""" text, _ = self.get_response(strategy_prompt, analysis_results) return text def compare_types(self, analysis_results): """比較不同配對""" compare_prompt = """請比較分析結果中不同配對組合的火系優勢差異。 請說明: 1. 哪些配對中火系特別強勢?可能的原因是什麼? - 考慮:特殊能力、數值優勢、招式組合 2. 哪些配對中火系反而處於劣勢?為什麼? 3. 配對間的異質性(sigma)告訴我們什麼? - 是否某些火/水系寶可夢特別能打破屬性相剋? 4. 有沒有令人意外的發現? - 例如:某個火系儘管屬性不利仍大幅獲勝 - 或某個火系表現比預期差很多 5. 這些差異對組隊策略有什麼啟示? - 如何針對性地選擇火系來對抗水系? 請用繁體中文回答。""" text, _ = self.get_response(compare_prompt, analysis_results) return text def reset_conversation(self): """重置對話歷史""" self.conversation_history = []