教育场景 Agent 设计:自适应学习路径的生成与调整策略

一、统一的教学进度,毁掉了多少学生的兴趣?

传统的在线教育系统有一个共同特点:所有学生走同一条学习路径。第一章学完学第二章,练习题做完做考试。但真实的学习过程是非线性的——有的学生第三章知识点早就掌握了,被困在进度条里浪费时间;有的学生基础薄弱,被课程进度拖着跑,越学越迷茫。

自适应学习要解决的核心问题是:根据学生当前的知识掌握状态,动态调整下一步学什么、怎么学、学多深。这不是简单地"推荐几道额外的练习题",而是需要一套完整的 Agent 架构,能够实时评估、决策、调整、反馈。

二、自适应学习 Agent 的架构设计

核心设计是一个"评估-决策-执行-反馈"的闭环:

关键设计决策:知识状态评估不能只用"正确率",而应该用认知诊断模型(如 DINA 或 IRT)。一道题做错了,需要区分是"完全不懂"还是"粗心算错"。这两种错误的后续推荐策略完全不同。

三、Go 实现:学习路径决策引擎核心

package adaptive

import (
    "fmt"
    "math"
    "sort"
)

// KnowledgePoint 知识点
type KnowledgePoint struct {
    ID           string   `json:"id"`
    Name         string   `json:"name"`
    Difficulty   float64  `json:"difficulty"`    // 0-1,1 最难
    Prerequisites []string `json:"prerequisites"` // 前置知识点 ID
}

// StudentState 学生知识状态
type StudentState struct {
    StudentID    string             `json:"student_id"`
    Mastery      map[string]float64 `json:"mastery"`       // 知识掌握概率 0-1
    Confidence   map[string]float64 `json:"confidence"`    // 置信度 0-1
    TotalAttempts map[string]int    `json:"total_attempts"`
}

// LearningResource 学习资源(视频/练习题/文章)
type LearningResource struct {
    ID            string   `json:"id"`
    Type          string   `json:"type"`          // video, exercise, article
    KnowledgeIDs  []string `json:"knowledge_ids"` // 关联知识点
    DifficultyEst float64  `json:"difficulty_est"`
}

// DecisionEngine 学习路径决策引擎
type DecisionEngine struct {
    knowledgeMap   map[string]*KnowledgePoint
    resourcePool   map[string]*LearningResource
    masteryThreshold float64 // 掌握阈值,超过即为"已掌握"
}

// NewDecisionEngine 创建决策引擎
func NewDecisionEngine(threshold float64) *DecisionEngine {
    return &DecisionEngine{
        knowledgeMap:     make(map[string]*KnowledgePoint),
        resourcePool:     make(map[string]*LearningResource),
        masteryThreshold: threshold,
    }
}

// RegisterKnowledge 注册知识点
func (de *DecisionEngine) RegisterKnowledge(kp *KnowledgePoint) {
    de.knowledgeMap[kp.ID] = kp
}

// RegisterResource 注册学习资源
func (de *DecisionEngine) RegisterResource(r *LearningResource) {
    de.resourcePool[r.ID] = r
}

