GLM-4.6 + Claude Code 构建教育 Agent

176 阅读8分钟

本文源自 GLM Coding 大师作品征集赛 的获奖作品,原文作者为 封一

数万开发者严选的编码搭子 GLM Coding Plan

推荐语:

从框架选型到系统集成,从技能重构到网站生成,本文完整展示了一个教育智能体的工程化实现路径。

项目基于 GLM-4.6 与 Claude Agent SDK 打造模块化架构,实现技能按需加载与高效协同,并融合 Firecrawl、Context7、WebSearchPrime 等工具,构建出实时、可信的知识采集与组织体系。

在此基础上,系统通过 模板化生成与TUI交互界面,将知识内容自动转化为结构化网站,实现了从数据到课程的全流程自动化,是一篇兼具工程深度与实用价值的教育技术实战指南。


项目背景

在信息爆炸的时代,传统MOOC网站的课程更新速度远远跟不上知识迭代的步伐。许多新兴技术、前沿话题往往散布在YouTube、Bilibili、Medium、技术博客等各个平台上,缺乏系统性的整理和结构化。为了解决这一痛点,我开发了OpenEDU Agent——一个基于GLM4.6+Claude Agent SDK构建的AI教育智能体,可以理解为”AI版的Coursera”。本项目由glm4.6+claudecode撰写,且最终智能体的模型也是GLM-4.6

OpenEDU Agent能够根据用户的学习需求,主动从全网搜集、整理、分析相关教学资源,并生成结构化的在线课程网站,为学习者提供最新的、个性化的学习体验。


技术架构

核心框架选择

我们选择Claude Agent SDK作为核心智能体框架,主要基于以下考虑:

  • 开箱即用:内置文件操作、代码理解等基础能力
  • 高度可扩展:支持自定义工具、技能和工作流
  • 官方Skills系统:近期推出的可复用工作流功能
  • 强大的推理能力:结合Claude模型提供高质量的内容分析

系统架构图

graph TB
    A[用户输入] --> B[Claude Agent SDK]
    B --> C[技能系统]
    C --> D[深度研究技能]
    C --> E[内容组织技能]
    C --> F[网站生成技能]

    B --> G[MCP工具集成]
    G --> H[Firecrawl爬取]
    G --> I[Context7文档]
    G --> J[WebSearch搜索]

    D --> H
    D --> I
    D --> J
    E --> K[结构化数据]
    F --> L[静态网站]

    B --> M[TUI用户界面]
    M --> N[会话管理]
    M --> O[实时流式响应]

核心功能实现

  1. Claude Agent SDK集成

基础配置

我们通过ClaudeAgentOptions来配置智能体的核心行为:

def _build_options(series_id: str) -> ClaudeAgentOptions:
    return ClaudeAgentOptions(
        cwd=workspace.parent,  # 设置项目目录
        setting_sources=["user", "project"],  # 加载技能系统
        allowed_tools=[
            "Skill",  # 启用技能工具
            "mcp__firecrawl",
            "mcp__context7",
            "mcp__web-search-prime",
            "WebFetch",
            "Read",
            "Write",
            "Edit",
        ],
        system_prompt=(
            "You are OpenEDU Agent, the educational knowledge curator. "
            "Gather, organize, and explain learning resources. "
            # ... 详细的系统提示
        ),
    )

MCP工具集成

我们集成了多个专业工具来增强搜索和内容获取能力:

mcp_servers={
    "firecrawl": {
        "command": "npx",
        "args": ["-y", "firecrawl-mcp"],
        "env": {"FIRECRAWL_API_KEY": FIRECRAWL_API_KEY},
    },
    "context7": {
        "type": "http",
        "url": "https://mcp.context7.com/mcp",
        "headers": {"CONTEXT7_API_KEY": CONTEXT7_API_KEY},
    },
    "web-search-prime": {
        "type": "http",
        "url": "https://open.bigmodel.cn/api/mcp/web_search_prime/mcp",
        "headers": {"Authorization": f"Bearer {WEB_SEARCH_API_KEY}"},
    },
}

2. 技能系统实现

技能架构设计

我们摒弃了传统的"将技能内容追加到系统提示"的做法,采用了Claude Agent SDK官方推荐的文件系统技能架构:

# 旧的错误做法(24KB的技能内容被追加到每个请求)
system_prompt = base_prompt + format_skills_for_system_prompt()  # ❌

