昨天我們學習了 LangGraph 的核心概念,今天讓我們動手建立一個實用的 LangGraph 應用 - 「智能學習助手」!這個助手能夠根據不同的學習需求,提供個性化的學習建議和資源推薦。
我們將建立一個多功能的學習助手,具備以下能力:
learning_assistant/
├── main.py # 主程式入口
├── models/
│ ├── __init__.py
│ ├── state.py # 狀態定義
│ └── schemas.py # 資料結構
├── nodes/
│ ├── __init__.py
│ ├── analyzer.py # 需求分析節點
│ ├── planner.py # 計劃制定節點
│ ├── explainer.py # 知識解釋節點
│ ├── generator.py # 題目生成節點
│ └── tracker.py # 進度追蹤節點
├── utils/
│ ├── __init__.py
│ ├── gemini_client.py # Gemini API 客戶端
│ └── helpers.py # 輔助函數
└── config.py # 設定檔
from typing import TypedDict, List, Dict, Optional
from enum import Enum
class LearningType(Enum):
PLAN = "learning_plan"
EXPLAIN = "concept_explanation"
PRACTICE = "practice_generation"
TRACK = "progress_tracking"
RECOMMEND = "resource_recommendation"
class LearningState(TypedDict):
# 使用者輸入
user_input: str
user_profile: Dict[str, str] # 學習者背景資訊
# 分析結果
learning_type: str
subject: str
difficulty_level: str
confidence_score: float
# 處理結果
learning_plan: Optional[Dict]
explanation: Optional[str]
practice_questions: Optional[List[Dict]]
progress_data: Optional[Dict]
recommendations: Optional[List[str]]
# 系統狀態
context: List[str]
session_history: List[Dict]
final_response: str
class QuestionItem(TypedDict):
question: str
answer: str
difficulty: str
explanation: str
import google.generativeai as genai
import os
from dotenv import load_dotenv
import json
from typing import Dict, Any
load_dotenv()
class GeminiClient:
def __init__(self):
genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
self.model = genai.GenerativeModel('gemini-2.5-flash')
def analyze_learning_request(self, user_input: str, user_profile: Dict) -> Dict[str, Any]:
"""分析學習需求"""
prompt = f"""
分析以下學習需求,並以 JSON 格式回應:
使用者輸入:{user_input}
使用者背景:{json.dumps(user_profile, ensure_ascii=False)}
請分析並回傳:
{{
"learning_type": "learning_plan | concept_explanation | practice_generation | progress_tracking | resource_recommendation",
"subject": "學科或主題",
"difficulty_level": "beginner | intermediate | advanced",
"confidence_score": 0.0-1.0,
"key_concepts": ["概念1", "概念2"],
"estimated_time": "預估學習時間"
}}
只回傳 JSON,不要額外文字。
"""
try:
response = self.model.generate_content(prompt)
return json.loads(response.text)
except Exception as e:
return {
"learning_type": "concept_explanation",
"subject": "通用",
"difficulty_level": "beginner",
"confidence_score": 0.5,
"key_concepts": [],
"estimated_time": "未知"
}
def generate_learning_plan(self, subject: str, difficulty: str, user_profile: Dict) -> Dict:
"""生成學習計劃"""
prompt = f"""
為以下條件制定詳細的學習計劃:
學科:{subject}
難度:{difficulty}
學習者背景:{json.dumps(user_profile, ensure_ascii=False)}
請以 JSON 格式提供:
{{
"title": "學習計劃標題",
"duration": "總學習時間",
"phases": [
{{
"phase_name": "階段名稱",
"duration": "階段時間",
"goals": ["目標1", "目標2"],
"activities": ["活動1", "活動2"],
"resources": ["資源1", "資源2"]
}}
],
"milestones": ["里程碑1", "里程碑2"],
"assessment_methods": ["評量方法1", "評量方法2"]
}}
"""
response = self.model.generate_content(prompt)
try:
return json.loads(response.text)
except:
return {"title": "基礎學習計劃", "duration": "4週", "phases": []}
def explain_concept(self, concept: str, difficulty: str, context: List[str]) -> str:
"""解釋概念"""
context_str = "\n".join(context) if context else "無特殊背景"
prompt = f"""
請詳細解釋以下概念,適合 {difficulty} 程度的學習者:
概念:{concept}
背景資訊:{context_str}
請包含:
1. 概念定義
2. 核心原理
3. 實際應用範例
4. 常見誤解澄清
5. 學習建議
用清晰易懂的方式說明,並使用適當的比喻或例子。
"""
response = self.model.generate_content(prompt)
return response.text
def generate_practice_questions(self, subject: str, difficulty: str, count: int = 3) -> List[Dict]:
"""生成練習題"""
prompt = f"""
為 {subject} 生成 {count} 道 {difficulty} 難度的練習題,以 JSON 格式回應:
[
{{
"question": "問題內容",
"answer": "標準答案",
"difficulty": "{difficulty}",
"explanation": "解題思路和詳細說明",
"hints": ["提示1", "提示2"],
"related_concepts": ["相關概念1", "相關概念2"]
}}
]
只回傳 JSON 陣列,不要額外文字。
"""
try:
response = self.model.generate_content(prompt)
return json.loads(response.text)
except:
return []
from models.state import LearningState, LearningType
from utils.gemini_client import GeminiClient
def analyze_learning_request(state: LearningState) -> LearningState:
"""分析學習需求節點"""
client = GeminiClient()
# 分析使用者請求
analysis = client.analyze_learning_request(
state["user_input"],
state["user_profile"]
)
# 更新狀態
return {
**state,
"learning_type": analysis["learning_type"],
"subject": analysis["subject"],
"difficulty_level": analysis["difficulty_level"],
"confidence_score": analysis["confidence_score"],
"context": state["context"] + [
f"識別學習類型:{analysis['learning_type']}",
f"學科:{analysis['subject']}",
f"難度:{analysis['difficulty_level']}"
]
}
from models.state import LearningState
from utils.gemini_client import GeminiClient
def create_learning_plan(state: LearningState) -> LearningState:
"""制定學習計劃節點"""
client = GeminiClient()
plan = client.generate_learning_plan(
state["subject"],
state["difficulty_level"],
state["user_profile"]
)
# 格式化回應
response = format_learning_plan(plan)
return {
**state,
"learning_plan": plan,
"final_response": response,
"context": state["context"] + ["已生成學習計劃"]
}
def format_learning_plan(plan: dict) -> str:
"""格式化學習計劃輸出"""
response = f"📚 **{plan.get('title', '個人化學習計劃')}**\n\n"
response += f"⏰ **總時長**:{plan.get('duration', '未指定')}\n\n"
phases = plan.get('phases', [])
for i, phase in enumerate(phases, 1):
response += f"### 階段 {i}:{phase.get('phase_name', f'階段{i}')}\n"
response += f"- ⏱ 時間:{phase.get('duration', '待定')}\n"
goals = phase.get('goals', [])
if goals:
response += f"- 🎯 目標:{', '.join(goals)}\n"
activities = phase.get('activities', [])
if activities:
response += f"- 📝 活動:{', '.join(activities)}\n"
response += "\n"
milestones = plan.get('milestones', [])
if milestones:
response += f"🏆 **重要里程碑**:\n"
for milestone in milestones:
response += f"- {milestone}\n"
return response
from models.state import LearningState
from utils.gemini_client import GeminiClient
def explain_concept(state: LearningState) -> LearningState:
"""解釋概念節點"""
client = GeminiClient()
explanation = client.explain_concept(
state["user_input"],
state["difficulty_level"],
state["context"]
)
formatted_response = f"💡 **概念解釋:{state['subject']}**\n\n{explanation}"
return {
**state,
"explanation": explanation,
"final_response": formatted_response,
"context": state["context"] + ["已提供概念解釋"]
}
from models.state import LearningState
from utils.gemini_client import GeminiClient
def generate_practice(state: LearningState) -> LearningState:
"""生成練習題節點"""
client = GeminiClient()
questions = client.generate_practice_questions(
state["subject"],
state["difficulty_level"],
count=3
)
response = format_practice_questions(questions)
return {
**state,
"practice_questions": questions,
"final_response": response,
"context": state["context"] + ["已生成練習題"]
}
def format_practice_questions(questions: list) -> str:
"""格式化練習題輸出"""
response = "📝 **練習題集**\n\n"
for i, q in enumerate(questions, 1):
response += f"### 題目 {i}\n"
response += f"**問題:** {q.get('question', '題目生成失敗')}\n\n"
response += f"**提示:** {', '.join(q.get('hints', ['暫無提示']))}\n\n"
response += f"---\n\n"
response += "💡 **學習建議:** 先嘗試獨立解答,再參考提示。完成後可以要求查看詳解!\n"
return response
from langgraph.graph import StateGraph, END
from models.state import LearningState, LearningType
from nodes.analyzer import analyze_learning_request
from nodes.planner import create_learning_plan
from nodes.explainer import explain_concept
from nodes.generator import generate_practice
from typing import Literal
def route_learning_task(state: LearningState) -> Literal["plan", "explain", "practice", "default"]:
"""路由到對應的處理節點"""
learning_type = state["learning_type"]
routing_map = {
"learning_plan": "plan",
"concept_explanation": "explain",
"practice_generation": "practice"
}
return routing_map.get(learning_type, "explain")
def create_learning_assistant():
"""建立學習助手工作流程"""
workflow = StateGraph(LearningState)
# 添加節點
workflow.add_node("analyzer", analyze_learning_request)
workflow.add_node("plan", create_learning_plan)
workflow.add_node("explain", explain_concept)
workflow.add_node("practice", generate_practice)
# 設定流程
workflow.set_entry_point("analyzer")
# 添加條件路由
workflow.add_conditional_edges(
"analyzer",
route_learning_task,
{
"plan": "plan",
"explain": "explain",
"practice": "practice"
}
)
# 結束節點
workflow.add_edge("plan", END)
workflow.add_edge("explain", END)
workflow.add_edge("practice", END)
return workflow.compile()
def main():
"""主程式"""
print("🎓 歡迎使用智能學習助手!")
print("💡 我可以幫您:制定學習計劃、解釋概念、生成練習題")
print("=" * 60)
# 初始化應用
app = create_learning_assistant()
# 簡單的使用者資料收集
print("\n📋 請先告訴我一些關於您的資訊:")
name = input("姓名:") or "學習者"
level = input("程度 (beginner/intermediate/advanced):") or "beginner"
background = input("學習背景:") or "一般學習者"
user_profile = {
"name": name,
"level": level,
"background": background
}
print(f"\n👋 {name},很高興為您服務!")
print("💡 提示:輸入 'quit' 結束程式\n")
while True:
try:
user_input = input(f"{name}:").strip()
if not user_input:
continue
if user_input.lower() in ['quit', 'exit', '退出']:
print("👋 再見!祝您學習愉快!")
break
# 執行工作流程
initial_state = {
"user_input": user_input,
"user_profile": user_profile,
"learning_type": "",
"subject": "",
"difficulty_level": level,
"confidence_score": 0.0,
"learning_plan": None,
"explanation": None,
"practice_questions": None,
"progress_data": None,
"recommendations": None,
"context": [],
"session_history": [],
"final_response": ""
}
result = app.invoke(initial_state)
print(f"\n🤖 學習助手:")
print(result["final_response"])
print("\n" + "="*60)
except KeyboardInterrupt:
print("\n👋 再見!祝您學習愉快!")
break
except Exception as e:
print(f"\n❌ 發生錯誤:{e}")
print("請再試一次或重新啟動程式。\n")
if __name__ == "__main__":
main()
python main.py
互動範例:
學習者:我想學習 Python 基礎程式設計
🤖 學習助手:
📚 **Python 基礎程式設計學習計劃**
⏰ **總時長**:6週
### 階段 1:基礎語法
- ⏱ 時間:2週
- 🎯 目標:掌握變數、資料型態、基本運算
- 📝 活動:程式碼練習、小專案實作
### 階段 2:控制結構
- ⏱ 時間:2週
- 🎯 目標:條件判斷、迴圈、函數
- 📝 活動:邏輯練習、函數設計
🏆 **重要里程碑**:
- 完成第一個完整程式
- 掌握基本除錯技巧
今天我們成功建立了一個功能完整的 LangGraph 學習助手!這個應用展示了如何運用圖形化工作流程來處理複雜的業務邏輯,每個節點都專注於特定功能,整體結構清晰易維護。
明天我們將學習如何整合 Gemini CLI 與 LangGraph,創建更加強大的 AI 助理系統!