// Recommend 基于学生状态推荐下一步学习内容
func (de *DecisionEngine) Recommend(
    state *StudentState,
) ([]*LearningResource, float64, error) {
    if len(de.knowledgeMap) == 0 {
        return nil, 0, fmt.Errorf("知识点库为空,请先注册知识点")
    }

    // 第一步:找出所有"前置已掌握、自身未掌握"的候选知识点
    var candidates []*KnowledgePoint
    for _, kp := range de.knowledgeMap {
        mastery, ok := state.Mastery[kp.ID]
        if ok && mastery >= de.masteryThreshold {
            continue // 已掌握,跳过
        }
        // 检查前置知识点是否全部掌握或接近掌握
        prereqOK := true
        for _, prereqID := range kp.Prerequisites {
            preMastery, exists := state.Mastery[prereqID]
            if !exists || preMastery < de.masteryThreshold*0.8 {
                prereqOK = false
                break
            }
        }
        if prereqOK {
            candidates = append(candidates, kp)
        }
    }

    if len(candidates) == 0 {
        return nil, 0, fmt.Errorf("所有知识点已掌握或无可学习的知识点")
    }

    // 第二步:选择最需要学习的知识点(掌握度最低 × 难度适中)
    sort.Slice(candidates, func(i, j int) bool {
        scoreI := (1 - state.Mastery[candidates[i].ID]) *
            candidates[i].Difficulty
        scoreJ := (1 - state.Mastery[candidates[j].ID]) *
            candidates[j].Difficulty
        return scoreI > scoreJ
    })

    // 第三步:匹配学习资源
    targetKP := candidates[0]
    var resources []*LearningResource
    for _, res := range de.resourcePool {
        for _, kid := range res.KnowledgeIDs {
            if kid == targetKP.ID {
                resources = append(resources, res)
                break
            }
        }
    }

    // 第四步:根据掌握度决定资源类型
    currentMastery := state.Mastery[targetKP.ID]
    if currentMastery == 0 {
        // 完全不会:优先推荐讲解视频
        sort.SliceStable(resources, func(i, j int) bool {
            return resources[i].Type == "video"
        })
    } else if currentMastery < de.masteryThreshold*0.6 {
        // 部分掌握:优先推荐练习题
        sort.SliceStable(resources, func(i, j int) bool {
            return resources[i].Type == "exercise"
        })
    }
    // 估计学习收益(信息增益的简化计算)
    estimatedGain := (1 - currentMastery) *
        (1 - state.Confidence[targetKP.ID]) *
        (1 - targetKP.Difficulty*0.3)

    return resources, estimatedGain, nil
}

// UpdateMastery 根据作答结果更新知识状态
func (de *DecisionEngine) UpdateMastery(
    state *StudentState,
    kpID string,
    correct bool,
    slipRate, guessRate float64, // DINA 模型的 slip 和 guess 参数
) {
    currentMastery := state.Mastery[kpID]
    state.TotalAttempts[kpID]++

    // 使用贝叶斯更新掌握概率
    var likelihood float64
    if correct {
        likelihood = (1 - slipRate)
    } else {
        likelihood = slipRate
    }
    // P(mastery|result) ∝ P(result|mastery) × P(mastery)
    posterior := likelihood * currentMastery
    if !correct {
        posterior = guessRate * (1 - currentMastery)
    }
    // 归一化
    state.Mastery[kpID] = math.Min(1.0, posterior+0.1*float64(state.TotalAttempts[kpID])*0.01)
    state.Confidence[kpID] = math.Min(1.0,
        0.3+0.05*float64(state.TotalAttempts[kpID]))
}

四、边界分析与 Trade-offs

冷启动问题:新学生没有历史数据,无法估算知识状态。可行的方案是先做 10-15 道诊断性测试题,覆盖核心知识点,快速建立初始画像。但测试题太多会导致学生反感,太少则估算不准。经验值是控制在 10 分钟以内。

知识图谱的维护成本:手工标注 500+ 个知识点的前置依赖关系需要 2-3 个教研人员一个月的工作量。可以用半自动方式:先让 AI 根据教材目录和章节顺序自动生成初版图谱,再由教研人员审核修正。这样能将人力降低 70%。

过度个性化 vs 教育公平:自适应路径可能导致"差生永远在补基础,看不到进阶内容"。解决方法是强制插入一定比例的"挑战性内容"(约 15%),即使掌握度不高也推送。这能保持学生的学习动力,避免"永远在刷简单题"的倦怠。

实时性要求:如果学生做完一道题要等 2 秒才收到下一道推荐,体验会很差。决策引擎的推荐计算必须在 100ms 内完成。为此,知识状态计算可以预计算并缓存,只有作答完成后触发增量更新。

五、总结

自适应学习 Agent 的核心不是推荐算法本身,而是"认知诊断 + 知识图谱"的组合。认知诊断解决"学生哪里不会"的问题,知识图谱解决"先学什么后学什么"的问题。决策引擎的推荐策略是分层的:完全不会 → 视频讲解,部分掌握 → 针对性练习,已掌握 → 进阶挑战。工程上注意冷启动和实时性两个约束,算法上注重贝叶斯更新和 IRT 模型的结合。好的自适应系统让学生觉得"被理解",而不是"被管理"。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