# 新的正确做法 - 技能作为独立的文件系统资源
return ClaudeAgentOptions(
    setting_sources=["user", "project"],  # ✅ 从.claude/skills/加载技能
    allowed_tools=["Skill"],  # ✅ 启用技能工具
)

技能文件结构

.claude/skills/
├── research/
│   ├── SKILL.md
│   └── skill.json
├── organize-course-artifact/
│   ├── SKILL.md
│   └── skill.json
└── generate-website/
    ├── SKILL.md
    ├── skill.json
    ├── template-styles.css
    └── template-script.js

深度研究技能

research/SKILL.md定义了深度研究的工作流程:

---
name: research
description: Perform comprehensive research on any topic using multiple search strategies and sources
---

# Research Skill

When asked to research a topic, follow this comprehensive workflow:

## Phase 1: Search Strategy Planning
1. Break down the research topic into key components
2. Identify primary search terms and alternative phrasings
3. Plan multiple search approaches (web search, academic sources, video content)

## Phase 2: Multi-Source Information Gathering
1. Use firecrawl_search for comprehensive web search
2. Use webSearchPrime for Chinese language sources
3. Fetch detailed content from promising URLs using WebFetch
4. Cross-reference information across multiple sources

内容组织技能

organize-course-artifact/SKILL.md实现信息的结构化处理:

---
name: organize-course-artifact
description: Transform research findings into structured educational content
---

## Content Organization Workflow

### Step 1: Quality Assessment
- Evaluate source credibility and relevance
- Check content freshness and accuracy
- Identify conflicting information

### Step 2: Knowledge Structuring
- Create learning objectives hierarchy
- Organize content into logical modules
- Establish prerequisite relationships

### Step 3: Curriculum Design
- Design learning progression
- Create assessment checkpoints
- Add practical exercises and examples

网站生成技能

generate-website/SKILL.md负责生成类似Coursera的在线课程网站:

---
name: generate-website
description: Transform structured curricula into interactive learning websites
---

## Website Generation Process

### Template Requirements
CRITICAL - You MUST:
- Use provided template files exactly as specified
- Copy templates byte-for-byte without modifications
- NEVER create custom CSS/JS from scratch
- Follow OpenEDU design patterns

### Implementation Steps
1. Read template-styles.css and template-script.js
2. Generate responsive HTML structure
3. Embed YouTube videos and interactive elements
4. Add navigation and progress tracking

图示为skills中的模板,由GLM-4.6生成

3. 实时流式TUI界面

TUI架构

我们使用Textual框架构建了功能丰富的终端用户界面:

class PrometheusApp(App):
    """主应用程序类"""

    BINDINGS = [
        Binding("ctrl+c", "quit", "Quit", show=True),
        Binding("ctrl+n", "new_chat", "New Chat", show=True),
        Binding("ctrl+s", "show_sessions", "Sessions", show=True),
        Binding("escape", "interrupt_stream", "Interrupt", show=False),
    ]

    is_streaming = reactive(False)
    current_series_id: str = None
    current_session_id: str = None
    current_client = None  # SDK客户端引用

消息流处理

实现实时流式响应的核心逻辑:

async def _stream_response(self, message: str, messages_container: MessagesContainer, todo_bar: TodoBar):
    """处理Claude响应的流式数据"""
    accumulated_text = ""
    active_tools = {}

    try:
        async for event in self.current_client.agent_event_stream(
            series_id=self.current_series_id,
            session_id=self.current_session_id,
            user_message=message
        ):
            if event.type == "assistant_message":
                # 处理助手消息
                for block in event.content:
                    if block.type == "text":
                        accumulated_text += block.text
                        messages_container.update_last_message(accumulated_text)

            elif event.type == "tool_use":
                # 处理工具使用
                tool_name = block.name
                tool_id = block.id

                # 创建工具状态标记
                marker = f"<!--TOOL_{tool_id}-->"
                accumulated_text += f"\n\n🔧 {marker}[RUNNING] `{display_name}`\n"

            elif event.type == "tool_result":
                # 处理工具结果
                if not block.is_error:
                    accumulated_text = accumulated_text.replace(f"{marker}[RUNNING]", "[DONE] ✅")
                else:
                    accumulated_text = accumulated_text.replace(f"{marker}[RUNNING]", "[FAILED] ❌")

    finally:
        self.set_status_ready()

