Pārlūkot izejas kodu

feat: add LearningAgent graduation project

Add a comprehensive AI-powered learning assistant built with HelloAgents framework.

Project: LearningAgent - AI 智能学习助手
Author: @Yixiang-Wu
Category: 学习辅助 (Learning Assistant)

Features:
- Intelligent learning plan generation from domain/GitHub/PDF
- Smart knowledge note management with LLM analysis
- Interactive learning with free/quiz modes
- Learning progress tracking and evaluation

Technical Highlights:
- Three-layer Agent architecture (Coordination/Function/Specialist)
- Hybrid strategy for summary updates
- Streaming output support
- 80%+ test coverage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
wuyixiang 5 mēneši atpakaļ
vecāks
revīzija
f42a2706a7
25 mainītis faili ar 3593 papildinājumiem un 0 dzēšanām
  1. 12 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/.env.example
  2. 313 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/README.md
  3. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/agents/__init__.py
  4. 295 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/agents/create_plan_agent.py
  5. 158 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/agents/summary_agent.py
  6. 471 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/agents/vibe_learning_agent.py
  7. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/cli/__init__.py
  8. 100 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/cli/repl.py
  9. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/core/__init__.py
  10. 143 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/core/file_manager.py
  11. 513 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/core/main_agent.py
  12. 244 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/core/summary_manager.py
  13. 16 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/main.py
  14. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/processors/__init__.py
  15. 402 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/processors/add_knowledge.py
  16. 15 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/requirements.txt
  17. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/__init__.py
  18. 294 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/paper_analyzer.py
  19. 129 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/quiz_generator.py
  20. 285 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/repo_analyzer.py
  21. 0 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/utils/__init__.py
  22. 52 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/utils/error_handlers.py
  23. 43 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/utils/exceptions.py
  24. 47 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/utils/logger.py
  25. 61 0
      Co-creation-projects/Yixiang-Wu-LearningAgent/utils/streaming.py

+ 12 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/.env.example

@@ -0,0 +1,12 @@
+# LLM Configuration
+LLM_MODEL_ID=claude-3-5-sonnet-20241022
+LLM_API_KEY=your_api_key_here
+LLM_BASE_URL=https://api.anthropic.com
+LLM_TIMEOUT=60
+
+# GitHub Configuration
+GITHUB_TOKEN=your_github_token_here
+
+# Application Configuration
+LEARNING_AGENT_HOME=~/.learningAgent
+LEARNING_AGENT_LOG_LEVEL=INFO

+ 313 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/README.md

