最近 GitHub 上面开源的 OpenMAIC 很火,我看了源码,很适合作为入门 Agent 的学习落地。今天给大家分享他的架构设计
目录
概述
OpenMAIC 是一个基于 AI 的智能课程生成平台,能够根据用户的自然语言需求自动生成完整的教学课件。系统采用三阶段流水线架构,依次生成课程大纲、场景内容和教学动作。
核心能力
- 智能大纲生成:从自然语言需求自动推导课程结构
- 多类型场景支持:幻灯片(slide)、测验(quiz)、互动(interactive)、项目式学习(pbl)
- 多模态输入:支持 PDF 文档、图片、网络搜索结果作为上下文
- 多模型支持:OpenAI、Anthropic Claude、Google Gemini、DeepSeek、Ollama 等
- 流式输出:SSE 实时返回生成进度
- 离线支持:IndexedDB 本地持久化
系统架构
┌─────────────────────────────────────────────────────────────────────────────────┐
│ OpenMAIC 系统架构 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 用户界面层 (Frontend) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 首页 │ │ 课堂页面 │ │ 设置面板 │ │ 代理工具栏 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 状态管理层 (Zustand) │ │
│ │ useStageStore | useSettingsStore | useCanvasStore | useMediaStore │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ API 路由层 (Next.js API Routes) │ │
│ │ /api/generate/scene-outlines-stream | scene-content | scene-actions │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 核心业务层 (Generation Pipeline) │ │
│ │ outline-generator → scene-generator → scene-builder → action-parser │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ AI 服务层 (LLM Abstraction) │ │
│ │ callLLM() / streamLLM() → providers.ts → OpenAI/Claude/Gemini/... │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 数据持久层 (IndexedDB + LocalStorage) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
技术栈
前端框架
| 技术 | 版本 | 用途 |
|---|---|---|
| Next.js | 16.1.2 | 全栈 React 框架 |
| React | 19.2.3 | UI 组件库 |
| TypeScript | 5.x | 类型安全 |
| Tailwind CSS | 4.x | 样式系统 |
状态管理
| 技术 | 用途 |
|---|---|
| Zustand | 全局状态管理 |
| Immer | 不可变数据更新 |
| Dexie | IndexedDB ORM |
AI 集成
| 技术 | 用途 |
|---|---|
| Vercel AI SDK | 统一 LLM 调用接口 |
| @ai-sdk/anthropic | Claude 模型支持 |
| @ai-sdk/openai | GPT 模型支持 |
| @ai-sdk/google | Gemini 模型支持 |
| LangChain | 复杂 AI 工作流 |
文档处理
| 技术 | 用途 |
|---|---|
| pdf-parse | PDF 文本提取 |
| unpdf | PDF 渲染 |
| pptxgenjs | PPT 生成 |
| mammoth | Word 文档解析 |
核心模块
目录结构
OpenMAIC/
├── app/ # Next.js App Router
│ ├── page.tsx # 首页(课程创建入口)
│ ├── layout.tsx # 根布局
│ ├── classroom/[id]/ # 课堂播放页面
│ ├── generation-preview/ # 生成预览页面
│ └── api/ # API 路由
│ ├── generate/ # 生成相关 API
│ │ ├── scene-outlines-stream/ # 大纲流式生成
│ │ ├── scene-content/ # 场景内容生成
│ │ ├── scene-actions/ # 教学动作生成
│ │ ├── image/ # AI 图片生成
│ │ ├── video/ # AI 视频生成
│ │ └── tts/ # 语音合成
│ ├── chat/ # 对话 API
│ ├── parse-pdf/ # PDF 解析
│ └── web-search/ # 网络搜索
│
├── lib/ # 核心业务逻辑
│ ├── ai/ # AI 服务层
│ │ ├── llm.ts # 统一 LLM 调用封装
│ │ └── providers.ts # 模型提供商配置
│ ├── generation/ # 生成流水线
│ │ ├── outline-generator.ts # 大纲生成器
│ │ ├── scene-generator.ts # 场景内容生成器
│ │ ├── scene-builder.ts # 场景构建器
│ │ ├── action-parser.ts # 动作解析器
│ │ └── prompts/ # Prompt 模板系统
│ │ ├── templates/ # 模板文件
│ │ ├── snippets/ # 可复用片段
│ │ └── loader.ts # 模板加载器
│ ├── store/ # Zustand 状态管理
│ ├── types/ # TypeScript 类型定义
│ └── utils/ # 工具函数
│
├── components/ # React 组件
│ ├── generation/ # 生成相关组件
│ ├── stage/ # 课堂舞台组件
│ ├── slide-renderer/ # 幻灯片渲染器
│ ├── settings/ # 设置面板
│ └── ui/ # 基础 UI 组件
│
├── configs/ # 配置文件
└── public/ # 静态资源
关键模块说明
1. AI 服务层 (lib/ai/)
llm.ts - 统一 LLM 调用封装
// 调用示例
import { callLLM, streamLLM } from '@/lib/ai/llm';
// 同步调用
const result = await callLLM({
model: languageModel,
system: systemPrompt,
prompt: userPrompt,
maxOutputTokens: 4096,
}, 'scene-content');
// 流式调用
const stream = streamLLM({
model: languageModel,
system: systemPrompt,
prompt: userPrompt,
}, 'scene-outlines-stream');
for await (const chunk of stream.textStream) {
console.log(chunk);
}
providers.ts - 模型提供商配置
支持以下提供商:
- OpenAI (GPT-4o, GPT-4-turbo, o1, o3)
- Anthropic (Claude 4.6, Claude 4.5)
- Google (Gemini 2.5, Gemini 3.x)
- DeepSeek (DeepSeek V3, R1)
- Qwen (通义千问)
- Kimi (Moonshot)
- GLM (智谱)
- Ollama (本地模型)
2. 生成流水线 (lib/generation/)
outline-generator.ts - 大纲生成
- 输入:用户需求文本、PDF 内容、图片描述
- 输出:SceneOutline[] 场景大纲数组
- 特点:使用 SSE 流式返回,支持增量解析 JSON
scene-generator.ts - 场景内容生成
- 输入:单个 SceneOutline
- 输出:完整场景内容(elements/questions/html)
- 特点:根据 type 分发到不同的内容生成器
prompts/ - Prompt 模板系统
prompts/
├── templates/ # 完整提示词模板
│ ├── requirements-to-outlines/ # 大纲生成
│ │ ├── system.md
│ │ └── user.md
│ ├── slide-content/ # 幻灯片内容
│ ├── quiz-content/ # 测验内容
│ ├── interactive-html/ # 互动内容
│ └── ...
├── snippets/ # 可复用片段
│ ├── json-format.md
│ └── ...
└── loader.ts # 模板加载和变量插值
课程生成流程
三阶段流水线
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 课程生成三阶段流程 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入 │
│ requirement: "帮我创建一个关于光合作用的初中生物课程,时长20分钟" │
│ language: "zh-CN" │
│ pdfFile: [可选] 教材PDF │
│ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ Stage 1: 需求 → 场景大纲 ║ │
│ ╠═══════════════════════════════════════════════════════════════════════════╣ │
│ ║ API: POST /api/generate/scene-outlines-stream ║ │
│ ║ 流程: 解析输入 → 构建Prompt → 调用LLM → 流式返回JSON ║ │
│ ║ 输出: SceneOutline[] ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ Stage 2: 大纲 → 场景内容 ║ │
│ ╠═══════════════════════════════════════════════════════════════════════════╣ │
│ ║ API: POST /api/generate/scene-content (每个大纲独立调用) ║ │
│ ║ 流程: 根据type选择prompt → 生成内容 → 解析JSON ║ │
│ ║ 输出: GeneratedSlideContent | GeneratedQuizContent | ... ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ Stage 3: 内容 → 教学动作 ║ │
│ ╠═══════════════════════════════════════════════════════════════════════════╣ │
│ ║ API: POST /api/generate/scene-actions ║ │
│ ║ 流程: 分析内容 → 生成讲解动作 → 添加动画触发 ║ │
│ ║ 输出: Action[] (教学动作序列) ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ 最终输出: 完整课堂 (Stage) │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Stage 1 详解:大纲生成
入口文件: app/api/generate/scene-outlines-stream/route.ts
// 请求体结构
interface OutlineRequest {
requirements: UserRequirements;
pdfText?: string; // PDF 提取的文本
pdfImages?: PdfImage[]; // PDF 提取的图片
imageMapping?: ImageMapping; // 图片ID -> base64 URL
researchContext?: string; // 网络搜索结果
agents?: AgentInfo[]; // 教师 Agent 信息
}
// 响应 (SSE 事件流)
// event: { type: 'outline', data: SceneOutline, index: number }
// event: { type: 'done', outlines: SceneOutline[] }
// event: { type: 'error', error: string }
Prompt 构建流程:
- 加载模板文件
templates/requirements-to-outlines/ - 变量插值:
{{requirement}}→ 用户需求文本{{language}}→ 课程语言{{pdfContent}}→ PDF 文本内容{{availableImages}}→ 可用图片描述{{researchContext}}→ 网络搜索结果
- 处理图片:
- Vision 模型:前 N 张图片直接传给模型
- 非 Vision 模型:转换为文本描述
Stage 2 详解:内容生成
入口文件: app/api/generate/scene-content/route.ts
根据 outline.type 分发到不同生成器:
| type | 模板 | 输出类型 |
|---|---|---|
| slide | slide-content/ | GeneratedSlideContent |
| quiz | quiz-content/ | GeneratedQuizContent |
| interactive | interactive-html/ + interactive-scientific-model/ | GeneratedInteractiveContent |
| pbl | 特殊处理 | GeneratedPBLContent |
Stage 3 详解:动作生成
入口文件: app/api/generate/scene-actions/route.ts
生成的动作类型:
| 动作类型 | 说明 |
|---|---|
| speak | 语音讲解 |
| highlight | 元素高亮 |
| show | 显示元素 |
| hide | 隐藏元素 |
| pointer | 指针移动 |
| animation | 触发动画 |
数据模型
核心类型定义
用户需求 (lib/types/generation.ts)
interface UserRequirements {
requirement: string; // 自由文本需求
language: 'zh-CN' | 'en-US';
userNickname?: string; // 学生昵称(个性化)
userBio?: string; // 学生背景(个性化)
webSearch?: boolean; // 是否启用网络搜索
}
场景大纲 (lib/types/generation.ts)
interface SceneOutline {
id: string;
type: 'slide' | 'quiz' | 'interactive' | 'pbl';
title: string;
description: string;
keyPoints: string[];
teachingObjective?: string;
estimatedDuration?: number;
order: number;
language?: 'zh-CN' | 'en-US';
// 图片引用
suggestedImageIds?: string[];
// AI 生成媒体请求
mediaGenerations?: MediaGenerationRequest[];
// 类型特定配置
quizConfig?: QuizConfig;
interactiveConfig?: InteractiveConfig;
pblConfig?: PBLConfig;
}
完整场景 (lib/types/stage.ts)
interface Scene {
id: string;
stageId: string;
title: string;
type: 'slide' | 'quiz' | 'interactive' | 'pbl';
order: number;
// 场景内容
elements?: PPTElement[]; // 幻灯片元素
questions?: QuizQuestion[]; // 测验问题
html?: string; // 互动 HTML
// 教学动作
actions: Action[];
// 元数据
duration?: number;
thumbnail?: string;
}
教学动作 (lib/types/action.ts)
interface Action {
id: string;
type: ActionType;
timing: number; // 触发时间 (ms)
duration?: number; // 持续时间 (ms)
targetId?: string; // 目标元素 ID
content?: string; // 讲解文本
params?: Record<string, unknown>; // 额外参数
}
AI 服务集成
模型配置 (lib/ai/providers.ts)
export const PROVIDERS: Record<ProviderId, ProviderConfig> = {
openai: {
type: 'openai',
name: 'OpenAI',
models: [
{
id: 'gpt-4o',
name: 'GPT-4o',
contextWindow: 128000,
outputWindow: 16384,
capabilities: { vision: true },
},
// ...
],
},
anthropic: {
type: 'anthropic',
name: 'Anthropic',
models: [
{
id: 'claude-sonnet-4-6-20250514',
name: 'Claude Sonnet 4.6',
contextWindow: 200000,
outputWindow: 64000,
capabilities: { vision: true, thinking: true },
},
// ...
],
},
// ...
};
Thinking 模式支持
系统支持为支持深度思考的模型启用 Thinking 模式:
// 调用时启用 thinking
await callLLM(params, 'source', undefined, {
enabled: true,
budgetTokens: 10240,
});
// 或全局禁用
LLM_THINKING_DISABLED=true
多模态输入
// Vision 模型直接传递图片
const result = await callLLM({
model: languageModel,
system: systemPrompt,
messages: [{
role: 'user',
content: [
{ type: 'text', text: userPrompt },
{ type: 'image', image: base64Url },
],
}],
});
API 接口
生成相关 API
| 接口 | 方法 | 说明 |
|---|---|---|
/api/generate/scene-outlines-stream | POST | SSE 流式生成大纲 |
/api/generate/scene-content | POST | 生成单个场景内容 |
/api/generate/scene-actions | POST | 生成教学动作 |
/api/generate/image | POST | AI 图片生成 |
/api/generate/video | POST | AI 视频生成 |
/api/generate/tts | POST | 文本转语音 |
其他 API
| 接口 | 方法 | 说明 |
|---|---|---|
/api/parse-pdf | POST | 解析 PDF 文档 |
/api/web-search | POST | 执行网络搜索 |
/api/chat | POST | 课堂对话 |
/api/classroom | GET/POST | 课堂数据管理 |
请求头约定
生成 API 通过请求头传递配置:
x-provider-id: openai
x-model-id: gpt-4o
x-image-generation-enabled: true
x-video-generation-enabled: false
状态管理
useStageStore (lib/store/stage.ts)
管理课堂核心状态:
interface StageState {
// 课堂信息
stage: Stage | null;
// 场景列表
scenes: Scene[];
currentSceneId: string | null;
// 生成状态
generatingOutlines: SceneOutline[];
outlines: SceneOutline[];
generationStatus: 'idle' | 'generating' | 'paused' | 'completed' | 'error';
// 操作方法
setStage: (stage: Stage) => void;
addScene: (scene: Scene) => void;
updateScene: (sceneId: string, updates: Partial<Scene>) => void;
// ...
}
useSettingsStore (lib/store/settings.ts)
管理用户配置:
interface SettingsState {
// 模型配置
providerId: ProviderId;
modelId: string;
providersConfig: ProvidersConfig;
// 功能配置
pdfProviderId: PDFProviderId;
webSearchProviderId: WebSearchProviderId;
// 操作方法
setModel: (providerId: ProviderId, modelId: string) => void;
// ...
}
数据持久化
IndexedDB Schema (lib/utils/database.ts)
// 使用 Dexie ORM
const db = new Dexie('OpenMAIC');
db.version(1).stores({
// 课堂数据
stages: 'id, createdAt, updatedAt',
stageScenes: 'stageId, sceneId',
stageOutlines: 'stageId',
// 媒体缓存
pdfImages: 'id, sessionId',
generatedImages: 'id, stageId',
generatedVideos: 'id, stageId',
// 用户配置
settings: 'key',
});
存储策略
| 数据类型 | 存储位置 | 生命周期 |
|---|---|---|
| 用户配置 | LocalStorage | 持久 |
| 课堂数据 | IndexedDB | 持久 |
| PDF 图片 | IndexedDB | 会话级 |
| 生成缓存 | IndexedDB | 按课堂 |
扩展开发指南
添加新的场景类型
- 在
lib/types/generation.ts添加类型定义 - 在
lib/generation/prompts/templates/创建新的 prompt 模板 - 在
lib/generation/scene-generator.ts添加生成逻辑 - 在
components/scene-renderers/创建渲染组件
添加新的 LLM 提供商
- 在
lib/ai/providers.ts添加提供商配置 - 在
lib/types/provider.ts添加类型定义 - 测试
callLLM/streamLLM兼容性
自定义 Prompt 模板
模板支持变量插值和片段复用:
<!-- templates/custom/system.md -->
# Custom Generator
{{snippet:json-format}}
## Variables
- Topic: {{topic}}
- Language: {{language}}
import { buildPrompt, PROMPT_IDS } from '@/lib/generation/prompts';
const prompts = buildPrompt('custom-generator', {
topic: 'My Topic',
language: 'zh-CN',
});
附录:关键文件索引
| 功能 | 文件路径 |
|---|---|
| 首页入口 | app/page.tsx |
| 课堂播放 | app/classroom/[id]/page.tsx |
| 大纲生成 API | app/api/generate/scene-outlines-stream/route.ts |
| 内容生成 API | app/api/generate/scene-content/route.ts |
| 动作生成 API | app/api/generate/scene-actions/route.ts |
| LLM 封装 | lib/ai/llm.ts |
| 提供商配置 | lib/ai/providers.ts |
| 大纲生成器 | lib/generation/outline-generator.ts |
| 场景生成器 | lib/generation/scene-generator.ts |
| Prompt 加载器 | lib/generation/prompts/loader.ts |
| 类型定义 | lib/types/generation.ts, lib/types/stage.ts |
| 状态管理 | lib/store/stage.ts, lib/store/settings.ts |
| 数据库 | lib/utils/database.ts |