状态指示器

(此处GLM-4.6一开始没有明确理解我的意思,错误的把对话框做了颜色处理)

实现直观的工作状态显示:

def set_status_working(self):
    """显示工作状态"""
    status = self.query_one("#status-indicator", Static)
    status.update("●  Working...")
    status.set_class(False, "ready")
    status.set_class(True, "working")

    # 为助手消息添加黄色边框
    messages_container = self.query_one("#messages-container", MessagesContainer)
    if messages_container.children:
        last_msg = messages_container.children[-1]
        if isinstance(last_msg, MessageWidget) and last_msg.role == "assistant":
            last_msg.add_class("working")
# CSS样式
MessageWidget.assistant.working {
    border: thick $warning;
}

4. 会话管理系统

会话持久化

实现完整的会话管理功能,包括创建、恢复、分支:

class SessionListScreen(Screen):
    """会话列表界面"""

    def compose(self) -> ComposeResult:
        yield Header("Session Management")

        with Vertical():
            yield Static("Available Sessions:")
            yield self.session_list

            with Horizontal():
                yield Button("Resume", id="resume-btn")
                yield Button("Fork", id="fork-btn")
                yield Button("Delete", id="delete-btn")
                yield Button("New Session", id="new-btn")

    async def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "fork-btn":
            # 创建会话分支
            await self.fork_selected_session()
        elif event.button.id == "resume-btn":
            # 恢复选中会话
            await self.resume_selected_session()

会话加载策略

智能的会话加载逻辑:

def load_or_create_session(self):
    """加载现有会话或创建新会话"""
    try:
        # 尝试加载现有会话
        existing_sessions = storage.load_series_list()
        if existing_sessions:
            # 恢复最近的会话
            latest_series = existing_sessions[0]
            self.current_series_id = latest_series["id"]

            sessions = storage.load_sessions(self.current_series_id)
            if sessions:
                latest_session = sessions[0]
                self.current_session_id = latest_session["id"]
                self.current_session_title = latest_session["title"]
                return

    except Exception:
        pass

    # 创建新会话
    self.create_new_session()

5. 任务管理集成

TodoWrite工具拦截

实现对TodoWrite工具的专门处理:

def _handle_todo_write(self, tool_use: dict, content: str) -> None:
    """处理TodoWrite工具调用"""
    args = tool_use.get("input", {})
    todos = args.get("todos", [])

    if todos:
        # 更新UI中的任务列表
        todo_bar = self.query_one("#todo-bar", TodoBar)
        todo_bar.update_todos(todos)

        # 保存到存储
        storage.save_todos(
            self.current_series_id,
            self.current_session_id,
            todos
        )

任务状态追踪

实时显示任务进度:

class TodoBar(Static):
    """任务进度显示组件"""

    def update_todos(self, todos: list) -> None:
        """更新任务列表"""
        if not todos:
            self.update("No tasks in progress")
            return

        # 计算进度统计
        total = len(todos)
        completed = sum(1 for todo in todos if todo.get("status") == "completed")
        in_progress = sum(1 for todo in todos if todo.get("status") == "in_progress")

        # 生成任务显示文本
        status_text = f"To Do: {total} | ✅ Completed: {completed} | 🔄 In Progress: {in_progress}"
        self.update(status_text)

关键技术创新

1. 技能系统重构

问题:传统方法将24KB的技能内容追加到每个系统提示中,造成巨大的性能开销。

解决方案:采用官方文件系统技能架构:

# Before: ❌ 错误方法
system_prompt = base_prompt + 24787  # 24KB的技能内容

# After: ✅ 正确方法
return ClaudeAgentOptions(
    setting_sources=["user", "project"],  # 从.claude/skills/加载
    allowed_tools=["Skill"],  # 按需调用技能
)

效果:系统提示减少24KB,技能按需调用,性能大幅提升。

2. 智能搜索策略

多源搜索集成:

SEARCH_AGENT_NAME = "search-specialist"

def create_search_agent():
    return AgentDefinition(
        description="Specialized scout for deep web research",
        prompt=(
            "You are the OpenEDU Agent Search Specialist. "
            "## Tool Preferences (Priority Order):\n"
            "1. **Primary Search**: firecrawl_search - Best quality\n"
            "2. **Chinese Sources**: webSearchPrime - For Chinese content\n"
            "3. **Content Fetching**: WebFetch - Extract page content\n"
        ),
        tools=["mcp__firecrawl__", "mcp__web-search-prime__", "WebFetch"],
    )