@@ -0,0 +1,313 @@
+# LearningAgent - AI 智能学习助手
+
+> 基于 HelloAgents 框架的个性化学习助手,通过 AI 对话帮助你创建学习计划、记录知识和追踪学习进度
+
+## 📝 项目简介
+
+LearningAgent 是一个智能学习助手,旨在帮助学习者:
+- **系统化学习**:根据领域描述、GitHub 项目或学术论文自动生成个性化学习路径
+- **知识管理**:智能分类和标签化学习笔记,支持多种输入方式(文本/文件/URL)
+- **互动学习**:通过对话和问答巩固知识,提供自由对话和结构化测验两种模式
+- **进度追踪**:自动评估学习进度,提供个性化学习建议
+
+**适用场景**:编程学习、技能提升、学术论文研读、开源项目分析等
+
+## ✨ 核心功能
+
+- [x] **智能学习计划生成** - 基于领域描述、GitHub 仓库或 PDF 论文自动生成学习路径
+- [x] **智能知识笔记** - LLM 自动分析、分类和标签化,支持文本/文件/URL 三种输入
+- [x] **互动式学习** - Free 模式(自由对话)和 Quiz 模式(结构化测验)
+- [x] **学习进度评估** - 分析学习计划、知识笔记和会话记录,生成进度报告
+- [x] **流式输出** - 实时显示 AI 响应,提升用户体验
+
+## 🛠️ 技术栈
+
+- **HelloAgents 框架**:
+  - SimpleAgent(MainAgent、SummaryAgent)
+  - ReActAgent(CreatePlanAgent)
+  - ReflectionAgent(VibeLearningAgent)
+- **专业层智能体**:RepoAnalyzerAgent、PaperAnalyzerAgent、QuizGeneratorAgent
+- **核心技术**:
+  - 三层 Agent 架构设计
+  - 混合策略摘要更新(<5个文件完全重写,≥5个增量更新)
+  - 流式输出支持
+- **LLM 提供商**:支持 OpenAI、DeepSeek、Qwen、ModelScope 等 10+ 服务
+- **开发工具**:pytest、black、mypy、flake8
+
+## 🚀 快速开始
+
+### 环境要求
+
+- Python 3.10+
+- Conda(推荐)或 venv
+
+### 安装依赖
+
+```bash
+# 克隆仓库
+git clone https://github.com/Yixiang-Wu/learningAgent.git
+cd learningAgent
+
+# 创建 conda 虚拟环境
+conda create -n learning-agent python=3.10
+conda activate learning-agent
+
+# 安装依赖
+pip install -r requirements.txt
+```
+
+### 配置 API 密钥
+
+```bash
+# 复制环境变量模板
+cp .env.example .env
+
+# 编辑 .env 文件,填入你的 API 密钥
+# LLM_MODEL_ID=gpt-4o-mini
+# LLM_API_KEY=your_api_key_here
+# LLM_BASE_URL=https://api.openai.com/v1
+```
+
+### 运行项目
+
+```bash
+# 启动 LearningAgent REPL
+python main.py
+
+# 在 REPL 中使用命令
+> /help                    # 显示帮助
+> /create Python           # 创建学习计划
+> /add Python # 装饰器模式  # 添加知识笔记
+> /vibe Python             # 开始互动学习
+> /vibe Python --mode quiz # 开始测验模式
+> /summary Python          # 查看学习总结
+> /list                    # 列出所有学习领域
+> /exit                    # 退出
+```
+
+## 📖 使用示例
+
+### 示例 1:创建学习计划
+
+```bash
+> /create Python
+```
+
+AI 会引导你:
+1. 询问学习目标(如:"想在工作中应用"、"想达到研究生水平")
+2. 分析领域/GitHub 项目/PDF 论文
+3. 搜索最佳学习资源
+4. 生成结构化的学习计划(保存到 `~/.learningAgent/{domain}/plan.md`)
+
+**生成计划示例**:
+```markdown
+# Python 学习计划
+
+## 领域概述
+Python 是一种高级编程语言...
+
+## 前置知识检查清单
+- [ ] 基本计算机概念
+- [ ] 逻辑思维能力
+
+## 分阶段学习路径
+### 阶段 1:Python 基础(2-3周)
+- 变量和数据类型
+- 控制流和函数
+...
+
+## 推荐资源
+- 书籍:《Python 编程:从入门到实践》
+- 课程:廖雪峰 Python 教程
+...
+```
+
+### 示例 2:添加知识笔记
+
+```bash
+# 文本输入
+> /add Python # 装饰器模式
+> /add 机器学习 决策树是一种监督学习算法...
+
+# 文件输入
+> /add ~/notes/react-hooks.md
+
+# URL 输入
+> /add https://blog.example.com/post
+```
+
+AI 会自动:
+1. 分析内容并识别领域
+2. 提取关键概念和标签
+3. 生成带时间戳的文件名
+4. 保存到 `~/.learningAgent/{domain}/knowledge/`
+5. 更新知识摘要文件
+
+**保存示例**:
+```
+~/.learningAgent/
+├── Python/
+│   ├── knowledge/
+│   │   ├── 20250111-算法-Python-装饰器.md
+│   │   └── 20250111-通用-列表推导式.md
+│   └── knowledge_summary.md
+```
+
+### 示例 3:互动学习
+
+```bash
+# Free 模式 - 自由对话
+> /vibe Python
+
+# Quiz 模式 - 结构化测验
+> /vibe Python --mode quiz
+```
+
+**Free 模式**:
+- AI 生成开放性问题,鼓励讨论
+- 动态调整对话方向
+- 引导深入思考
+
+**Quiz 模式**:
+- 结构化测验题
+- 自动评估答案
+- 难度逐步递增
+
+每次会话自动记录并生成总结。
+
+### 示例 4:查看学习总结
+
+```bash
+> /summary Python
+```
+
+生成进度报告包含:
+1. **当前水平评估** - 整体掌握度百分比、所处学习阶段
+2. **知识点分析** - 掌握良好的知识点、需要加强的知识点
+3. **下一步建议** - 具体学习主题推荐
+4. **总体建议** - 鼓励和指导、学习策略调整
+
+## 🎯 项目亮点
+
+### 1. 三层 Agent 架构设计
+
+清晰的职责分离:
+- **协调层**(Layer 1):MainAgent - 意图识别和路由
+- **功能层**(Layer 2):CreatePlanAgent、VibeLearningAgent、SummaryAgent
+- **专业层**(Layer 3):RepoAnalyzerAgent、PaperAnalyzerAgent、QuizGeneratorAgent
+
+### 2. 混合策略摘要更新
+
+优化性能和存储:
+- **< 5 个文件**:完全重写摘要
+- **≥ 5 个文件**:增量更新摘要
+
+### 3. 流式输出支持
+
+实时显示 AI 响应,提升用户体验:
+- 自动检测终端能力
+- 可配置启用/禁用
+- 优雅处理累积块
+
+### 4. 智能知识管理
+
+- LLM 自动分析和分类
+- 支持多种输入方式(文本/文件/URL)
+- 提取关键概念和标签
+- 生成结构化摘要
+
+### 5. 完整的测试覆盖
+
+- 单元测试 > 80% 覆盖率
+- 集成测试
+- 真实环境测试
+
+## 📊 项目结构
+
+```
+learningAgent/
+├── core/                      # 核心层(基础设施)
+│   ├── main_agent.py          # 主 Agent(协调层)
+│   ├── file_manager.py        # 文件管理
+│   └── summary_manager.py     # 摘要管理(混合策略)
+├── agents/                    # 功能层(业务逻辑)
+│   ├── create_plan_agent.py   # 创建学习计划(ReAct)
+│   ├── vibe_learning_agent.py # 互动学习(Reflection)
+│   └── summary_agent.py       # 学习总结(Simple)
+├── specialist/                # 专业层(专项能力)
+│   ├── repo_analyzer.py       # GitHub 仓库分析
+│   ├── paper_analyzer.py      # PDF 论文分析
+│   └── quiz_generator.py      # 测验生成
+├── processors/                # 处理器
+│   └── add_knowledge.py       # 添加知识笔记
+├── cli/                       # 命令行界面
+│   └── repl.py                # REPL 循环
+├── utils/                     # 工具类
+│   ├── streaming.py           # 流式输出
+│   ├── error_handlers.py      # 错误处理
+│   └── exceptions.py          # 异常定义
+├── tests/                     # 测试套件
+├── main.py                    # 入口文件
+├── requirements.txt           # 依赖列表
+└── README.md                  # 项目文档
+```
+
+## 🧪 运行测试
+
+```bash
+# 运行所有测试
+pytest tests/ -v
+
+# 运行特定测试
+pytest tests/test_agents/test_create_plan_agent.py -v
+
+# 查看测试覆盖率
+pytest tests/ --cov=. --cov-report=term-missing
+```
+
+## 🔮 未来计划
+
+- [ ] Web UI 界面(基于 FastAPI + Vue)
+- [ ] 多语言支持
+- [ ] 导出学习报告为 PDF
+- [ ] 集成更多 LLM 提供商
+- [ ] 学习数据可视化
+- [ ] 社区学习计划分享
+
+## 🤝 贡献指南
+
+欢迎提交 Issue 和 Pull Request!
+
+### 开发流程
+
+1. Fork 本仓库
+2. 创建特性分支(`git checkout -b feature/AmazingFeature`)
+3. 提交更改(`git commit -m 'feat: Add some AmazingFeature'`)
+4. 推送到分支(`git push origin feature/AmazingFeature`)
+5. 开启 Pull Request
+
+### 代码规范
+
+- 使用 `black` 格式化代码
+- 使用 `mypy` 进行类型检查
+- 使用 `flake8` 检查代码质量
+- 编写单元测试
+
+## 📄 许可证
+
+MIT License
+
+## 👤 作者
+
+- GitHub: [@Yixiang-Wu](https://github.com/Yixiang-Wu)
+- 项目链接: [learningAgent](https://github.com/Yixiang-Wu/learningAgent)
+
+## 🙏 致谢
+
+- [Datawhale](https://github.com/datawhalechina) 社区
+- [Hello-Agents](https://github.com/datawhalechina/hello-agents) 框架
+- 所有贡献者和学习者
+
+---
+
+**注意**:本项目作为 Hello-Agents 教程的毕业设计项目,旨在展示如何使用 HelloAgents 框架构建完整的多智能体应用。

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/agents/__init__.py


+ 295 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/agents/create_plan_agent.py

@@ -0,0 +1,295 @@
+# agents/create_plan_agent.py
+"""学习计划生成 Agent"""
+
+import re
+from hello_agents import ReActAgent, HelloAgentsLLM
+from core.file_manager import FileManager
+
+
+class CreatePlanAgent(ReActAgent):
+    """
+    学习计划生成专家
+    支持三种输入:领域描述、GitHub URL、PDF 论文
+    """
+
+    def __init__(self, llm: HelloAgentsLLM, streaming: bool = None):
+        """
+        初始化 CreatePlanAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            streaming: 是否启用流式输出(None = 自动检测)
+        """
+        self.max_steps = 5
+        self.file_manager = FileManager()
+
+        # 添加流式输出支持
+        from utils.streaming import should_stream
+        self.streaming = should_stream(streaming)
+
+        # 系统提示词
+        system_prompt = """
+        你是学习规划专家。工作流程:
+
+        1. 识别输入类型:
+           - 领域描述(如:"我想学习数学")
+           - GitHub URL(如:"https://github.com/user/project")
+           - PDF 论文路径(如:"/path/to/paper.pdf")
+
+        2. 如果是 URL/文件,调用相应工具深度分析
+
+        3. 询问用户的学习目标:
+           - 使用自然语言描述(如:"想在工作中应用","想达到研究生水平")
+
+        4. 根据分析结果和学习目标,搜索该领域的最佳学习路径
+
+        5. 生成结构化的学习计划(Markdown格式),包括:
+           - 领域概述
+           - 前置知识要求
+           - 学习路径(分阶段)
+           - 推荐资源
+           - 里程碑和检查点
+
+        使用 ReAct 格式:
+        Thought: 你的思考过程
+        Action: tool_name[input]
+        Observation: 工具返回结果
+        ...
+        Finish: [最终生成的学习计划]
+        """
+
+        # 使用父类初始化
+        super().__init__("CreatePlanAgent", llm, system_prompt)
+
+    def _identify_input_type(self, input_data: str) -> str:
+        """
+        识别输入类型
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            输入类型(github_url/pdf_paper/domain_description)
+        """
+        # 检查 GitHub URL
+        if input_data.startswith("https://github.com/"):
+            return "github_url"
+
+        # 检查 PDF 文件路径
+        if (
+            input_data.endswith(".pdf")
+            or input_data.startswith("~/")
+            or input_data.startswith("/")
+        ):
+            return "pdf_paper"
+
+        # 默认为领域描述
+        return "domain_description"
+
+    def _analyze_github_repo(self, url: str) -> dict:
+        """
+        分析 GitHub 仓库
+
+        Args:
+            url: GitHub URL
+
+        Returns:
+            分析结果字典
+        """
+        from specialist.repo_analyzer import RepoAnalyzerAgent
+        import os
+
+        # 获取 GitHub Token(如果配置了)
+        github_token = os.getenv("GITHUB_TOKEN")
+
+        # 创建 RepoAnalyzerAgent
+        repo_analyzer = RepoAnalyzerAgent(self.llm, github_token)
+
+        # 分析仓库
+        try:
+            analysis = repo_analyzer.analyze(url)
+            return {
+                "domain": analysis.get("domain", ""),
+                "tech_stack": analysis.get("tech_stack", []),
+                "prerequisites": analysis.get("prerequisites", []),
+                "description": analysis.get("description", ""),
+                "stars": analysis.get("stars", 0),
+            }
+        except Exception as e:
+            # 降级:使用简化实现
+            repo_name = url.rstrip(".git").split("/")[-1]
+            return {
+                "domain": repo_name.replace("-", " ").replace("_", " "),
+                "tech_stack": [],
+                "prerequisites": [],
+                "description": f"GitHub 仓库分析失败:{e}",
+                "stars": 0,
+            }
+
+    def _analyze_pdf_paper(self, file_path: str) -> dict:
+        """
+        分析 PDF 论文
+
+        Args:
+            file_path: PDF 文件路径
+
+        Returns:
+            分析结果字典
+        """
+        from specialist.paper_analyzer import PaperAnalyzerAgent
+
+        # 创建 PaperAnalyzerAgent
+        paper_analyzer = PaperAnalyzerAgent(self.llm)
+
+        # 分析论文
+        try:
+            analysis = paper_analyzer.analyze(file_path)
+            return {
+                "domain": analysis.get("domain", ""),
+                "title": analysis.get("title", ""),
+                "prerequisites": analysis.get("prerequisites", []),
+                "core_concepts": analysis.get("core_concepts", []),
+            }
+        except Exception as e:
+            # 降级:使用简化实现
+            import os
+
+            filename = os.path.basename(file_path).replace(".pdf", "").replace("-", " ")
+            return {
+                "domain": filename,
+                "title": filename,
+                "prerequisites": [],
+                "core_concepts": [],
+                "error": f"PDF 分析失败:{e}",
+            }
+
+    def _ask_learning_goal(self, analysis: dict) -> str:
+        """
+        询问学习目标
+
+        Args:
+            analysis: 分析结果
+
+        Returns:
+            学习目标描述
+        """
+        print(f"\n📚 分析结果:{analysis.get('domain', '未知领域')}")
+        if analysis.get("tech_stack"):
+            print(f"技术栈:{', '.join(analysis['tech_stack'])}")
+        if analysis.get("prerequisites"):
+            print(f"前置知识:{', '.join(analysis['prerequisites'])}")
+        if analysis.get("title"):
+            print(f"论文标题:{analysis['title']}")
+        if analysis.get("core_concepts"):
+            print(
+                f"核心概念:{', '.join(analysis['core_concepts'][:5])}"
+            )  # 最多显示5个
+        if analysis.get("description"):
+            print(f"描述:{analysis['description']}")
+        if analysis.get("stars", 0) > 0:
+            print(f"⭐ Stars: {analysis['stars']}")
+
+        return input("\n🎯 你想达到什么学习程度?(请用自然语言描述)\n> ")
+
+    def _search_learning_resources(self, query: str) -> str:
+        """
+        搜索学习资源
+
+        Args:
+            query: 搜索查询
+
+        Returns:
+            搜索结果
+        """
+        # 简化实现,返回通用建议
+        return f"为 '{query}' 找到的学习资源:在线课程、书籍、文档、实战项目"
+
+    def _generate_plan(self, analysis: dict, goal: str, resources: str) -> str:
+        """
+        生成学习计划
+
+        Args:
+            analysis: 分析结果
+            goal: 学习目标
+            resources: 学习资源
+
+        Returns:
+            学习计划内容
+        """
+        user_prompt = f"""请为以下场景生成学习计划(Markdown格式):
+
+【领域/主题】
+{analysis.get('domain', '未知')}
+
+【技术栈】
+{', '.join(analysis.get('tech_stack', ['无']))}
+
+【前置知识要求】
+{', '.join(analysis.get('prerequisites', ['无']))}
+
+【学习目标】
+{goal}
+
+【参考资源】
+{resources}
+
+请生成结构化的学习计划,包括:
+1. 领域概述(100字)
+2. 前置知识检查清单
+3. 分阶段学习路径(3-5个阶段)
+4. 每个阶段的具体学习内容
+5. 推荐资源(书籍、课程、文档)
+6. 里程碑和自我评估标准
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个专业的学习规划助手,擅长创建结构化的学习计划。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        if self.streaming:
+            from utils.streaming import stream_response
+            return stream_response(self.llm, messages)
+        else:
+            return self.llm.invoke(messages)
+
+    def run(self, input_data: str) -> str:
+        """
+        执行学习计划创建流程
+
+        Args:
+            input_data: 用户输入(领域描述/GitHub URL/PDF路径)
+
+        Returns:
+            执行结果
+        """
+        # 步骤1:识别输入类型
+        input_type = self._identify_input_type(input_data)
+
+        # 步骤2:根据类型处理
+        if input_type == "github_url":
+            analysis = self._analyze_github_repo(input_data)
+        elif input_type == "pdf_paper":
+            analysis = self._analyze_pdf_paper(input_data)
+        else:  # domain_description
+            analysis = {"domain": input_data, "tech_stack": [], "prerequisites": []}
+
+        # 步骤3:确认学习目标
+        learning_goal = self._ask_learning_goal(analysis)
+
+        # 步骤4:搜索学习路径
+        search_query = f"{analysis['domain']} 学习路径 {learning_goal}"
+        learning_resources = self._search_learning_resources(search_query)
+
+        # 步骤5:生成计划
+        plan = self._generate_plan(analysis, learning_goal, learning_resources)
+
+        # 步骤6:保存计划
+        domain = analysis["domain"]
+        self.file_manager.create_domain(domain)
+        self.file_manager.save_plan(domain, plan)
+
+        return f"✅ 学习计划已创建:{domain}\n\n{plan}"

+ 158 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/agents/summary_agent.py

@@ -0,0 +1,158 @@
+# agents/summary_agent.py
+"""学习进度评估 Agent - 生成学习总结和建议"""
+
+from hello_agents import SimpleAgent, HelloAgentsLLM
+from core.file_manager import FileManager
+from pathlib import Path
+
+
+class SummaryAgent(SimpleAgent):
+    """
+    学习进度评估专家
+
+    功能:
+    - 读取学习目标(plan.md)
+    - 读取已掌握知识(knowledge_summary.md)
+    - 读取学习历程(session_summary.md)
+    - 生成当前水平评估
+    - 推荐下一步学习内容
+    """
+
+    def __init__(self, llm: HelloAgentsLLM, file_manager: FileManager, streaming: bool = None):
+        """
+        初始化 SummaryAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            file_manager: FileManager 实例
+            streaming: 是否启用流式输出(None = 自动检测)
+        """
+        system_prompt = """
+你是学习评估专家。
+
+任务:
+1. 对比学习目标和现状,评估掌握程度(百分比)
+2. 识别强项和弱项
+3. 推荐下一步学习内容
+4. 提供具体的学习建议
+
+输出格式:
+# 📊 学习进度报告
+
+## 当前水平
+- 整体掌握度:XX%
+- 处于阶段:入门/熟练/精通
+
+## ✅ 掌握良好的知识点
+- [知识点1]:简短评价
+- [知识点2]:简短评价
+
+## ⚠️ 需要加强的知识点
+- [知识点1]:原因分析
+- [知识点2]:原因分析
+
+## 📌 下一步学习建议
+1. [具体主题1]:学习建议
+2. [具体主题2]:学习建议
+
+## 💡 总体建议
+[鼓励和指导]
+"""
+
+        self.llm = llm
+        self.file_manager = file_manager
+
+        # 添加流式输出支持
+        from utils.streaming import should_stream
+        self.streaming = should_stream(streaming)
+
+        # 使用父类初始化
+        super().__init__("SummaryAgent", llm, system_prompt)
+
+    def run(self, domain: str) -> str:
+        """
+        生成学习进度总结
+
+        Args:
+            domain: 领域名称
+
+        Returns:
+            学习进度报告
+        """
+        # 检查领域是否存在
+        if not self.file_manager.domain_exists(domain):
+            return f"❌ 领域 '{domain}' 不存在。请先使用 /create 创建学习计划。"
+
+        # 读取必要的文件
+        try:
+            # 读取学习计划
+            plan = self.file_manager.read_plan(domain)
+
+            # 读取知识摘要
+            knowledge_summary_path = (
+                self.file_manager.BASE_DIR / domain / "knowledge" / "knowledge_summary.md"
+            )
+            if knowledge_summary_path.exists():
+                knowledge_summary = knowledge_summary_path.read_text(encoding="utf-8")
+            else:
+                knowledge_summary = "暂无知识笔记"
+
+            # 读取会话摘要
+            session_summary_path = (
+                self.file_manager.BASE_DIR / domain / "sessions" / "session_summary.md"
+            )
+            if session_summary_path.exists():
+                session_summary = session_summary_path.read_text(encoding="utf-8")
+            else:
+                session_summary = "暂无学习记录"
+
+        except Exception as e:
+            return f"❌ 读取文件失败:{e}"
+
+        # 生成总结
+        user_prompt = f"""请分析以下学习情况:
+
+【学习目标】
+{plan[:2000]}
+
+【已掌握知识】
+{knowledge_summary[:2000]}
+
+【学习历程】
+{session_summary[:2000]}
+
+请按照系统提示词的格式生成学习进度报告。
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个学习评估专家,擅长分析学习进度并提供针对性建议。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            if self.streaming:
+                from utils.streaming import stream_response
+                return stream_response(self.llm, messages)
+            else:
+                return self.llm.invoke(messages).strip()
+        except Exception as e:
+            # 如果 LLM 调用失败,返回简化版本
+            return f"""# 📊 学习进度报告
+
+## 当前水平
+- 领域:{domain}
+- 状态:学习进行中
+
+## 📚 学习内容
+- 学习计划:已创建
+- 知识笔记:{'有' if knowledge_summary != '暂无知识笔记' else '无'}
+- 学习记录:{'有' if session_summary != '暂无学习记录' else '无'}
+
+## 💡 建议
+请继续添加知识笔记和参与互动学习,以获得更准确的进度评估。
+
+⚠️ 生成详细报告时遇到问题:{e}
+"""

+ 471 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/agents/vibe_learning_agent.py

@@ -0,0 +1,471 @@
+# agents/vibe_learning_agent.py
+"""互动学习 Agent - 通过对话和测验巩固知识"""
+
+import json
+from datetime import datetime
+from typing import Dict, List
+from hello_agents import HelloAgentsLLM
+from hello_agents import SimpleAgent
+from specialist.quiz_generator import QuizGeneratorAgent
+from core.file_manager import FileManager
+from core.summary_manager import SummaryManager
+
+
+class VibeLearningAgent(SimpleAgent):
+    """
+    互动学习专家
+
+    功能:
+    - 支持两种模式:free(自由对话)和 quiz(结构化测验)
+    - 根据学习计划生成问题
+    - 评估用户回答并提供反馈
+    - 动态调整问题难度
+    - 生成会话总结
+    """
+
+    def __init__(self, llm: HelloAgentsLLM, file_manager: FileManager, streaming: bool = None):
+        """
+        初始化 VibeLearningAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            file_manager: FileManager 实例
+            streaming: 是否启用流式输出(None = 自动检测)
+        """
+        system_prompt = """
+你是专业的学习教练。
+
+工作流程:
+1. 读取学习计划(plan.md),了解知识体系
+2. 根据模式(free/quiz)生成初始问题
+3. 评估用户回答,给予反馈
+4. 动态调整问题难度
+5. 结束时生成会话总结
+
+模式差异:
+- free: 开放性问题,鼓励讨论,引导思考
+- quiz: 结构化考察,固定问题,自动评分
+
+反馈技巧:
+- 肯定正确的部分
+- 指出需要改进的地方
+- 提供额外的知识点链接
+- 鼓励继续探索
+"""
+
+        self.llm = llm
+        self.file_manager = file_manager
+        self.quiz_generator = QuizGeneratorAgent(llm)
+        self.max_iterations = 10
+
+        # 添加流式输出支持
+        from utils.streaming import should_stream
+        self.streaming = should_stream(streaming)
+
+        # 使用父类初始化
+        super().__init__("VibeLearningAgent", llm, system_prompt)
+
+    def start_session(
+        self, domain: str, mode: str = "free"
+    ) -> str:
+        """
+        开始互动学习会话(只生成第一个问题)
+
+        Args:
+            domain: 领域名称
+            mode: 学习模式(free/quiz)
+
+        Returns:
+            第一个问题
+        """
+        # 检查领域是否存在
+        if not self.file_manager.domain_exists(domain):
+            return f"❌ 领域 '{domain}' 不存在。请先使用 /create 创建学习计划。"
+
+        # 读取学习计划
+        try:
+            plan = self.file_manager.read_plan(domain)
+        except Exception as e:
+            return f"❌ 读取学习计划失败:{e}"
+
+        # 生成第一个问题
+        question = self._generate_first_question(plan, mode)
+
+        # 保存问题到会话历史(用于后续继续)
+        self._save_session_start(domain, mode, question)
+
+        return f"""💬 {mode.upper()} 模式学习会话开始
+
+{question}
+
+💡 提示:输入你的回答开始对话
+"""
+
+    def _save_session_start(self, domain: str, mode: str, question: str) -> None:
+        """
+        保存会话开始状态
+
+        Args:
+            domain: 领域名称
+            mode: 模式
+            question: 第一个问题
+        """
+        session_path = self.file_manager.BASE_DIR / domain / "sessions"
+        session_path.mkdir(parents=True, exist_ok=True)
+
+        # 创建临时会话文件
+        temp_file = session_path / ".current_session.txt"
+        temp_file.write_text(
+            f"{mode}\n{datetime.now().strftime('%Y-%m-%d %H:%M')}\n{question}",
+            encoding='utf-8'
+        )
+
+    def continue_session(self, domain: str, user_answer: str, mode: str) -> str:
+        """
+        继续对话会话
+
+        Args:
+            domain: 领域名称
+            user_answer: 用户回答
+            mode: 模式
+
+        Returns:
+            反馈和下一个问题
+        """
+        try:
+            # 读取计划
+            plan = self.file_manager.read_plan(domain)
+
+            # 读取上一个问题(从临时文件)
+            session_path = self.file_manager.BASE_DIR / domain / "sessions"
+            temp_file = session_path / ".current_session.txt"
+
+            if temp_file.exists():
+                lines = temp_file.read_text(encoding='utf-8').strip().split('\n')
+                last_question = lines[-1] if len(lines) > 0 else ""
+            else:
+                last_question = "请描述你对这个主题的理解。"
+
+            # 生成反馈
+            feedback = self._generate_feedback(last_question, user_answer, plan)
+
+            # 生成下一个问题
+            next_question = self._generate_next_question(plan, [last_question, user_answer], mode)
+
+            # 更新临时文件
+            temp_file.write_text(
+                f"{mode}\n{datetime.now().strftime('%Y-%m-%d %H:%M')}\n{next_question}",
+                encoding='utf-8'
+            )
+
+            # 返回反馈和下一个问题
+            return f"""✅ {feedback}
+
+{next_question}
+
+💡 输入你的回答,或输入"退出"结束会话
+"""
+
+        except Exception as e:
+            # 发生错误时保存会话并返回
+            return f"❌ 处理回答时发生错误:{e}\n\n会话已自动保存。"
+
+    def _save_conversation_history(self, domain: str, mode: str, conversation: List[str], error: str = None) -> None:
+        """
+        保存对话历史
+
+        Args:
+            domain: 领域名称
+            mode: 模式
+            conversation: 对话记录
+            error: 错误信息(可选)
+        """
+        try:
+            session_path = self.file_manager.BASE_DIR / domain / "sessions"
+            timestamp = datetime.now().strftime("%Y-%m-%d %H-%M")
+
+            content = f"# 学习会话 - {domain}\n"
+            content += f"模式: {mode}\n"
+            content += f"时间: {timestamp}\n\n"
+
+            if conversation:
+                content += "\n".join(conversation)
+
+            if error:
+                content += f"\n\n错误: {error}"
+
+            # 保存会话
+            self.file_manager.save_session(domain, content)
+
+        except Exception:
+            pass  # 静默失败,避免干扰主流程
+
+    def _generate_first_question(self, plan: str, mode: str) -> str:
+        """
+        生成第一个问题
+
+        Args:
+            plan: 学习计划
+            mode: 模式(free/quiz)
+
+        Returns:
+            问题文本
+        """
+        if mode == "quiz":
+            # quiz 模式:使用 QuizGenerator
+            return self.quiz_generator.generate_question(plan, difficulty="easy")
+        else:
+            # free 模式:生成开放性问题
+            user_prompt = f"""基于以下学习计划,生成一个开放性的问题,开始对话:
+
+{plan[:2000]}
+
+问题应该:
+1. 从基础概念开始
+2. 引导用户思考和表达
+3. 不要太难,建立信心
+
+直接返回问题,不需要额外说明。
+"""
+
+            messages = [
+                {
+                    "role": "system",
+                    "content": "你是一个专业的学习教练,擅长通过提问引导学习。",
+                },
+                {"role": "user", "content": user_prompt},
+            ]
+
+            try:
+                if self.streaming:
+                    from utils.streaming import stream_response
+                    return stream_response(self.llm, messages)
+                else:
+                    return self.llm.invoke(messages).strip()
+            except Exception:
+                return "请简单描述一下你对这个主题的理解,以及你最想学习的部分是什么?"
+
+    def _generate_next_question(
+        self, plan: str, history: List[str], mode: str
+    ) -> str:
+        """
+        生成下一个问题(根据历史对话调整)
+
+        Args:
+            plan: 学习计划
+            history: 对话历史
+            mode: 模式
+
+        Returns:
+            问题文本
+        """
+        # 提取最后一个问题和回答(如果有)
+        if len(history) < 3:
+            return self._generate_first_question(plan, mode)
+
+        if mode == "quiz":
+            # quiz 模式:逐步增加难度
+            difficulty = min(1.0, 0.3 + len(history) * 0.1)
+            return self.quiz_generator.generate_question(plan, difficulty=difficulty)
+        else:
+            # free 模式:基于上下文生成问题
+            recent_context = "\n".join(history[-5:])
+
+            user_prompt = f"""基于以下对话历史,生成下一个问题:
+
+{recent_context}
+
+要求:
+1. 继续深入探讨
+2. 根据用户之前的回答调整方向
+3. 保持对话流畅性
+
+直接返回问题,不需要额外说明。
+"""
+
+            messages = [
+                {
+                    "role": "system",
+                    "content": "你是一个专业的学习教练,擅长通过对话引导深入学习。",
+                },
+                {"role": "user", "content": user_prompt},
+            ]
+
+            try:
+                if self.streaming:
+                    from utils.streaming import stream_response
+                    return stream_response(self.llm, messages)
+                else:
+                    return self.llm.invoke(messages).strip()
+            except Exception:
+                return "请继续分享你的想法,或者有什么具体的问题想讨论吗?"
+
+    def _generate_feedback(
+        self, question: str, answer: str, plan: str
+    ) -> str:
+        """
+        生成反馈
+
+        Args:
+            question: 问题
+            answer: 用户回答
+            plan: 学习计划
+
+        Returns:
+            反馈文本
+        """
+        user_prompt = f"""问题:{question}
+
+用户回答:{answer}
+
+参考计划:{plan[:1000]}
+
+生成友好的反馈(100字以内):
+1. 肯定正确的部分
+2. 指出需要改进的地方(温和地)
+3. 提供一个额外的知识点或建议
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个友善的学习教练,善于鼓励和引导。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            if self.streaming:
+                from utils.streaming import stream_response
+                return stream_response(self.llm, messages)
+            else:
+                return self.llm.invoke(messages).strip()
+        except Exception:
+            return "好的,谢谢你的回答。让我们继续深入探讨这个话题。"
+
+    def _evaluate_answer(
+        self, question: str, answer: str, plan: str
+    ) -> Dict[str, any]:
+        """
+        评估回答质量
+
+        Args:
+            question: 问题
+            answer: 回答
+            plan: 学习计划
+
+        Returns:
+            评估结果(包含 score, mastery_level, suggested_next)
+        """
+        user_prompt = f"""评估以下回答的质量(0-1分):
+
+问题:{question}
+
+回答:{answer}
+
+返回 JSON 格式:
+{{
+  "score": 0.8,
+  "mastery_level": "good/poor/medium",
+  "suggested_next": "increase/maintain/decrease"
+}}
+
+只返回 JSON,不需要其他内容。
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个教育评估专家,擅长评估学生的学习质量。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            response = self.llm.invoke(messages).strip()
+
+            # 尝试解析 JSON
+            # 简化实现:使用规则提取
+            return self._extract_evaluation(response)
+
+        except Exception:
+            # 降级:返回默认评估
+            return {
+                "score": 0.5,
+                "mastery_level": "medium",
+                "suggested_next": "maintain",
+            }
+
+    def _extract_evaluation(self, text: str) -> Dict[str, any]:
+        """
+        从文本中提取评估结果(简化版)
+
+        Args:
+            text: LLM 响应文本
+
+        Returns:
+            评估结果字典
+        """
+        # 简化实现:返回默认值
+        # 在真实场景中,应该使用更健壮的 JSON 解析
+        try:
+            # 尝试直接解析
+            return json.loads(text)
+        except:
+            # 失败则返回默认值
+            return {
+                "score": 0.5,
+                "mastery_level": "medium",
+                "suggested_next": "maintain",
+            }
+
+    def _summarize_session(self, conversation: List[str], domain: str) -> str:
+        """
+        总结会话
+
+        Args:
+            conversation: 对话历史
+            domain: 领域名称
+
+        Returns:
+            会话总结
+        """
+        content = "\n".join(conversation)
+
+        user_prompt = f"""总结以下学习会话(控制在200字以内):
+
+{content[:3000]}
+
+包括:
+1. 讨论的主题
+2. 用户掌握良好的知识点
+3. 需要复习的内容
+4. 下次学习的建议
+
+输出格式:
+## 会话总结
+
+**讨论主题:** ...
+
+**掌握情况:**
+- ...
+
+**需要复习:**
+- ...
+
+**下一步建议:**
+- ...
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个学习总结专家,擅长提炼关键信息。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            return self.llm.invoke(messages).strip()
+        except Exception:
+            return f"## 会话总结\n\n完成了 {domain} 领域的学习会话。\n继续加油!"

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/cli/__init__.py


+ 100 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/cli/repl.py

@@ -0,0 +1,100 @@
+# cli/repl.py
+"""REPL 循环实现"""
+
+from hello_agents import HelloAgentsLLM
+from core.main_agent import MainAgent
+from core.file_manager import FileManager
+from utils.logger import setup_logger
+
+
+def print_welcome():
+    """打印欢迎信息"""
+    print(
+        """
+╔══════════════════════════════════════════════════════════╗
+║                                                          ║
+║           🤖 Welcome to LearningAgent!                   ║
+║                                                          ║
+║              Your AI Learning Companion                  ║
+║                                                          ║
+╚══════════════════════════════════════════════════════════╝
+
+输入 /help 查看可用命令
+    """
+    )
+
+
+def print_goodbye():
+    """打印告别信息"""
+    print(
+        """
+╔══════════════════════════════════════════════════════════╗
+║                                                          ║
+║                  👋 Goodbye!                             ║
+║                                                          ║
+║              Keep Learning, Keep Growing!                ║
+║                                                          ║
+╚══════════════════════════════════════════════════════════╝
+    """
+    )
+
+
+def start_repl():
+    """
+    启动 REPL 循环
+    """
+    # 设置日志
+    logger = setup_logger("learning_agent")
+    logger.info("LearningAgent started")
+
+    # 初始化组件
+    try:
+        llm = HelloAgentsLLM()
+        file_manager = FileManager()
+        # REPL 始终是交互式环境,启用流式输出
+        agent = MainAgent(llm, file_manager, streaming=True)
+    except Exception as e:
+        print(f"❌ 初始化失败:{e}")
+        print("请检查配置文件(.env)和 API Key")
+        return
+
+    # 显示欢迎信息
+    print_welcome()
+
+    # REPL 循环
+    while True:
+        try:
+            # 获取用户输入
+            user_input = input("\n> ").strip()
+
+            # 空输入跳过
+            if not user_input:
+                continue
+
+            # 处理命令
+            result = agent.process_command(user_input)
+
+            # 检查是否退出
+            if result == "EXIT":
+                print_goodbye()
+                logger.info("LearningAgent exited normally")
+                break
+
+            # 显示结果
+            # 注意:如果 agent.streaming=True,流式输出已经打印到 stdout
+            # 这里只打印非流式的结果(如帮助信息、错误消息等)
+            if not agent.streaming:
+                print(result)
+
+        except KeyboardInterrupt:
+            print("\n\n👋 操作已取消")
+            continue
+
+        except Exception as e:
+            logger.error(f"Error in REPL: {e}", exc_info=True)
+            print(f"❌ 发生错误:{e}")
+            print("输入 /help 查看帮助,或 /exit 退出")
+
+
+if __name__ == "__main__":
+    start_repl()

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/core/__init__.py


+ 143 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/core/file_manager.py

@@ -0,0 +1,143 @@
+# core/file_manager.py
+"""文件管理器 - 统一管理 ~/.learningAgent/ 下的所有文件操作"""
+
+from pathlib import Path
+from datetime import datetime
+from typing import List
+from utils.exceptions import FileReadError, FileWriteError
+
+
+class FileManager:
+    """
+    统一管理 ~/.learningAgent/ 下的所有文件操作
+
+    Attributes:
+        BASE_DIR: 基础目录路径
+    """
+
+    BASE_DIR = Path.home() / ".learningAgent"
+
+    def __init__(self):
+        """初始化文件管理器,确保基础目录存在"""
+        self.ensure_structure()
+
+    def ensure_structure(self) -> None:
+        """确保基础目录结构存在"""
+        self.BASE_DIR.mkdir(exist_ok=True)
+
+    def create_domain(self, domain: str) -> None:
+        """
+        创建新的学习领域目录
+
+        Args:
+            domain: 领域名称
+        """
+        domain_path = self.BASE_DIR / domain
+        domain_path.mkdir(exist_ok=True)
+        (domain_path / "knowledge").mkdir(exist_ok=True)
+        (domain_path / "sessions").mkdir(exist_ok=True)
+
+        # 创建空的 summary 文件
+        (domain_path / "knowledge" / "knowledge_summary.md").write_text(
+            "# 知识总结\n\n> 暂无知识笔记\n", encoding="utf-8"
+        )
+        (domain_path / "sessions" / "session_summary.md").write_text(
+            "# 学习历程\n\n> 暂无学习记录\n", encoding="utf-8"
+        )
+
+    def save_plan(self, domain: str, plan_content: str) -> None:
+        """
+        保存学习计划
+
+        Args:
+            domain: 领域名称
+            plan_content: 计划内容(markdown格式)
+        """
+        plan_path = self.BASE_DIR / domain / "plan.md"
+        try:
+            plan_path.write_text(plan_content, encoding="utf-8")
+        except Exception as e:
+            raise FileWriteError(f"无法保存学习计划:{e}")
+
+    def save_knowledge(self, domain: str, filename: str, content: str) -> None:
+        """
+        保存知识笔记
+
+        Args:
+            domain: 领域名称
+            filename: 文件名
+            content: 文件内容
+        """
+        knowledge_path = self.BASE_DIR / domain / "knowledge" / filename
+        try:
+            knowledge_path.write_text(content, encoding="utf-8")
+        except Exception as e:
+            raise FileWriteError(f"无法保存知识笔记:{e}")
+
+    def save_session(self, domain: str, session_content: str) -> Path:
+        """
+        保存单次学习会话记录
+
+        Args:
+            domain: 领域名称
+            session_content: 会话内容
+
+        Returns:
+            保存的文件路径
+        """
+        date = datetime.now().strftime("%Y-%m-%d")
+        time = datetime.now().strftime("%H-%M")
+        session_path = self.BASE_DIR / domain / "sessions" / f"session_{date}_{time}.md"
+
+        try:
+            session_path.write_text(session_content, encoding="utf-8")
+        except Exception as e:
+            raise FileWriteError(f"无法保存会话记录:{e}")
+
+        return session_path
+
+    def read_plan(self, domain: str) -> str:
+        """
+        读取学习计划
+
+        Args:
+            domain: 领域名称
+
+        Returns:
+            计划内容
+
+        Raises:
+            FileNotFoundError: 如果计划不存在
+        """
+        plan_path = self.BASE_DIR / domain / "plan.md"
+        if not plan_path.exists():
+            raise FileNotFoundError(f"学习计划不存在:{domain}")
+
+        try:
+            return plan_path.read_text(encoding="utf-8")
+        except Exception as e:
+            raise FileReadError(f"无法读取学习计划:{e}")
+
+    def domain_exists(self, domain: str) -> bool:
+        """
+        检查领域是否存在
+
+        Args:
+            domain: 领域名称
+
+        Returns:
+            是否存在
+        """
+        return (self.BASE_DIR / domain).exists()
+
+    def list_domains(self) -> List[str]:
+        """
+        列出所有学习领域
+
+        Returns:
+            领域名称列表
+        """
+        if not self.BASE_DIR.exists():
+            return []
+
+        return [d.name for d in self.BASE_DIR.iterdir() if d.is_dir()]

+ 513 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/core/main_agent.py

@@ -0,0 +1,513 @@
+# core/main_agent.py
+"""主 Agent - 协调层,负责意图识别和路由"""
+
+from hello_agents import SimpleAgent, HelloAgentsLLM
+from core.file_manager import FileManager
+
+
+class MainAgent(SimpleAgent):
+    """
+    系统协调者,负责意图识别和路由
+
+    职责:
+    - 接收用户输入
+    - 识别用户意图(create/add/vibe/summary/help/exit)
+    - 路由到相应的子 Agent 或处理器
+    - 管理基本命令(help, list, exit)
+    """
+
+    # 意图关键词映射(按优先级排序,更具体的在前)
+    INTENT_KEYWORDS = {
+        "create": [
+            "/create",
+            "创建计划",
+            "制定学习路径",
+            "我想学",
+            "我想学习",
+            "学习计划",  # 移除单独的"学习"以避免冲突
+        ],
+        "add": ["/add", "添加笔记", "记录知识", "添加知识"],
+        "vibe": ["/vibe", "开始学习", "互动学习", "练习", "考察"],
+        "summary": ["/summary", "学习进度", "总结", "评估"],
+        "help": ["/help", "帮助", "help"],
+        "list": ["/list", "列出所有", "所有领域", "列表"],
+        "exit": ["/exit", "退出", "quit", "exit"],
+    }
+
+    def __init__(self, llm: HelloAgentsLLM, file_manager: FileManager, streaming: bool = None):
+        """
+        初始化主 Agent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            file_manager: FileManager 实例
+            streaming: 是否启用流式输出(None = 自动检测)
+        """
+        system_prompt = """
+        你是 LearningAgent 学习助手的主界面。
+
+        支持的功能:
+        1. 创建学习计划 (/create, "我想学习")
+        2. 添加知识笔记 (/add, "添加笔记")
+        3. 开始互动学习 (/vibe, "开始学习")
+        4. 查看学习总结 (/summary, "总结")
+        5. 显示帮助 (/help, "帮助")
+        6. 列出所有领域 (/list)
+        7. 退出程序 (/exit, "退出")
+
+        识别用户意图后,调用相应的功能。
+        如果意图模糊,询问用户确认。
+        """
+
+        self.llm = llm
+        self.file_manager = file_manager
+
+        # 添加流式输出支持
+        from utils.streaming import should_stream
+        self.streaming = should_stream(streaming)
+
+        # 会话状态管理
+        self.active_session = None  # {"domain": str, "mode": str, "round": int}
+
+        # 使用父类初始化
+        super().__init__("MainAgent", llm, system_prompt)
+
+    def _identify_intent(self, user_input: str) -> str:
+        """
+        识别用户意图
+
+        Args:
+            user_input: 用户输入
+
+        Returns:
+            意图类型(create/add/vibe/summary/help/list/exit/unknown)
+        """
+        user_input_lower = user_input.lower().strip()
+
+        # 检查每个意图的关键词
+        for intent, keywords in self.INTENT_KEYWORDS.items():
+            for keyword in keywords:
+                if keyword.lower() in user_input_lower:
+                    return intent
+
+        return "unknown"
+
+    def process_command(self, user_input: str) -> str:
+        """
+        处理用户命令
+
+        Args:
+            user_input: 用户输入
+
+        Returns:
+            处理结果
+        """
+        # 检查是否有活跃的 vibe 会话
+        if self.active_session is not None:
+            # 检查是否要退出会话
+            if self._is_exit_command(user_input):
+                return self._end_vibe_session()
+            # 否则继续对话
+            return self._continue_vibe_session(user_input)
+
+        # 正常命令处理
+        intent = self._identify_intent(user_input)
+
+        if intent == "create":
+            return self._route_to_create_plan(user_input)
+        elif intent == "add":
+            return self._route_to_add_knowledge(user_input)
+        elif intent == "vibe":
+            return self._route_to_vibe_learning(user_input)
+        elif intent == "summary":
+            return self._route_to_summary(user_input)
+        elif intent == "help":
+            return self._show_help()
+        elif intent == "list":
+            return self._list_domains()
+        elif intent == "exit":
+            return "EXIT"
+        elif intent == "unknown":
+            return "❓ 未识别的命令。输入 /help 查看帮助。"
+
+    def _route_to_create_plan(self, input_data: str) -> str:
+        """
+        路由到 CreatePlanAgent
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            执行结果
+        """
+        from agents.create_plan_agent import CreatePlanAgent
+
+        try:
+            # 去掉命令前缀,只保留参数部分
+            # 支持: "/create 数学", "创建计划 数学", "我想学习数学"
+            clean_input = input_data
+
+            # 去掉 /create 前缀
+            for prefix in ["/create", "/CREATE"]:
+                if clean_input.startswith(prefix):
+                    clean_input = clean_input[len(prefix) :].strip()
+                    break
+
+            # 如果是自然语言形式,保留原样
+            # 例如: "我想学习数学", "创建一个学习计划"
+            if not clean_input or clean_input == input_data:
+                clean_input = input_data
+
+            agent = CreatePlanAgent(self.llm, streaming=self.streaming)
+            return agent.run(clean_input)
+        except Exception as e:
+            return f"❌ 创建学习计划失败:{e}"
+
+    def _route_to_add_knowledge(self, input_data: str) -> str:
+        """
+        路由到 AddKnowledgeProcessor
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            执行结果
+        """
+        from processors.add_knowledge import AddKnowledgeProcessor
+
+        try:
+            # 去掉命令前缀,只保留参数部分
+            # 支持: "/add 数学 算法", "添加笔记", "记录知识"
+            clean_input = input_data
+
+            # 去掉 /add 前缀
+            for prefix in ["/add", "/ADD"]:
+                if clean_input.startswith(prefix):
+                    clean_input = clean_input[len(prefix) :].strip()
+                    break
+
+            # 如果是自然语言形式,需要询问用户输入内容和领域
+            if not clean_input or clean_input == input_data or len(clean_input) < 10:
+                return self._ask_for_knowledge_input()
+
+            # 解析输入(格式:领域 内容)
+            # 例如: "机器学习 决策树算法"
+            parts = clean_input.split(maxsplit=1)
+            if len(parts) == 2:
+                domain, content = parts
+                domain = domain.strip()
+                content = content.strip()
+            else:
+                # 无法解析,询问用户
+                return self._ask_for_knowledge_input()
+
+            processor = AddKnowledgeProcessor(self.llm, self.file_manager)
+            return processor.add(domain, content)
+        except Exception as e:
+            return f"❌ 添加知识失败:{e}"
+
+    def _ask_for_knowledge_input(self) -> str:
+        """
+        询问用户知识内容和领域
+
+        Returns:
+            提示信息
+        """
+        return """📝 添加知识笔记
+
+请按以下格式输入:
+
+> /add <领域> <知识内容>
+
+例如:
+> /add 机器学习 # 决策树算法简介
+
+或者直接输入内容(会询问领域):
+> /add 决策树是一种监督学习算法,用于分类和回归...
+
+💡 提示:长内容可以先在编辑器中准备好,然后一次性粘贴。
+"""
+
+    def _route_to_vibe_learning(self, input_data: str) -> str:
+        """
+        路由到 VibeLearningAgent
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            执行结果
+        """
+        from agents.vibe_learning_agent import VibeLearningAgent
+
+        try:
+            # 去掉命令前缀,只保留参数部分
+            # 支持: "/vibe Python", "/vibe Python --mode quiz"
+            clean_input = input_data
+
+            # 去掉 /vibe 前缀
+            for prefix in ["/vibe", "/VIBE", "/Vibe"]:
+                if clean_input.startswith(prefix):
+                    clean_input = clean_input[len(prefix):].strip()
+                    break
+
+            # 如果是自然语言形式,询问用户
+            if not clean_input or len(clean_input.split()) < 1:
+                return self._ask_for_vibe_input()
+
+            # 解析输入
+            # 格式: <领域> [--mode <mode>]
+            parts = clean_input.split()
+            domain = parts[0].strip()
+
+            # 检查是否有模式选项
+            mode = "free"  # 默认模式
+            if "--mode" in parts:
+                mode_idx = parts.index("--mode")
+                if mode_idx + 1 < len(parts):
+                    mode = parts[mode_idx + 1].strip().lower()
+                    if mode not in ["free", "quiz"]:
+                        return "❌ 无效的模式。请使用 --mode free 或 --mode quiz"
+
+            # 启动学习会话
+            agent = VibeLearningAgent(self.llm, self.file_manager,
+                                     streaming=self.streaming)
+            result = agent.start_session(domain, mode=mode)
+
+            # 设置活跃会话
+            self.active_session = {
+                "domain": domain,
+                "mode": mode,
+                "round": 1,
+                "agent": agent,
+                "streaming": self.streaming  # 保存 streaming 设置
+            }
+
+            return result
+
+        except Exception as e:
+            return f"❌ 启动互动学习失败:{e}"
+
+    def _route_to_summary(self, input_data: str) -> str:
+        """
+        路由到 SummaryAgent
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            执行结果
+        """
+        from agents.summary_agent import SummaryAgent
+
+        try:
+            # 去掉命令前缀,只保留参数部分
+            # 支持: "/summary Python", "总结学习进度"
+            clean_input = input_data
+
+            # 去掉 /summary 前缀
+            for prefix in ["/summary", "/SUMMARY", "/Summary"]:
+                if clean_input.startswith(prefix):
+                    clean_input = clean_input[len(prefix):].strip()
+                    break
+
+            # 如果是自然语言形式,询问用户
+            if not clean_input or len(clean_input.split()) < 1:
+                return self._ask_for_summary_input()
+
+            # 解析输入
+            # 格式: <领域>
+            domain = clean_input.split()[0].strip()
+
+            # 生成学习总结
+            agent = SummaryAgent(self.llm, self.file_manager,
+                                streaming=self.streaming)
+            return agent.run(domain)
+
+        except Exception as e:
+            return f"❌ 生成学习总结失败:{e}"
+
+    def _is_exit_command(self, user_input: str) -> bool:
+        """
+        检查是否是退出命令
+
+        Args:
+            user_input: 用户输入
+
+        Returns:
+            是否是退出命令
+        """
+        exit_keywords = ["/exit", "exit", "退出", "quit", "/quit", "结束", "完成"]
+        return user_input.strip().lower() in exit_keywords
+
+    def _end_vibe_session(self) -> str:
+        """
+        结束 vibe 会话
+
+        Returns:
+            结束消息
+        """
+        domain = self.active_session["domain"]
+        mode = self.active_session["mode"]
+        rounds = self.active_session["round"]
+
+        # 清除会话状态
+        self.active_session = None
+
+        return f"""✅ 会话已结束
+
+📁 领域: {domain}
+📝 模式: {mode}
+💬 对话轮数: {rounds}
+
+💡 上下文已保存。输入 /help 查看可用命令。
+"""
+
+    def _continue_vibe_session(self, user_input: str) -> str:
+        """
+        继续 vibe 会话
+
+        Args:
+            user_input: 用户回答
+
+        Returns:
+            反馈和下一个问题
+        """
+        try:
+            agent = self.active_session["agent"]
+            domain = self.active_session["domain"]
+            mode = self.active_session["mode"]
+
+            # 确保 streaming 设置一致
+            agent.streaming = self.active_session.get("streaming", agent.streaming)
+
+            # 继续对话(获取反馈和下一个问题)
+            result = agent.continue_session(domain, user_input, mode)
+
+            # 增加轮次计数
+            self.active_session["round"] += 1
+
+            return result
+
+        except Exception as e:
+            # 发生错误时清除会话状态
+            self.active_session = None
+            return f"❌ 对话过程中发生错误:{e}\n\n会话已结束。"
+
+    def _ask_for_vibe_input(self) -> str:
+        """
+        询问用户互动学习参数
+
+        Returns:
+            提示信息
+        """
+        return """📝 互动学习
+
+请按以下格式输入:
+
+> /vibe <领域> [--mode <模式>]
+
+例如:
+> /vibe Python
+> /vibe Python --mode quiz
+
+模式说明:
+- free: 自由对话模式(默认)
+- quiz: 测验模式
+
+💡 提示:需要先使用 /create 创建学习计划。
+"""
+
+    def _ask_for_summary_input(self) -> str:
+        """
+        询问用户学习总结参数
+
+        Returns:
+            提示信息
+        """
+        return """📝 学习进度总结
+
+请按以下格式输入:
+
+> /summary <领域>
+
+例如:
+> /summary Python
+> /summary 机器学习
+
+💡 提示:需要先使用 /create 创建学习计划。
+"""
+
+    def _show_help(self) -> str:
+        """显示帮助信息"""
+        return """
+# 🤖 LearningAgent 帮助
+
+## 命令列表
+
+### 创建学习计划
+- `/create <领域>` - 创建学习计划
+  例:`/create 数学`
+  例:`/create https://github.com/user/project`
+  例:`/create ~/paper.pdf`
+
+- 自然语言:`我想学习数学`
+
+### 添加知识笔记 ✨ 新功能
+- `/add <领域> <内容>` - 添加知识笔记
+  例:`/add 机器学习 # 决策树算法简介`
+  例:`/add Python 这是一个列表推导式的例子...`
+
+- 文件输入:`/add ~/notes.md`
+
+- 自然语言:`添加笔记` `记录知识`
+
+### 开始互动学习 ✨ 新功能
+- `/vibe <领域>` - 开始互动学习
+  例:`/vibe Python`
+  例:`/vibe Python --mode quiz`
+
+- 模式说明:
+  - `free`: 自由对话模式(默认)
+  - `quiz`: 测验模式
+
+- 退出会话:输入"退出"、"exit"或"/exit"即可随时结束
+
+- 自然语言:`开始学习数学` `练习一下`
+
+### 查看学习总结 ✨ 新功能
+- `/summary <领域>` - 查看学习总结
+  例:`/summary Python`
+  例:`/summary 机器学习`
+
+- 自然语言:`总结学习进度` `评估我的水平`
+
+### 其他命令
+- `/list` - 列出所有学习领域
+- `/help` - 显示帮助
+- `/exit` 或 `exit` - 退出程序
+
+## 提示
+- 支持命令前缀(如 `/create`)和自然语言(如"我想学习")
+- 添加知识时,内容会自动分析、分类并打标签
+- 随时输入 `/help` 查看帮助
+"""
+
+    def _list_domains(self) -> str:
+        """列出所有学习领域"""
+        domains = self.file_manager.list_domains()
+
+        if not domains:
+            return "📭 还没有创建任何学习领域。\n使用 `/create` 创建第一个学习计划。"
+
+        domain_list = "\n".join([f"- {domain}" for domain in domains])
+        return f"# 📚 学习领域\n\n{domain_list}\n\n共 {len(domains)} 个领域"
+
+    def list_domains(self) -> list:
+        """
+        获取所有领域列表
+
+        Returns:
+            领域名称列表
+        """
+        return self.file_manager.list_domains()

+ 244 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/core/summary_manager.py

@@ -0,0 +1,244 @@
+# core/summary_manager.py
+"""摘要更新管理器 - 混合策略:<5个文件完全重写,≥5个增量更新"""
+
+from pathlib import Path
+from typing import List
+from hello_agents import HelloAgentsLLM
+from config import Config
+
+
+class SummaryManager:
+    """
+    管理知识摘要和会话摘要的更新
+
+    使用混合策略:
+    - 文件数 < 5:完全重写摘要
+    - 文件数 ≥ 5:增量更新摘要
+
+    Attributes:
+        fm: FileManager 实例
+        llm: HelloAgentsLLM 实例
+    """
+
+    def __init__(self, file_manager):
+        """
+        初始化摘要管理器
+
+        Args:
+            file_manager: FileManager 实例
+        """
+        self.fm = file_manager
+        self.llm = HelloAgentsLLM()
+
+    def update_knowledge_summary(self, domain: str, new_file: str) -> None:
+        """
+        更新 knowledge_summary.md
+
+        Args:
+            domain: 领域名称
+            new_file: 新添加的文件名
+        """
+        domain_path = self.fm.BASE_DIR / domain
+        knowledge_dir = domain_path / "knowledge"
+        summary_path = knowledge_dir / "knowledge_summary.md"
+
+        # 统计文件数(排除 summary.md)
+        existing_files: List[Path] = list(knowledge_dir.glob("*.md"))
+        file_count = len(
+            [f for f in existing_files if f.name != "knowledge_summary.md"]
+        )
+
+        if file_count < Config.SUMMARY_FULL_REWRITE_THRESHOLD:
+            self._full_rewrite_knowledge_summary(domain, knowledge_dir, summary_path)
+        else:
+            self._incremental_update_knowledge_summary(domain, new_file, summary_path)
+
+    def _full_rewrite_knowledge_summary(
+        self, domain: str, knowledge_dir: Path, summary_path: Path
+    ) -> None:
+        """
+        完全重写知识摘要
+
+        Args:
+            domain: 领域名称
+            knowledge_dir: 知识目录
+            summary_path: 摘要文件路径
+        """
+        # 读取所有知识文件
+        all_files: List[Path] = [
+            f for f in knowledge_dir.glob("*.md") if f.name != "knowledge_summary.md"
+        ]
+        all_content = []
+        for file in all_files:
+            content = file.read_text(encoding="utf-8")
+            all_content.append(f"## {file.stem}\n{content}\n")
+
+        # 让 LLM 生成压缩摘要
+        user_prompt = f"""以下是 {domain} 领域的所有知识笔记,请生成一个结构化的总结摘要:
+
+{''.join(all_content)}
+
+要求:
+1. 按主题分类组织
+2. 提取核心概念和关键知识点
+3. 保持结构化(markdown格式)
+4. 控制在原来内容的20%长度
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个知识总结助手,擅长提取核心概念并生成结构化摘要。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            summary = self.llm.invoke(messages)
+            summary_path.write_text(summary, encoding="utf-8")
+        except Exception:
+            # 如果 LLM 调用失败,使用简单的合并
+            fallback_summary = f"# {domain} 知识总结\n\n" + "\n".join(all_content)
+            summary_path.write_text(fallback_summary, encoding="utf-8")
+
+    def _incremental_update_knowledge_summary(
+        self, domain: str, new_file: str, summary_path: Path
+    ) -> None:
+        """
+        增量更新知识摘要
+
+        Args:
+            domain: 领域名称
+            new_file: 新文件名
+            summary_path: 摘要文件路径
+        """
+        # 读取当前摘要和新文件
+        current_summary = summary_path.read_text(encoding="utf-8")
+        new_content = (self.fm.BASE_DIR / domain / "knowledge" / new_file).read_text(
+            encoding="utf-8"
+        )
+
+        # 让 LLM 合并
+        user_prompt = f"""当前摘要:
+{current_summary}
+
+新增内容:
+{new_content}
+
+请将新增内容整合到摘要中,保持结构化和简洁性。
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个知识总结助手,擅长整合新内容到现有摘要中。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            updated_summary = self.llm.invoke(messages)
+            summary_path.write_text(updated_summary, encoding="utf-8")
+        except Exception:
+            # 如果 LLM 调用失败,使用简单追加
+            updated_summary = (
+                current_summary + f"\n\n## {Path(new_file).stem}\n{new_content}"
+            )
+            summary_path.write_text(updated_summary, encoding="utf-8")
+
+    def update_session_summary(self, domain: str, new_session_content: str) -> None:
+        """
+        更新 session_summary.md
+
+        Args:
+            domain: 领域名称
+            new_session_content: 新会话内容
+        """
+        domain_path = self.fm.BASE_DIR / domain
+        sessions_dir = domain_path / "sessions"
+        summary_path = sessions_dir / "session_summary.md"
+
+        # 统计文件数
+        existing_files: List[Path] = list(sessions_dir.glob("session_*.md"))
+        file_count = len(
+            [f for f in existing_files if not f.name.startswith("session_summary")]
+        )
+
+        if file_count < Config.SUMMARY_FULL_REWRITE_THRESHOLD:
+            self._full_rewrite_session_summary(domain, sessions_dir, summary_path)
+        else:
+            self._incremental_update_session_summary(new_session_content, summary_path)
+
+    def _full_rewrite_session_summary(
+        self, domain: str, sessions_dir: Path, summary_path: Path
+    ) -> None:
+        """
+        完全重写会话摘要
+        """
+        all_sessions: List[Path] = [
+            f
+            for f in sessions_dir.glob("session_*.md")
+            if not f.name.startswith("session_summary")
+        ]
+        all_content = []
+        for file in all_sessions:
+            content = file.read_text(encoding="utf-8")
+            all_content.append(f"## {file.stem}\n{content}\n")
+
+        user_prompt = f"""以下是 {domain} 领域的所有学习会话记录,请生成一个压缩的总结:
+
+{''.join(all_content)}
+
+要求:
+1. 提取关键学习点
+2. 记录进步轨迹
+3. 识别需要复习的内容
+4. 控制在原来内容的30%长度
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个学习历程总结助手,擅长提取关键学习点和进步轨迹。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            summary = self.llm.invoke(messages)
+            summary_path.write_text(summary, encoding="utf-8")
+        except Exception:
+            fallback_summary = f"# {domain} 学习历程\n\n" + "\n".join(all_content)
+            summary_path.write_text(fallback_summary, encoding="utf-8")
+
+    def _incremental_update_session_summary(
+        self, new_session_content: str, summary_path: Path
+    ) -> None:
+        """
+        增量更新会话摘要
+        """
+        current_summary = summary_path.read_text(encoding="utf-8")
+
+        user_prompt = f"""当前总结:
+{current_summary}
+
+新会话记录:
+{new_session_content}
+
+请将新会话整合到总结中。
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个学习历程总结助手,擅长整合新的学习会话到总结中。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            updated_summary = self.llm.invoke(messages)
+            summary_path.write_text(updated_summary, encoding="utf-8")
+        except Exception:
+            updated_summary = current_summary + f"\n\n{new_session_content}"
+            summary_path.write_text(updated_summary, encoding="utf-8")

+ 16 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/main.py

@@ -0,0 +1,16 @@
+# main.py
+"""LearningAgent 主入口文件"""
+
+import sys
+from cli.repl import start_repl
+
+
+def main():
+    """
+    主函数
+    """
+    start_repl()
+
+
+if __name__ == "__main__":
+    main()

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/processors/__init__.py


+ 402 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/processors/add_knowledge.py

@@ -0,0 +1,402 @@
+# processors/add_knowledge.py
+"""知识添加处理器 - 使用 LLM 分析、分类并保存知识"""
+
+import json
+import os
+from datetime import datetime
+from pathlib import Path
+from typing import Dict, List, Optional
+from hello_agents import HelloAgentsLLM
+from core.file_manager import FileManager
+from core.summary_manager import SummaryManager
+
+
+class AddKnowledgeProcessor:
+    """
+    知识添加处理器
+
+    功能:
+    - 识别输入类型(文本/文件/URL)
+    - 使用 LLM 分析内容
+    - 智能分类和打标签
+    - 提取关键概念
+    - 生成文件名
+    - 保存到 knowledge 目录
+    - 更新 knowledge_summary.md
+    """
+
+    def __init__(self, llm: HelloAgentsLLM, file_manager: FileManager):
+        """
+        初始化 AddKnowledgeProcessor
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            file_manager: FileManager 实例
+        """
+        self.llm = llm
+        self.file_manager = file_manager
+        self.summary_manager = SummaryManager(file_manager)
+
+    def _identify_input_type(self, input_data: str) -> str:
+        """
+        识别输入类型
+
+        Args:
+            input_data: 用户输入
+
+        Returns:
+            输入类型(text/file/url)
+        """
+        # 检查 URL
+        if input_data.startswith("http://") or input_data.startswith("https://"):
+            return "url"
+
+        # 检查文件路径
+        if (
+            input_data.startswith("~")
+            or input_data.startswith("/")
+            or input_data.startswith("./")
+        ):
+            return "file"
+
+        # 默认为文本
+        return "text"
+
+    def _read_file(self, file_path: str) -> str:
+        """
+        读取文件内容
+
+        Args:
+            file_path: 文件路径
+
+        Returns:
+            文件内容
+        """
+        # 处理 ~ 路径
+        if file_path.startswith("~"):
+            file_path = os.path.expanduser(file_path)
+
+        with open(file_path, "r", encoding="utf-8") as f:
+            return f.read()
+
+    def _analyze_content(self, content: str, domain: str) -> Dict[str, any]:
+        """
+        使用 LLM 分析内容
+
+        Args:
+            content: 知识内容
+            domain: 领域名称
+
+        Returns:
+            分析结果字典,包含:
+            - category: 分类
+            - tags: 标签列表
+            - key_concepts: 关键概念列表
+            - summary: 摘要
+        """
+        user_prompt = f"""请分析以下知识内容并提取关键信息:
+
+【领域】
+{domain}
+
+【知识内容】
+{content[:2000]}
+
+请提供以下信息(JSON格式):
+{{
+  "category": "分类(如:算法、概念、工具、实践等)",
+  "tags": ["标签1", "标签2", "标签3"],
+  "key_concepts": ["核心概念1", "核心概念2", "核心概念3"],
+  "summary": "一句话摘要(50字以内)"
+}}
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个知识管理专家,擅长分析学习内容并提取关键信息、分类和标签。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            response = self.llm.invoke(messages)
+
+            # 尝试解析 JSON(简化实现:使用规则提取)
+            return self._extract_metadata_from_text(response)
+        except Exception:
+            # 降级:使用规则分析
+            return {
+                "category": self._classify_content(content, domain),
+                "tags": self._extract_tags_from_content(content),
+                "key_concepts": self._extract_concepts_from_content(content),
+                "summary": content[:100] + "..." if len(content) > 100 else content,
+                "domain": domain,  # 添加 domain 字段
+            }
+
+    def _extract_metadata_from_text(self, text: str) -> Dict[str, any]:
+        """
+        从文本中提取元数据(简化版)
+
+        Args:
+            text: LLM 响应文本
+
+        Returns:
+            元数据字典
+        """
+        # 简化实现:基于规则提取
+        lines = text.strip().split("\n")
+
+        category = "通用"
+        tags = []
+        key_concepts = []
+        summary = ""
+
+        for line in lines:
+            line = line.strip()
+            if "分类" in line or "category" in line.lower():
+                category = line.split(":")[-1].split(":")[-1].strip()
+            elif "标签" in line or "tags" in line.lower():
+                tags = [
+                    tag.strip(" \"'[]{}")
+                    for tag in line.split(":")[-1].split(":")[-1].split(",")
+                ]
+            elif "概念" in line or "concepts" in line.lower():
+                key_concepts = [
+                    c.strip(" \"'[]{}")
+                    for c in line.split(":")[-1].split(":")[-1].split(",")
+                ]
+            elif "摘要" in line or "summary" in line.lower():
+                summary = line.split(":")[-1].split(":")[-1].strip()
+
+        return {
+            "category": category if category else "通用",
+            "tags": [t for t in tags if t],
+            "key_concepts": [c for c in key_concepts if c],
+            "summary": summary if summary else "知识笔记",
+            "domain": domain,  # 添加 domain 字段
+        }
+
+    def _extract_tags_from_content(self, content: str) -> List[str]:
+        """
+        从内容中提取标签(基于关键词)
+
+        Args:
+            content: 内容文本
+
+        Returns:
+            标签列表
+        """
+        # 常见技术关键词
+        keywords = [
+            "算法",
+            "数据结构",
+            "机器学习",
+            "深度学习",
+            "Python",
+            "JavaScript",
+            "TypeScript",
+            "Java",
+            "框架",
+            "库",
+            "工具",
+            "API",
+            "前端",
+            "后端",
+            "全栈",
+            "数据库",
+            "理论",
+            "实践",
+            "教程",
+            "示例",
+        ]
+
+        found = []
+        content_lower = content.lower()
+        for keyword in keywords:
+            if keyword.lower() in content_lower:
+                found.append(keyword)
+
+        return found[:5]  # 最多5个标签
+
+    def _extract_concepts_from_content(self, content: str) -> List[str]:
+        """
+        从内容中提取关键概念
+
+        Args:
+            content: 内容文本
+
+        Returns:
+            关键概念列表
+        """
+        # 提取以 # 开头的标题作为概念
+        concepts = []
+        for line in content.split("\n"):
+            line = line.strip()
+            if line.startswith("#"):
+                # 去掉 # 符号和空格
+                concept = line.lstrip("#").strip()
+                if concept and len(concept) < 50:  # 限制长度
+                    concepts.append(concept)
+
+        return concepts[:5]  # 最多5个概念
+
+    def _generate_filename(self, title: str, category: str = "") -> str:
+        """
+        生成文件名
+
+        Args:
+            title: 标题
+            category: 分类(可选)
+
+        Returns:
+            文件名(带扩展名)
+        """
+        # 提取第一句话作为文件名
+        if len(title) > 50:
+            title = title[:50]
+
+        # 清理特殊字符
+        title = title.replace(" ", "-")
+        title = "".join(c for c in title if c.isalnum() or c in "-_")
+
+        # 添加时间戳
+        timestamp = datetime.now().strftime("%Y%m%d-%H%M")
+
+        if category:
+            base_name = f"{timestamp}-{category}-{title}"
+        else:
+            base_name = f"{timestamp}-{title}"
+
+        return f"{base_name}.md"  # 添加 .md 扩展名
+
+    def _save_knowledge(
+        self, domain: str, content: str, metadata: Dict[str, any]
+    ) -> Path:
+        """
+        保存知识笔记
+
+        Args:
+            domain: 领域名称
+            content: 知识内容
+            metadata: 元数据
+
+        Returns:
+            保存的文件路径
+        """
+        # 生成文件名(_generate_filename 已包含 .md 扩展名)
+        title = content.split("\n")[0].lstrip("#").strip()
+        filename = self._generate_filename(title, metadata.get("category", ""))
+
+        # 添加元数据到内容
+        full_content = f"""# {title}
+
+> **分类**: {metadata.get('category', '通用')}
+> **标签**: {', '.join(metadata.get('tags', []))}
+> **添加时间**: {datetime.now().strftime('%Y-%m-%d %H:%M')}
+
+---
+
+{content}
+
+## 关键概念
+{chr(10).join(f"- {c}" for c in metadata.get('key_concepts', []))}
+
+## 摘要
+{metadata.get('summary', '无')}
+"""
+
+        # 保存文件
+        self.file_manager.save_knowledge(domain, filename, full_content)
+
+        # 返回完整路径
+        return self.file_manager.BASE_DIR / domain / "knowledge" / filename
+
+    def _classify_content(self, content: str, domain: str) -> str:
+        """
+        分类内容
+
+        Args:
+            content: 内容
+            domain: 领域
+
+        Returns:
+            分类名称
+        """
+        # 基于规则的简单分类
+        content_lower = content.lower()
+
+        if any(
+            word in content_lower for word in ["算法", "algorithm", "方法", "method"]
+        ):
+            return "算法"
+        elif any(
+            word in content_lower for word in ["概念", "concept", "原理", "principle"]
+        ):
+            return "概念"
+        elif any(
+            word in content_lower
+            for word in ["工具", "tool", "框架", "framework", "库", "library"]
+        ):
+            return "工具"
+        elif any(
+            word in content_lower
+            for word in ["实践", "practice", "案例", "case", "项目", "project"]
+        ):
+            return "实践"
+        elif any(
+            word in content_lower for word in ["教程", "tutorial", "指南", "guide"]
+        ):
+            return "教程"
+        else:
+            return "通用"
+
+    def add(self, domain: str, input_data: str, input_type: str = None) -> str:
+        """
+        添加知识
+
+        Args:
+            domain: 领域名称
+            input_data: 输入数据(文本/文件路径/URL)
+            input_type: 输入类型(可选,自动识别)
+
+        Returns:
+            执行结果
+        """
+        # 识别输入类型
+        if not input_type:
+            input_type = self._identify_input_type(input_data)
+
+        # 获取内容
+        if input_type == "text":
+            content = input_data
+        elif input_type == "file":
+            try:
+                content = self._read_file(input_data)
+            except Exception as e:
+                return f"❌ 读取文件失败:{e}"
+        elif input_type == "url":
+            # 简化实现:提示用户复制内容
+            content = f"# URL 知识\n\n来源:{input_data}\n\n请手动添加内容..."
+        else:
+            return f"❌ 未知的输入类型:{input_type}"
+
+        # 分析内容
+        metadata = self._analyze_content(content, domain)
+
+        # 保存知识
+        try:
+            file_path = self._save_knowledge(domain, content, metadata)
+
+            # 更新摘要
+            self.summary_manager.update_knowledge_summary(domain, file_path.name)
+
+            return f"""✅ 知识已添加
+
+📁 保存位置: {domain}/knowledge/{file_path.name}
+📊 分类: {metadata.get('category', '通用')}
+🏷️  标签: {', '.join(metadata.get('tags', []))}
+"""
+
+        except Exception as e:
+            return f"❌ 添加知识失败:{e}"

+ 15 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/requirements.txt

@@ -0,0 +1,15 @@
+hello-agents==0.2.8
+PyPDF2>=3.0.0
+python-docx>=0.8.11
+markdown>=3.4.0
+PyGithub>=1.59
+requests>=2.28.0
+python-dateutil>=2.8.0
+huggingface-hub>=0.17.0
+python-dotenv>=1.0.0
+pytest>=7.0.0
+pytest-cov>=4.0.0
+pytest-mock>=3.10.0
+black>=23.0.0
+flake8>=6.0.0
+mypy>=1.0.0

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/__init__.py


+ 294 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/paper_analyzer.py

@@ -0,0 +1,294 @@
+# specialist/paper_analyzer.py
+"""PDF 论文分析专家"""
+
+import os
+from pathlib import Path
+from typing import Dict, List
+import PyPDF2
+from hello_agents import HelloAgentsLLM
+
+
+class PaperAnalyzerAgent:
+    """
+    PDF 论文分析专家
+
+    功能:
+    - 读取 PDF 论文
+    - 提取标题和摘要
+    - 识别核心概念
+    - 推断前置知识
+    - 确定研究领域
+    """
+
+    def __init__(self, llm: HelloAgentsLLM):
+        """
+        初始化 PaperAnalyzerAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+        """
+        self.llm = llm
+
+    def _extract_title_from_path(self, file_path: str) -> str:
+        """
+        从文件路径提取论文标题
+
+        Args:
+            file_path: PDF 文件路径
+
+        Returns:
+            论文标题
+        """
+        # 处理 ~ 路径
+        if file_path.startswith("~"):
+            file_path = os.path.expanduser(file_path)
+
+        # 获取文件名(去掉扩展名)
+        filename = Path(file_path).stem
+
+        # 将连字符和下划线替换为空格
+        title = filename.replace("-", " ").replace("_", " ")
+
+        return title
+
+    def _extract_text_from_pdf(self, file_path: str) -> str:
+        """
+        从 PDF 提取文本
+
+        Args:
+            file_path: PDF 文件路径
+
+        Returns:
+            提取的文本内容
+        """
+        # 处理 ~ 路径
+        if file_path.startswith("~"):
+            file_path = os.path.expanduser(file_path)
+
+        try:
+            with open(file_path, "rb") as file:
+                reader = PyPDF2.PdfReader(file)
+                text = ""
+
+                # 提取前3页的内容(通常包含摘要和引言)
+                max_pages = min(3, len(reader.pages))
+                for i in range(max_pages):
+                    page = reader.pages[i]
+                    text += page.extract_text() + "\n"
+
+                return text
+        except Exception as e:
+            raise IOError(f"无法读取 PDF 文件:{e}")
+
+    def _extract_keywords_from_text(self, text: str) -> List[str]:
+        """
+        从文本中提取关键词
+
+        Args:
+            text: 论文文本
+
+        Returns:
+            关键词列表
+        """
+        # 学术领域常见关键词
+        academic_keywords = [
+            # 深度学习/机器学习
+            "Neural Network",
+            "Deep Learning",
+            "Transformer",
+            "Attention",
+            "CNN",
+            "RNN",
+            "LSTM",
+            "Backpropagation",
+            "Gradient Descent",
+            "Optimization",
+            # 自然语言处理
+            "NLP",
+            "Language Model",
+            "Tokenization",
+            "Embedding",
+            "BERT",
+            "GPT",
+            # 计算机视觉
+            "Computer Vision",
+            "Image Processing",
+            "Convolution",
+            "Feature Extraction",
+            # 其他
+            "Algorithm",
+            "Data Structure",
+            "Complexity",
+            "Statistics",
+            "Probability",
+        ]
+
+        found_keywords = []
+        text_lower = text.lower()
+
+        for keyword in academic_keywords:
+            if keyword.lower() in text_lower:
+                found_keywords.append(keyword)
+
+        return found_keywords
+
+    def _identify_prerequisites(self, keywords: List[str]) -> List[str]:
+        """
+        根据关键词推断前置知识
+
+        Args:
+            keywords: 关键词列表
+
+        Returns:
+            前置知识列表
+        """
+        # 前置知识映射
+        prereq_map = {
+            "Deep Learning": ["Machine Learning", "Python", "Linear Algebra"],
+            "Transformer": ["Attention Mechanism", "Sequence Models"],
+            "Neural Network": ["Calculus", "Linear Algebra", "Probability"],
+            "CNN": ["Image Processing", "Linear Algebra"],
+            "RNN": ["Sequence Models", "Calculus"],
+            "NLP": ["Machine Learning", "Statistics", "Python"],
+            "Computer Vision": ["Linear Algebra", "Probability", "Python"],
+        }
+
+        prerequisites = []
+        for keyword in keywords:
+            if keyword in prereq_map:
+                prerequisites.extend(prereq_map[keyword])
+
+        # 去重
+        return list(set(prerequisites))
+
+    def _analyze_with_llm(self, title: str, text: str) -> Dict[str, any]:
+        """
+        使用 LLM 深度分析论文
+
+        Args:
+            title: 论文标题
+            text: 论文文本
+
+        Returns:
+            分析结果字典
+        """
+        user_prompt = f"""请分析以下学术论文并提取学习相关信息:
+
+【论文标题】
+{title}
+
+【论文内容(前1000字)】
+{text[:1000]}
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个学术教育专家,擅长分析学术论文并提取学习相关信息。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            response = self.llm.invoke(messages)
+            # 简化实现:返回基于规则的分析结果
+            keywords = self._extract_keywords_from_text(text)
+            prerequisites = self._identify_prerequisites(keywords)
+
+            return {
+                "domain": self._infer_domain_from_keywords(keywords),
+                "core_concepts": keywords[:5],  # 前5个关键词
+                "prerequisites": prerequisites,
+                "title": title,
+                "learning_difficulty": "高级",
+                "estimated_weeks": 8,
+            }
+        except Exception:
+            # 降级:使用基于规则的分析
+            keywords = self._extract_keywords_from_text(text)
+            prerequisites = self._identify_prerequisites(keywords)
+
+            return {
+                "domain": self._infer_domain_from_keywords(keywords),
+                "core_concepts": keywords[:5],
+                "prerequisites": prerequisites,
+                "title": title,
+                "learning_difficulty": "高级",
+                "estimated_weeks": 8,
+            }
+
+    def _infer_domain_from_keywords(self, keywords: List[str]) -> str:
+        """
+        根据关键词推断研究领域
+
+        Args:
+            keywords: 关键词列表
+
+        Returns:
+            研究领域
+        """
+        if not keywords:
+            return "general"
+
+        keyword_lower = " ".join(keywords).lower()
+
+        # 领域映射
+        if any(
+            kw in keyword_lower
+            for kw in ["transformer", "attention", "nlp", "language", "bert", "gpt"]
+        ):
+            return "natural-language-processing"
+        elif any(
+            kw in keyword_lower
+            for kw in ["cnn", "image", "vision", "computer", "processing"]
+        ):
+            return "computer-vision"
+        elif any(
+            kw in keyword_lower
+            for kw in ["neural", "deep", "learning", "network", "backpropagation"]
+        ):
+            return "deep-learning"
+        elif any(
+            kw in keyword_lower for kw in ["machine", "learning", "algorithm", "model"]
+        ):
+            return "machine-learning"
+        else:
+            return "general"
+
+    def analyze(self, pdf_path: str) -> Dict[str, any]:
+        """
+        分析 PDF 论文
+
+        Args:
+            pdf_path: PDF 文件路径
+
+        Returns:
+            分析结果字典,包含:
+            - domain: 研究领域
+            - title: 论文标题
+            - core_concepts: 核心概念列表
+            - prerequisites: 前置知识列表
+            - learning_difficulty: 学习难度
+            - estimated_weeks: 估计学习周数
+        """
+        # 提取标题
+        title = self._extract_title_from_path(pdf_path)
+
+        # 提取文本
+        try:
+            text = self._extract_text_from_pdf(pdf_path)
+        except IOError:
+            # 如果无法读取 PDF,使用基于路径的分析
+            return {
+                "domain": "general",
+                "title": title,
+                "core_concepts": [],
+                "prerequisites": [],
+                "learning_difficulty": "高级",
+                "estimated_weeks": 8,
+            }
+
+        # 使用 LLM 深度分析
+        result = self._analyze_with_llm(title, text)
+
+        return result

+ 129 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/quiz_generator.py

@@ -0,0 +1,129 @@
+# specialist/quiz_generator.py
+"""测验生成器 - 根据学习计划生成测验题"""
+
+import json
+from typing import List, Union
+from hello_agents import HelloAgentsLLM
+
+
+class QuizGeneratorAgent:
+    """
+    测验生成 Agent
+
+    功能:
+    - 基于学习计划生成问题
+    - 支持不同难度级别(easy/medium/hard 或 0.0-1.0)
+    - 生成单个或多个问题
+    """
+
+    def __init__(self, llm: HelloAgentsLLM):
+        """
+        初始化 QuizGeneratorAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+        """
+        self.llm = llm
+
+    def generate_question(
+        self, plan: str, difficulty: Union[str, float] = "medium"
+    ) -> str:
+        """
+        生成单个问题
+
+        Args:
+            plan: 学习计划内容
+            difficulty: 难度级别
+                - str: "easy", "medium", "hard"
+                - float: 0.0-1.0(0.0=最简单,1.0=最难)
+
+        Returns:
+            生成的问题文本
+        """
+        # 转换难度级别
+        difficulty_level = self._normalize_difficulty(difficulty)
+
+        # 构建提示词
+        user_prompt = f"""请基于以下学习计划,生成一个{difficulty_level}难度的问题:
+
+【学习计划】
+{plan[:2000]}
+
+要求:
+1. 问题应该清晰、具体
+2. 难度符合 {difficulty_level} 级别
+3. 直接返回问题,不需要额外说明
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个教育专家,擅长根据学习内容生成合适的测验问题。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            response = self.llm.invoke(messages)
+            return response.strip()
+        except Exception as e:
+            # 降级:返回默认问题
+            return f"请简要描述你从学习计划中学到的核心内容(难度:{difficulty_level})"
+
+    def generate_questions(
+        self,
+        plan: str,
+        count: int = 3,
+        difficulty: Union[str, float] = "medium",
+    ) -> List[str]:
+        """
+        生成多个问题
+
+        Args:
+            plan: 学习计划内容
+            count: 问题数量
+            difficulty: 难度级别
+
+        Returns:
+            问题列表
+        """
+        questions = []
+
+        for i in range(count):
+            # 稍微调整每个问题的难度,增加多样性
+            if isinstance(difficulty, float):
+                # 在基础难度上浮动 ±0.1
+                adjusted_difficulty = max(0.0, min(1.0, difficulty + (i - 1) * 0.1))
+            else:
+                adjusted_difficulty = difficulty
+
+            question = self.generate_question(plan, adjusted_difficulty)
+            questions.append(question)
+
+        return questions
+
+    def _normalize_difficulty(self, difficulty: Union[str, float]) -> str:
+        """
+        标准化难度级别
+
+        Args:
+            difficulty: 难度(str 或 float)
+
+        Returns:
+            标准化的难度描述
+        """
+        if isinstance(difficulty, float):
+            if difficulty < 0.3:
+                return "简单"
+            elif difficulty < 0.7:
+                return "中等"
+            else:
+                return "困难"
+        else:
+            # 映射字符串到中文
+            mapping = {
+                "easy": "简单",
+                "medium": "中等",
+                "hard": "困难",
+            }
+            return mapping.get(difficulty.lower(), "中等")

+ 285 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/specialist/repo_analyzer.py

@@ -0,0 +1,285 @@
+# specialist/repo_analyzer.py
+"""GitHub 仓库分析专家"""
+
+import re
+from typing import Dict, List, Optional
+import requests
+from hello_agents import HelloAgentsLLM
+
+
+class RepoAnalyzerAgent:
+    """
+    GitHub 仓库分析专家
+
+    功能:
+    - 从 GitHub URL 提取仓库信息
+    - 获取项目基本信息(描述、语言、stars等)
+    - 获取并分析 README 内容
+    - 识别技术栈
+    - 推断前置知识要求
+    """
+
+    GITHUB_API_BASE = "https://api.github.com"
+
+    def __init__(self, llm: HelloAgentsLLM, github_token: Optional[str] = None):
+        """
+        初始化 RepoAnalyzerAgent
+
+        Args:
+            llm: HelloAgentsLLM 实例
+            github_token: GitHub API Token(可选,用于提高速率限制)
+        """
+        self.llm = llm
+        self.github_token = github_token
+        self.headers = {}
+        if github_token:
+            self.headers["Authorization"] = f"token {github_token}"
+
+    def _extract_repo_info(self, url: str) -> tuple[str, str]:
+        """
+        从 GitHub URL 提取 owner 和 repo 名称
+
+        Args:
+            url: GitHub URL(如 https://github.com/vuejs/core)
+
+        Returns:
+            (owner, repo) 元组
+        """
+        # 去掉 .git 后缀
+        url = url.rstrip(".git")
+
+        # 提取 owner 和 repo
+        parts = url.rstrip("/").split("/")
+        if len(parts) >= 2:
+            owner = parts[-2]
+            repo = parts[-1]
+            return owner, repo
+
+        raise ValueError(f"无法解析 GitHub URL: {url}")
+
+    def _fetch_repo_info(self, owner: str, repo: str) -> Dict:
+        """
+        获取仓库基本信息
+
+        Args:
+            owner: 仓库所有者
+            repo: 仓库名称
+
+        Returns:
+            仓库信息字典
+        """
+        url = f"{self.GITHUB_API_BASE}/repos/{owner}/{repo}"
+        response = requests.get(url, headers=self.headers, timeout=10)
+        response.raise_for_status()
+        return response.json()
+
+    def _fetch_readme(self, owner: str, repo: str) -> Optional[str]:
+        """
+        获取 README 内容
+
+        Args:
+            owner: 仓库所有者
+            repo: 仓库名称
+
+        Returns:
+            README 文本内容,如果不存在则返回 None
+        """
+        try:
+            url = f"{self.GITHUB_API_BASE}/repos/{owner}/{repo}/readme"
+            response = requests.get(url, headers=self.headers, timeout=10)
+            if response.status_code == 200:
+                data = response.json()
+                # README 内容是 base64 编码的
+                import base64
+
+                content = base64.b64decode(data["content"]).decode("utf-8")
+                return content
+        except Exception:
+            pass
+        return None
+
+    def _extract_tech_stack_from_text(self, text: str) -> List[str]:
+        """
+        从文本中提取技术栈关键词
+
+        Args:
+            text: 文本内容
+
+        Returns:
+            技术栈列表
+        """
+        # 常见技术关键词
+        tech_keywords = [
+            "React",
+            "Vue",
+            "Angular",
+            "Svelte",
+            "TypeScript",
+            "JavaScript",
+            "Python",
+            "Java",
+            "Go",
+            "Rust",
+            "Node.js",
+            "Django",
+            "Flask",
+            "FastAPI",
+            "Express",
+            "TensorFlow",
+            "PyTorch",
+            "Keras",
+            "Docker",
+            "Kubernetes",
+            "MongoDB",
+            "PostgreSQL",
+            "MySQL",
+            "Redis",
+            "TailwindCSS",
+            "Bootstrap",
+            "CSS",
+            "HTML",
+        ]
+
+        found_techs = []
+        text_lower = text.lower()
+
+        for tech in tech_keywords:
+            if tech.lower() in text_lower:
+                found_techs.append(tech)
+
+        return found_techs
+
+    def _analyze_with_llm(
+        self, repo_info: Dict, readme: Optional[str]
+    ) -> Dict[str, any]:
+        """
+        使用 LLM 深度分析仓库
+
+        Args:
+            repo_info: 仓库基本信息
+            readme: README 内容(可选)
+
+        Returns:
+            分析结果字典
+        """
+        # 构建分析提示
+        repo_name = repo_info.get("name", "unknown")
+        description = repo_info.get("description", "")
+        language = repo_info.get("language", "")
+        topics = repo_info.get("topics", [])
+
+        user_prompt = f"""请分析以下 GitHub 仓库并提取学习相关信息:
+
+【仓库名称】
+{repo_name}
+
+【描述】
+{description}
+
+【主要语言】
+{language}
+
+【主题标签】
+{', '.join(topics) if topics else '无'}
+
+"""
+
+        if readme:
+            user_prompt += f"""
+【README 内容】
+{readme[:2000]}  # 限制长度
+"""
+
+        user_prompt += """
+请提供以下信息(JSON格式):
+{
+  "domain": "学习领域(如 web-development, data-science 等)",
+  "tech_stack": ["技术1", "技术2", "..."],
+  "prerequisites": ["前置知识1", "前置知识2", "..."],
+  "learning_difficulty": "初级/中级/高级",
+  "estimated_weeks": 学习所需周数(整数)
+}
+"""
+
+        messages = [
+            {
+                "role": "system",
+                "content": "你是一个技术教育专家,擅长分析开源项目并提取学习相关信息。",
+            },
+            {"role": "user", "content": user_prompt},
+        ]
+
+        try:
+            response = self.llm.invoke(messages)
+            # 简化实现:返回基本信息(实际应该解析 LLM 返回的 JSON)
+            return {
+                "domain": repo_name.lower().replace("-", " "),
+                "tech_stack": self._extract_tech_stack_from_text(
+                    description + " " + language
+                ),
+                "prerequisites": [],
+                "learning_difficulty": "中级",
+                "estimated_weeks": 4,
+            }
+        except Exception:
+            # 降级:使用基于规则的分析
+            return {
+                "domain": repo_name.lower().replace("-", " "),
+                "tech_stack": [language] if language else [],
+                "prerequisites": [],
+                "learning_difficulty": "中级",
+                "estimated_weeks": 4,
+            }
+
+    def analyze(self, github_url: str) -> Dict[str, any]:
+        """
+        分析 GitHub 仓库
+
+        Args:
+            github_url: GitHub 仓库 URL
+
+        Returns:
+            分析结果字典,包含:
+            - domain: 学习领域
+            - tech_stack: 技术栈列表
+            - prerequisites: 前置知识列表
+            - description: 项目描述
+            - language: 主要语言
+            - stars: Star 数量
+        """
+        # 提取仓库信息
+        owner, repo = self._extract_repo_info(github_url)
+
+        # 获取基本信息
+        repo_info = self._fetch_repo_info(owner, repo)
+
+        # 获取 README
+        readme = self._fetch_readme(owner, repo)
+
+        # 提取技术栈(基于规则)
+        tech_stack = []
+        if repo_info.get("language"):
+            tech_stack.append(repo_info["language"])
+
+        if readme:
+            tech_stack.extend(self._extract_tech_stack_from_text(readme))
+
+        # 去重
+        tech_stack = list(set(tech_stack))
+
+        # 使用 LLM 深度分析(如果可用)
+        llm_analysis = self._analyze_with_llm(repo_info, readme)
+
+        # 合并结果
+        result = {
+            "domain": llm_analysis.get("domain", repo.lower().replace("-", " ")),
+            "tech_stack": tech_stack,
+            "prerequisites": llm_analysis.get("prerequisites", []),
+            "description": repo_info.get("description", ""),
+            "language": repo_info.get("language", ""),
+            "stars": repo_info.get("stargazers_count", 0),
+            "learning_difficulty": llm_analysis.get("learning_difficulty", "中级"),
+            "estimated_weeks": llm_analysis.get("estimated_weeks", 4),
+        }
+
+        return result

+ 0 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/utils/__init__.py


+ 52 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/utils/error_handlers.py

@@ -0,0 +1,52 @@
+"""错误处理装饰器和工具函数"""
+
+import logging
+from functools import wraps
+from typing import Callable, Any
+from utils.exceptions import (
+    LearningAgentError,
+    DomainNotFoundError,
+    FileReadError,
+    FileWriteError,
+    LLMError,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def handle_errors(func: Callable) -> Callable:
+    """
+    统一错误处理装饰器
+
+    捕获异常并返回友好的错误消息
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs) -> Any:
+        try:
+            return func(*args, **kwargs)
+
+        except DomainNotFoundError as e:
+            return f"❌ 错误:{e}\n请先使用 /create 创建学习计划。"
+
+        except FileReadError as e:
+            return f"❌ {e}\n请检查文件路径和权限。"
+
+        except FileWriteError as e:
+            return f"❌ {e}\n请检查磁盘空间和权限。"
+
+        except LLMError as e:
+            return f"❌ {e}\n请稍后重试或检查配置。"
+
+        except KeyboardInterrupt:
+            return "\n\n👋 操作已取消"
+
+        except LearningAgentError as e:
+            logger.error(f"LearningAgent error in {func.__name__}: {e}")
+            return f"❌ {e}"
+
+        except Exception as e:
+            logger.error(f"Unexpected error in {func.__name__}: {e}", exc_info=True)
+            return f"❌ 发生未知错误:{e}\n请查看日志或联系开发者。"
+
+    return wrapper

+ 43 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/utils/exceptions.py

@@ -0,0 +1,43 @@
+"""LearningAgent 自定义异常类"""
+
+
+class LearningAgentError(Exception):
+    """基础异常类"""
+
+    pass
+
+
+class DomainNotFoundError(LearningAgentError):
+    """领域不存在"""
+
+    def __init__(self, domain: str):
+        self.domain = domain
+        super().__init__(f"领域 '{domain}' 不存在。请先使用 /create 创建学习计划。")
+
+
+class FileReadError(LearningAgentError):
+    """文件读取失败"""
+
+    def __init__(self, message: str):
+        super().__init__(f"文件读取失败:{message}")
+
+
+class FileWriteError(LearningAgentError):
+    """文件写入失败"""
+
+    def __init__(self, message: str):
+        super().__init__(f"文件写入失败:{message}")
+
+
+class LLMError(LearningAgentError):
+    """LLM 调用失败"""
+
+    def __init__(self, message: str):
+        super().__init__(f"AI服务错误:{message}")
+
+
+class InvalidInputError(LearningAgentError):
+    """无效输入"""
+
+    def __init__(self, message: str):
+        super().__init__(f"无效输入:{message}")

+ 47 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/utils/logger.py

@@ -0,0 +1,47 @@
+"""日志配置"""
+
+import logging
+import sys
+from pathlib import Path
+from config import Config
+
+
+def setup_logger(name: str = "learning_agent") -> logging.Logger:
+    """
+    配置并返回日志记录器
+
+    Args:
+        name: 日志记录器名称
+
+    Returns:
+        配置好的日志记录器
+    """
+    logger = logging.getLogger(name)
+    logger.setLevel(getattr(logging, Config.LOG_LEVEL.upper()))
+
+    # 避免重复添加 handler
+    if logger.handlers:
+        return logger
+
+    # 控制台 handler
+    console_handler = logging.StreamHandler(sys.stdout)
+    console_handler.setLevel(logging.INFO)
+
+    # 文件 handler
+    log_dir = Path.home() / ".learningAgent" / "logs"
+    log_dir.mkdir(parents=True, exist_ok=True)
+    file_handler = logging.FileHandler(log_dir / "app.log")
+    file_handler.setLevel(logging.DEBUG)
+
+    # 格式化
+    formatter = logging.Formatter(
+        "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+        datefmt="%Y-%m-%d %H:%M:%S",
+    )
+    console_handler.setFormatter(formatter)
+    file_handler.setFormatter(formatter)
+
+    logger.addHandler(console_handler)
+    logger.addHandler(file_handler)
+
+    return logger

+ 61 - 0
Co-creation-projects/Yixiang-Wu-LearningAgent/utils/streaming.py

@@ -0,0 +1,61 @@
+# utils/streaming.py
+"""流式输出工具函数"""
+
+import sys
+from typing import List
+from hello_agents import HelloAgentsLLM
+
+
+def should_stream(streaming: bool = None) -> bool:
+    """
+    判断是否应该使用流式输出
+
+    Args:
+        streaming: 手动指定的流式输出设置(None = 自动检测)
+
+    Returns:
+        是否使用流式输出
+    """
+    if streaming is None:
+        # 自动检测:交互式终端使用流式输出
+        return sys.stdout.isatty()
+    return streaming
+
+
+def stream_response(llm: HelloAgentsLLM, messages: List[dict], silent: bool = False) -> str:
+    """
+    执行流式 LLM 调用并打印结果
+
+    Args:
+        llm: HelloAgentsLLM 实例
+        messages: LLM 消息列表
+        silent: 是否静默模式(不打印输出)
+
+    Returns:
+        完整的响应文本
+    """
+    full_response = ""
+    previous_length = 0
+
+    try:
+        for chunk in llm.stream_invoke(messages):
+            # chunk 是累积式的,只打印新增部分
+            if len(chunk) > previous_length:
+                new_content = chunk[previous_length:]
+                if not silent:
+                    print(new_content, end='', flush=True)
+                previous_length = len(chunk)
+
+            # 保存完整响应
+            full_response = chunk
+
+        if not silent:
+            print()  # 换行
+
+        return full_response
+
+    except Exception as e:
+        # 如果流式输出失败,降级到普通输出
+        if not silent:
+            print(f"\n[流式输出失败,使用普通输出: {e}]")
+        return llm.invoke(messages)