搜索质量优化:

  • Firecrawl:高质量网页爬取和内容提取
  • WebSearchPrime:中文内容专项搜索
  • Context7:技术文档和专业资源检索
  • 自动去重和可信度评估

3. 实时状态指示

直观的工作状态显示:

/* 工作状态样式 */
MessageWidget.assistant.working {
    border: thick $warning;  /* 黄色边框 */
}

#status-indicator.working {
    color: $warning;
    text-style: bold;
}

流畅的状态切换:

def set_status_working(self):
    """智能状态管理"""
    status.update("●  Working...")

    # 找到最后一条助手消息并高亮
    messages_container = self.query_one("#messages-container", MessagesContainer)
    if messages_container.children:
        last_msg = messages_container.children[-1]
        if isinstance(last_msg, MessageWidget) and last_msg.role == "assistant":
            last_msg.add_class("working")

4. 模板驱动的网站生成

严格的模板使用:

# generate-website/SKILL.md
"""CRITICAL - Website Generation Requirements:
When using the 'generate-website' skill, you MUST:
- Follow ALL guidelines in the generate-website SKILL.md exactly
- Use the provided template files (template-styles.css, template-script.js)
- NEVER create custom CSS/JS from scratch or memory
- Copy templates byte-for-byte without modifications
"""

模板文件结构:

  • template-styles.css:响应式布局和OpenEDU主题
  • template-script.js:交互功能和学习进度追踪
  • 自动生成HTML结构,保持设计一致性

部署和使用

环境要求

# Python环境
python >= 3.11

# 依赖安装
uv sync

# 环境变量配置
export ANTHROPIC_API_KEY="your-claude-api-key"
export FIRECRAWL_API_KEY="your-firecrawl-key"
export CONTEXT7_API_KEY="your-context7-key"

启动应用

# TUI模式
uv run python tui.py

# CLI模式
uv run python cli.py

使用示例

# 基础学习需求
"我想学习人工智能的最新发展"

# 技能主题学习
"帮我制作一个关于量子计算的课程"

# 多语言资源
"我想学习机器学习,需要中英文资源"

性能优化

1. 系统提示优化

  • 优化前:24KB技能内容 + 基础提示 = 巨大开销
  • 优化后:仅基础提示 + 技能按需加载
  • 性能提升:系统提示减少95%,响应速度显著提升

2. 流式响应处理

# 异步流式处理,避免阻塞UI
async for event in self.current_client.agent_event_stream(...):
    if event.type == "assistant_message":
        # 实时更新界面,不等待完整响应
        self._update_ui_partial(event.content)

3. 智能缓存机制

  • 会话状态持久化存储
  • 搜索结果缓存复用
  • 模板文件预加载

扩展性设计

1. 模块化技能系统

新技能只需在.claude/skills/目录下创建:

.claude/skills/
├── new-skill/
│   ├── SKILL.md          # 技能定义和流程
│   ├── skill.json        # 元数据配置
│   └── resources/        # 技能专用资源

2. MCP工具扩展

支持集成更多专业工具:

mcp_servers={
    "existing-tool": {...},
    "new-tool": {
        "type": "stdio",
        "command": "python",
        "args": ["-m", "new_tool_server"],
    },
}

3. 多模态支持

未来可扩展支持:

  • 音频内容处理(播客、讲座)
  • 视频内容分析(教学视频)
  • 交互式练习生成

项目成果

OpenEDU Agent成功实现了以下目标:

  1. 个性化课程生成:根据用户需求自动生成结构化课程内容
  2. 实时信息整合:从多源获取最新、最准确的学习资源
  3. 专业网站输出:生成类似Coursera的专业在线课程网站
  4. 高效学习体验:流畅的TUI界面和实时状态反馈
  5. 可扩展架构:模块化设计支持快速功能扩展

该项目展示了AI Agent在教育领域的巨大潜力,为个性化、实时的在线学习提供了全新的解决方案。通过结合Claude Agent SDK的强大能力和专业的搜索爬取工具,OpenEDU Agent成为了传统MOOC平台的强大补充和替代方案。


项目采用开源许可证,欢迎社区贡献和反馈。