演讲式 AI 陪练引擎设计:PPT 内容解析、翻页节奏感知与多模态评分的工程实现

简介: 本文详解AI演讲陪练系统的技术架构,聚焦三大核心挑战:页级内容基准构建(PPT解析+讲稿/关键词双基准)、翻页节奏感知(显式指令+内容跳跃状态机)、多维量化评分(语速/停顿/脱稿/时间等可计算信号)。强调“评估什么”决定“如何工程化”,实现从人工感觉向客观数据的精准转化。

一、问题拆解:演讲陪练和对话陪练是两种完全不同的评估问题

对话陪练(销售话术、客诉处理)评估的是"说什么"——AI 扮演客户,看学员的应对是否得当,核心是内容与策略的匹配。

演讲陪练评估的是"怎么说"——同样一份 PPT、同一篇讲稿,讲得流畅与讲得磕绊是两种结果,全程念稿与脱稿转述是两种水平,压着时间讲完与超时一半更是天壤之别。核心是表现与基准的偏离度。

这个差异带来三个全新的工程问题:

问题一:内容基准从哪来? 对话陪练的基准是"标准应对话术",通常几句话就够。演讲陪练的基准是"这 31 页 PPT 每页应该讲什么"——一份 31 页的方案汇报,全程应该讲 40 分钟,每页都有该覆盖的要点。基准必须做到页级粒度,否则评估无从谈起。

问题二:翻页节奏怎么感知? 现场演讲中"讲到哪页了"由演讲者翻页决定,AI 陪练要跟着学员的节奏走:学员说"下一页",界面要翻页;学员第 3 页还没讲完就开始讲第 4 页的内容,AI 要知道当前应该停在第 3 页——否则"这一页漏讲了"的判定全部错位。

问题三:表现质量怎么量化? "讲得好不好"在人工评委那里是感觉,在 AI 这里必须拆成可计算的信号:语速(每分钟字数)、停顿(异常长停顿的次数与分布)、口头禅密度("嗯""然后"的每分钟频次)、脱稿程度(演讲内容与 PPT 原文的逐字重合度)、时间管控(实际时长 vs 预算时长)。

三个问题对应本文的三个核心模块:页级内容基准、翻页感知状态机、多维评分引擎。先看整体架构。

二、整体架构:从五步创建流程到陪练引擎

演讲陪练在产品上是一个五步创建流程:创建对话剧本 → 演讲稿设置 → 创建评分标准 → 创建陪练计划 → 添加学员。流程背后是四个引擎模块:

┌──────────────────────────────────────────────────────────────┐
│                    陪练创建流程(管理端)                       │
│  ① 对话剧本      ② 演讲稿设置        ③ 评分标准               │
│  (场景设定)      (页级讲稿+关键词)    (维度/权重/细则)           │
│                 ┌──────────────┐  ┌──────────────┐           │
│                 │ PPT 解析器     │  │ 评分标准编辑器 │           │
│                 │ 逐页提取结构   │  │ AI 生成维度   │           │
│                 └──────┬───────┘  └──────┬───────┘           │
│                 ┌──────▼───────┐  ┌──────▼───────┐           │
│                 │ 讲稿生成器     │  │ 细则结构化     │           │
│                 │ (AI帮写/全篇)  │  │ (评分点抽取)  │           │
│                 └──────────────┘  └──────────────┘           │
└──────────────────────────┬───────────────────────────────────┘
                           │ 内容基准 + 评分标准(陪练配置)
┌──────────────────────────▼───────────────────────────────────┐
│                    演练执行引擎(学员端)                       │
│                                                              │
│  ┌──────────┐   ┌──────────────┐   ┌────────────────────┐   │
│  │ 流式 ASR   │──▶│ 翻页感知      │──▶│ 实时反馈控制器       │   │
│  │ 语音流采集  │   │ 状态机        │   │ (漏讲提示/翻页提示)  │   │
│  └─────┬────┘   └──────┬───────┘   └────────────────────┘   │
│        │               │                                     │
│        │        ┌──────▼──────────────────────────────┐      │
│        └───────▶│         多维评分引擎(演练结束触发)      │      │
│                 │  内容覆盖度(50%) × 语言表达(30%)        │      │
│                 │       × 时间管控(20%)                 │      │
│                 └─────────────────────────────────────┘      │
└──────────────────────────┬───────────────────────────────────┘
                           │
                    ┌──────▼─────────────┐
                    │ 评分报告生成         │
                    │ 维度分/雷达图/逐页批注│
                    └────────────────────┘

管理端先把"考题"出好(内容基准 + 评分标准),学员端演练时实时感知节奏、结束后整体评分。两侧的关键数据模型如下逐一展开。

三、页级内容基准:PPT 解析与"讲稿 + 关键词"双基准设计

3.1 PPT 解析器:逐页结构提取

陪练创建的第一步是把上传的 PPT 解析成逐页结构。解析用 Apache POI(.pptx 结构化解析)+ 视觉模型兜底(图表类内容转文字描述):

/**
 * PPT 解析器:输出页级结构数据
 */
@Service
public class PptParser {
   

    public List<PptPage> parse(InputStream pptStream) {
   
        XMLSlideShow ppt = new XMLSlideShow(pptStream);
        List<PptPage> pages = new ArrayList<>();

        int index = 1;
        for (XSLFSlide slide : ppt.getSlides()) {
   
            PptPage page = new PptPage();
            page.setPageIndex(index++);
            page.setTitle(extractTitle(slide));          // 页标题
            page.setBulletPoints(extractBullets(slide)); // 要点列表
            page.setTables(extractTables(slide));        // 表格数据
            page.setNotes(extractNotes(slide));          // 备注页(如有)
            page.setImageCount(countImages(slide));

            // 图表/复杂图形:截图后走视觉模型生成文字描述
            if (page.getImageCount() > 0 || hasCharts(slide)) {
   
                page.setVisualDescription(
                    visionModel.describe(renderSlideToImage(slide))
                );
            }
            pages.add(page);
        }
        return pages;
    }
}

/**
 * 解析出的页结构
 */
@Data
public class PptPage {
   
    private Integer pageIndex;
    private String title;                  // "产品背景"
    private List<String> bulletPoints;     // 要点
    private List<TableData> tables;
    private String notes;                  // PPT 备注栏
    private String visualDescription;      // 图表的文字描述
    private Integer imageCount;
}

视觉模型兜底是必要的:方案汇报类 PPT 大量使用图表(架构图、数据图),纯文本解析会漏掉这些页的核心信息。渲染成图片后用视觉模型生成"本页展示的是三层系统架构,包含接入层/服务层/数据层"这类描述,讲稿生成和评分时都用得上。

3.2 双基准设计:演讲稿 + 关键词

演讲稿设置界面为每页提供两个输入:演讲稿(该页应该讲什么,2000 字上限)和关键词(逗号分隔的必须命中的要点)。这不是两个冗余字段,而是两种互补的评估基准:

/**
 * 页级内容基准:双基准模型
 *
 * 演讲稿(Script):语义基准 —— 评分时做整体语义对齐,
 *                   判断"讲的内容对不对、全不全"
 * 关键词(Keywords):点命中基准 —— 评分时做精确命中检查,
 *                    判断"关键点有没有讲"
 *
 * 设计动机:
 * - 只有讲稿:语义相似度对"关键术语漏讲"不敏感
 *   (学员讲了一大段相关但无关痛痒的话,语义分不低)
 * - 只有关键词:无法评估内容的连贯性和完整性
 */
@Data
public class PageContentBaseline {
   

    private Integer pageIndex;

    /** 演讲稿(语义基准) */
    private String script;                 // 该页的标准讲稿

    /** 关键词(命中基准) */
    private List<String> keywords;         // 必须覆盖的关键要点

    /** 预计讲解时长(秒)—— 讲稿字数 / 语速换算,用于时间预算 */
    private Integer estimatedDurationSec;

    public void calculateDuration() {
   
        // 中文演讲语速基准:200-240 字/分钟
        int charCount = script.replaceAll("\\s", "").length();
        this.estimatedDurationSec = charCount / 4;  // 约 240字/分 → 4字/秒
    }
}

时间预算从讲稿推导:每页讲稿按 240 字/分钟换算出预计讲解时长,全篇累加就是这场演讲的时间预算——评分标准里的"时间管控"维度(规定时长内完成、超时扣分)就有了客观基准,而不是拍脑袋定一个总时长。

3.3 讲稿生成器:"AI 帮写"与"AI 写全篇讲稿"

31 页 PPT 每页都手写讲稿,对管理者是不小的负担。产品提供两级 AI 辅助:单页"AI 帮写"(按页触发,可逐页调整)和"AI 写全篇讲稿"(一键生成全部)。

/**
 * 讲稿生成器
 *
 * 两种触发粒度:
 * 1. 单页生成(AI帮写):只看当前页 + 全局上下文摘要
 * 2. 全篇生成(AI写全篇讲稿):带前后页语境的连贯生成
 */
@Service
public class ScriptGenerator {
   

    /**
     * 单页讲稿生成
     * 上下文策略:当前页完整信息 + 前后页标题(保持衔接)+ 演讲整体信息
     */
    public String generatePageScript(ScriptGenRequest req) {
   
        PptPage current = req.getCurrentPage();
        String prevTitles = joinTitles(req.getPages(), current.getPageIndex() - 2, 2);
        String nextTitles = joinTitles(req.getPages(), current.getPageIndex(), 2);

        return llmClient.chat(String.format("""
            你是一位资深演讲教练。请为 PPT 的指定页面撰写演讲稿。

            ## 演讲信息
            - 演讲主题:%s(如"甲方方案汇报")
            - 听众:%s
            - 总页数:%d,当前页:第 %d 页
            - 前两页标题:%s(衔接上文用,不要重复讲)
            - 后两页标题:%s(为下文铺垫)

            ## 当前页内容
            - 页标题:%s
            - 要点:%s
            - 图表描述:%s
            - PPT备注:%s

            ## 撰写要求
            1. 口语化演讲风格(是说出来的话,不是书面文档)
            2. 覆盖所有要点,图表页要"讲图"(描述图表说明什么问题)
            3. 开头自然承接上一页,结尾为下一页做钩子
            4. 长度:%s 字左右(按语速 %d 字/分钟 × 本页预算 %d 秒换算)
            5. 用户如有补充指令,优先遵循:%s

            只输出讲稿正文。
            """,
            req.getSpeechTitle(), req.getAudience(),
            req.getTotalPages(), current.getPageIndex(),
            prevTitles, nextTitles,
            current.getTitle(), String.join(";", current.getBulletPoints()),
            defaultString(current.getVisualDescription()), defaultString(current.getNotes()),
            req.getTargetCharCount(), 240, req.getEstimatedDurationSec(),
            defaultString(req.getUserInstruction())
        ));
    }

    /**
     * 全篇讲稿生成:逐页生成但带"已生成讲稿的尾部上下文"
     *
     * 为什么不一次生成全部?
     * - 31 页 × 300 字 = 9300 字,单次生成质量衰减明显(后几页明显敷衍)
     * - 逐页生成可以逐页控制长度(按各页时间预算)
     * - 用户体验:生成过程逐页落库,可中断、可从某页重新生成
     */
    public void generateFullScript(Long trainingId) {
   
        List<PptPage> pages = pptRepo.getPages(trainingId);
        Deque<String> recentTails = new ArrayDeque<>(3);  // 最近3页讲稿的结尾

        for (PptPage page : pages) {
   
            ScriptGenRequest req = buildRequest(trainingId, page)
                .withRecentContext(recentTails);  // 注入前文尾部,保证衔接

            String script = generatePageScript(req);
            baselineRepo.saveScript(trainingId, page.getPageIndex(), script);

            recentTails.addLast( StringUtils.abbreviate(script, 100) );
            if (recentTails.size() > 3) recentTails.pollFirst();

            // 逐页推送前端(流式体验:编辑器里逐页填充)
            sseEmitter.push(trainingId, page.getPageIndex(), script);
        }
    }
}

"全篇生成"为什么不一次生成? 这是实践中验证过的决策:长文本单次生成时,模型对后段内容的投入度明显下降(第 25 页之后的讲稿会变得模板化)。逐页生成 + 尾部上下文注入(最近三页讲稿的结尾),既保证了页间衔接,又让每页都获得完整的模型注意力。代价是 31 次 LLM 调用,但可以并行化(只有承接关系存在时串行,用小上下文并行 + 后处理校验衔接)。

关键词生成与讲稿同源——从讲稿中抽取该页的 3-8 个核心要点(产品名、数字、关键概念),供管理者确认或修改。管理者的修改权重高于 AI:改过的关键词在后续重新生成讲稿时作为约束注入。

四、翻页节奏感知:双触发的状态机

演练执行时,引擎必须知道"现在讲到第几页"。这是所有页级评估的前提。

4.1 翻页状态机

翻页采用显式指令 + 内容感知双触发:

                    ┌─────────────────────────────┐
                    │   PageState 状态机(第 N 页)  │
                    └─────────────────────────────┘
    ┌──────────┐  显式指令    ┌──────────┐  显式指令   ┌──────────┐
    │ 第 N 页    │──────────▶│ 第 N+1 页  │──────────▶│ 第 N+2 页  │
    │ SPEAKING  │  "下一页"    │ SPEAKING  │           │          │
    └────┬─────┘             └──────────┘           └──────────┘
         │
         │ 内容完成度 ≥ 90% 且停顿 > 2s(隐式信号)
         ▼
    ┌──────────┐    学员内容跳跃(讲到下一页的标题/关键词)
    │ 翻页建议   │────────────────────────────────────┐
    │ (浮层提示)  │                                    │
    └──────────┘                                     ▼
                                              自动跟随翻页
/**
 * 翻页感知状态机
 *
 * 双触发设计:
 * 1. 显式触发:学员说"下一页/翻页/过"——语音指令关键词命中
 * 2. 隐式触发:内容完成度达标 + 自然停顿 → 弹"建议翻页"提示
 *    (不自动翻,把控制权留给学员——真实演讲也是自己翻页)
 *
 * 特殊处理:内容跳跃跟随 —— 学员开始讲下一页的标题/关键词时,
 * 状态机自动跟随到下一页(演讲者忘了翻页是真实高频场景)
 */
@Service
public class PageTurnStateMachine {
   

    private static final List<String> TURN_COMMANDS = Arrays.asList(
        "下一页", "翻页", "下一张", "过", "next", "next page"
    );

    /** 当前页状态 */
    private int currentPage = 1;
    private List<PageContentBaseline> baselines;

    /**
     * 处理流式 ASR 的新增文本片段
     */
    public PageTurnEvent onTranscriptDelta(String delta, long timestampMs) {
   

        // 1. 显式翻页指令检测
        if (containsTurnCommand(delta)) {
   
            return turnToPage(currentPage + 1, TurnTrigger.EXPLICIT);
        }

        // 2. 内容完成度检测(当前页关键词命中 + 语义覆盖)
        double coverage = coverageCalculator.computeIncremental(
            currentPage, delta);

        if (coverage >= 0.9 && isLongPause(timestampMs)) {
   
            // 内容讲完 + 停顿:建议翻页(浮层提示,不强制)
            return PageTurnEvent.suggestTurn(currentPage);
        }

        // 3. 内容跳跃跟随:新文本与下一页的关键词/标题高度匹配
        if (matchesNextPageContent(delta)) {
   
            return turnToPage(currentPage + 1, TurnTrigger.CONTENT_JUMP);
        }

        return PageTurnEvent.none();
    }

    private boolean matchesNextPageContent(String delta) {
   
        PageContentBaseline next = baselines.get(currentPage);  // 下一页
        if (next == null) return false;

        // 命中下一页标题或 ≥2 个关键词 → 判定为内容跳跃
        boolean titleHit = delta.contains(next.getPageTitle());
        long keywordHits = next.getKeywords().stream()
            .filter(delta::contains).count();

        return titleHit || keywordHits >= 2;
    }
}

"内容跳跃跟随"是体验的关键细节:真实演练中,演讲者讲到兴头上忘了翻页是高频事件——学员已经开讲"第二部分:产品能力",界面还停在第 5 页。状态机检测到新文本命中下一页的标题/关键词时自动跟随,页级评估的锚点就不会错位。而显式指令永远优先——学员说"下一页"立即翻,无论内容完成度多少(回练某页时学员会主动跳页)。

4.2 页级时间轴的沉淀

翻页事件与 ASR 时间戳共同沉淀出"页级时间轴",评分阶段消费:

{
   
  "practiceId": "p_20261014_001",
  "totalDurationSec": 2380,
  "pageTimeline": [
    {
    "page": 1, "startMs": 0,      "endMs": 62000,   "durationSec": 62 },
    {
    "page": 2, "startMs": 62000,  "endMs": 118000,  "durationSec": 56 },
    {
    "page": 3, "startMs": 118000, "endMs": 285000,  "durationSec": 167 }
  ],
  "turnEvents": [
    {
    "page": 2, "trigger": "EXPLICIT",     "atMs": 62000 },
    {
    "page": 3, "trigger": "CONTENT_JUMP", "atMs": 118000 }
  ]
}

五、多维评分引擎:产品评分标准的技术化

评分标准创建界面定义了演讲陪练的评分体系——以"甲方方案汇报"为例的三个维度:

维度 权重 评分细则(界面原文摘录)
内容掌握程度 50% 熟悉度(15分):不逐字念PPT,能脱稿/用自己语言转述;不卡顿、不频繁看幻灯片。重点把控(12分):能识别PPT核心信息……
语言表达与呈现 30% 语言表达(12分):吐字清晰、语速适中;专业术语准确;口语流畅,无大量口头禅(嗯、然后)。语气感染力(10分):节奏有变化……
时间管控 20% 按时完成:在规定时长内完成汇报。轻微超时酌情扣分;超时较多大幅扣分;提前过多收尾、内容仓促也扣分。超时≤30s……

这个评分体系的技术挑战在于:界面上的细则是给人看的自然语言,评分引擎需要把它转成可计算的规则。 我们的方案是"细则结构化"——创建评分标准时,把自然语言细则抽取成"评分点 + 计算方式"的结构化规则。

5.1 评分标准的数据模型

/**
 * 评分维度
 */
@Data
public class ScoringDimension {
   
    private String name;              // "内容掌握程度"
    private Double weight;            // 0.5(权重 50%)
    private List<ScoringPoint> points;
}

/**
 * 评分点:自然语言细则 → 结构化规则
 *
 * 每个评分点绑定一个或多个"信号计算器",
 * 引擎运行时按计算器输出打分。
 */
@Data
public class ScoringPoint {
   
    private String description;       // "不逐字念PPT,能脱稿/用自己语言转述"
    private Double maxScore;          // 15

    /** 计算器类型(引擎按此路由到对应的信号算法) */
    private SignalCalculatorType calculator;
    // SCRIPT_DEPENDENCE   - 脱稿程度(讲稿逐字重合度)
    // KEYWORD_COVERAGE    - 关键词覆盖度
    // SPEECH_RATE         - 语速区间
    // FILLER_DENSITY      - 口头禅密度
    // PAUSE_ANOMALY       - 异常停顿
    // TERM_ACCURACY       - 专业术语准确(ASR 置信度 + 术语表比对)
    // TIME_BUDGET         - 时间管控
    // RHYTHM_VARIATION    - 节奏变化(可选,需较长时间轴)

    /** 计算器参数(如语速区间 180-260 字/分) */
    private Map<String, Object> params;
}

5.2 AI 生成评分维度:细则到规则的自动转换

产品支持"AI 生成"评分维度——输入演讲主题,AI 输出维度、权重和细则。生成时同步产出结构化规则,人工校对后生效:

/**
 * 评分标准 AI 生成器
 * 
 * 关键设计:让 LLM 一次性产出"自然语言细则 + 结构化规则"对,
 * 人工校对自然语言时同步看到规则映射,避免规则与细则脱节。
 */
@Service
public class ScoringStandardGenerator {
   

    public ScoringStandardDraft generate(String speechTitle, String audience,
                                          Integer totalPages,
                                          Integer plannedDurationMin) {
   
        String prompt = String.format("""
            你是一位企业演讲培训专家。请为以下演讲场景设计评分标准。

            ## 演讲信息
            - 主题:%s
            - 听众:%s
            - PPT 页数:%d 页
            - 规定时长:%d 分钟

            ## 要求
            1. 设计 3-4 个评分维度,权重合计 100%%。
               (典型维度:内容掌握程度、语言表达与呈现、时间管控——
               可按演讲类型调整,述职汇报侧重逻辑,产品发布侧重感染力)
            2. 每个维度下 2-4 个评分点,标注该点的满分。
            3. 每个评分点的细则用自然语言描述(给培训管理员看),
               同时指定对应的计算器类型和参数(给评分引擎用)。

            ## 可用计算器类型
            SCRIPT_DEPENDENCE(脱稿度:讲稿逐字重合度,越低越好)
            KEYWORD_COVERAGE(关键词覆盖:页级关键词命中率)
            SPEECH_RATE(语速:字/分钟,参数给 [min, max] 区间)
            FILLER_DENSITY(口头禅:嗯/然后/那个 的每分钟频次)
            PAUSE_ANOMALY(异常停顿:超过 N 秒的停顿次数)
            TIME_BUDGET(时间:实际 vs 预算的偏离规则)
            TERM_ACCURACY(术语准确:ASR 低置信片段占比 + 术语表比对)

            ## 输出 JSON
            {
   
              "dimensions": [
                {
   
                  "name": "内容掌握程度",
                  "weight": 0.5,
                  "points": [
                    {
   
                      "description": "熟悉度:不逐字念PPT,能脱稿/用自己的语言转述;不卡顿、不频繁停顿",
                      "maxScore": 15,
                      "calculator": "SCRIPT_DEPENDENCE",
                      "params": {
    "verbatimThreshold": 0.6, "minParaphraseRatio": 0.7 }
                    }
                  ]
                }
              ]
            }
            """, speechTitle, audience, totalPages, plannedDurationMin);

        LlmResponse resp = llmClient.chat(prompt, Map.of("temperature", 0.4));
        return parseDraft(resp.getContent());
    }
}

为什么让 AI 同时输出细则和规则? 如果只生成自然语言细则、再靠人工映射计算器,31 页 × 多维度的映射工作没人愿意做。让 LLM 在生成细则时就指定计算器类型(从枚举里选),规则与细则天然绑定,人工校对变成"确认"而非"翻译"。

5.3 信号计算器:五个核心算法

评分引擎运行时按页级时间轴 + 全程 ASR 转写计算各信号:

/**
 * 信号计算器实现(核心五个)
 */
@Service
public class SignalCalculators {
   

    /**
     * 1. 脱稿程度(SCRIPT_DEPENDENCE)
     *
     * 原理:把学员的转写文本与该页讲稿做 n-gram 重合度分析。
     * - 3-gram 重合度 > 0.6:存在大段逐字背诵/念稿
     * - 关键指标:n-gram 覆盖率(讲稿的 3-gram 有多少出现在学员语音里)
     * - 区分"转述"与"背诵":语义相似度高 + n-gram 重合度低 = 好的脱稿
     */
    public double calcScriptDependence(String transcript, String script) {
   
        Set<String> scriptTrigrams = ngrams(script, 3);
        Set<String> spokenTrigrams = ngrams(transcript, 3);

        // 讲稿 trigram 在学员语音中的出现比例(越高越像念稿)
        double verbatimRatio = (double) countOverlap(scriptTrigrams, spokenTrigrams)
            / scriptTrigrams.size();

        // 语义相似度(转述是否保留了讲稿的内容)
        double semanticSim = embeddingClient.similarity(
            embeddingClient.embed(transcript),
            embeddingClient.embed(script));

        // 脱稿分 = 语义保持 × (1 - 逐字比例)
        // 内容讲对了但用自己的话 = 满分;内容对但逐字背诵 = 中等;内容不对 = 低分
        return semanticSim * (1 - normalize(verbatimRatio));
    }

    /**
     * 2. 关键词覆盖度(KEYWORD_COVERAGE)
     *
     * 按页计算:该页的关键词在对应页时间段内的命中比例。
     * "时间归属"很重要——第 5 页的关键词在第 3 页讲了不算命中。
     */
    public double calcKeywordCoverage(PracticeSession session) {
   
        int totalKeywords = 0, hitKeywords = 0;

        for (PageTimelineSegment seg : session.getPageTimeline()) {
   
            PageContentBaseline baseline = session.getBaseline(seg.getPage());
            String segTranscript = session.getTranscriptBetween(
                seg.getStartMs(), seg.getEndMs());

            for (String keyword : baseline.getKeywords()) {
   
                totalKeywords++;
                if (segTranscript.contains(keyword)) {
   
                    hitKeywords++;
                } else {
   
                    // 精确 miss 后做同义词/形态变体匹配
                    if (synonymMatcher.match(segTranscript, keyword)) {
   
                        hitKeywords++;
                    } else {
   
                        session.recordMissedKeyword(seg.getPage(), keyword);
                    }
                }
            }
        }
        return totalKeywords == 0 ? 1.0 : (double) hitKeywords / totalKeywords;
    }

    /**
     * 3. 语速(SPEECH_RATE)
     *
     * 有效语速:剔除停顿后的"说话时间"内每分钟字数。
     * 演讲推荐区间 180-260 字/分钟(界面细则"语速适中"的量化)。
     */
    public double calcSpeechRate(PracticeSession session) {
   
        long speechTimeMs = session.getTotalDurationMs() - session.getTotalPauseMs(1000);
        int charCount = session.getTranscript().replaceAll("\\s", "").length();

        double charsPerMin = charCount / (speechTimeMs / 60000.0);

        // 区间评分:180-260 满分,线性衰减到区间外
        if (charsPerMin >= 180 && charsPerMin <= 260) return 1.0;
        if (charsPerMin < 180) return Math.max(0, charsPerMin / 180.0);
        return Math.max(0, 1 - (charsPerMin - 260) / 120.0);  // 380字/分 → 0分
    }

    /**
     * 4. 口头禅密度(FILLER_DENSITY)
     *
     * "嗯、然后、那个、就是说" 的每分钟频次。
     * 依赖 ASR 的词级时间戳(或逐句转写),按句统计。
     * 基准:≤2 次/分钟 满分,≥8 次/分钟 零分,线性插值。
     */
    public double calcFillerDensity(PracticeSession session) {
   
        List<String> fillers = Arrays.asList("嗯", "呃", "然后", "那个", "就是说", "这个");

        long fillerCount = 0;
        for (AsrSentence sentence : session.getSentences()) {
   
            for (String filler : fillers) {
   
                fillerCount += countOccurrences(sentence.getText(), filler);
            }
        }

        double perMinute = fillerCount / (session.getTotalDurationMs() / 60000.0);
        return clamp01(1 - (perMinute - 2) / 6.0);
    }

    /**
     * 5. 时间管控(TIME_BUDGET)
     *
     * 对应界面细则:"轻微超时酌情扣分;超时较多大幅扣分;
     * 提前过多收尾、内容仓促也扣分"
     *
     * 量化规则:
     * - |偏离| ≤ 5%:满分(正常节奏浮动)
     * - 超时 5%-15%:线性扣分至 60%
     * - 超时 >15%:线性扣分至 20%
     * - 提前 >15%(讲太快/漏内容):线性扣分至 30%
     *   (提前收尾会连带触发内容覆盖度扣分,双重信号定位"仓促")
     */
    public double calcTimeBudget(long actualMs, long plannedMs) {
   
        double deviation = (double) (actualMs - plannedMs) / plannedMs;

        if (Math.abs(deviation) <= 0.05) return 1.0;
        if (deviation > 0) {
   
            // 超时
            if (deviation <= 0.15) return 1 - (deviation - 0.05) / 0.10 * 0.4;
            return Math.max(0.2, 0.6 - (deviation - 0.15) * 1.0);
        } else {
   
            // 提前
            double early = -deviation;
            if (early <= 0.15) return 1 - (early - 0.05) / 0.10 * 0.3;
            return Math.max(0.3, 0.7 - (early - 0.15) * 0.8);
        }
    }
}

"提前收尾也扣分"的设计与产品细则一致且必要:只罚超时不罚提前,学员的最优策略就变成飞快念完。提前收尾触发双信号——时间管控扣分 + 内容覆盖度下降(漏讲导致关键词 miss),两个维度交叉印证"内容仓促"。

5.4 总分合成与评分报告

/**
 * 总分合成:维度加权 + LLM 总评生成
 */
@Service
public class ScoreComposer {
   

    public PracticeReport compose(PracticeSession session) {
   
        double totalScore = 0;

        for (ScoringDimension dimension : session.getScoringStandard().getDimensions()) {
   
            double dimensionScore = 0;
            for (ScoringPoint point : dimension.getPoints()) {
   
                // 路由到对应计算器
                double signal = calculatorRegistry
                    .get(point.getCalculator())
                    .calculate(session, point.getParams());
                dimensionScore += signal * point.getMaxScore();
            }
            totalScore += dimensionScore * dimension.getWeight();
        }

        // LLM 生成总评与逐页批注(人话版的反馈)
        String commentary = generateCommentary(session, totalScore);

        return PracticeReport.builder()
            .totalScore(totalScore)
            .dimensionScores(calcDimensionBreakdown(session))
            .missedKeywords(session.getMissedKeywords())   // 漏讲要点清单(逐页)
            .fillerStatistics(session.getFillerStats())
            .pageTimeline(session.getPageTimeline())
            .commentary(commentary)
            .build();
    }

    /**
     * 总评生成:数字分 → 有指导价值的人话
     * 界面"总评"字段(如"熟悉方案,不念PPT;能抓住重点……")
     * 是给"合格表现"的描述模板,AI 总评对齐这个风格
     */
    private String generateCommentary(PracticeSession session, double score) {
   
        return llmClient.chat(String.format("""
            你是一位资深演讲教练。请基于以下评估数据,为学员的演练写一段总评。

            ## 评估数据
            - 总分:%.1f / 100
            - 各维度得分:%s
            - 漏讲关键词(按页):%s
            - 口头禅频次:%.1f 次/分钟
            - 语速:%.0f 字/分钟
            - 时间:实际 %d 分钟 / 预算 %d 分钟
            - 翻页方式分布:%s

            ## 要求
            1. 先一句话总体定性(达到了什么水平)
            2. 指出 1-2 个最突出的优点(有数据支撑)
            3. 指出 1-2 个最需要改进的问题,每个问题给出具体的改进动作
               (如"第 12 页漏讲了'成本对比'这个关键点,建议在讲完竞品分析后
               主动引到成本话题"——要具体到页码和关键词,不要说"加强重点把控")
            4. 全文 150 字以内,语气是教练而不是考官
            """,
            score, formatDimensions(session),
            formatMissedKeywords(session),
            session.getFillerStats().getPerMinute(),
            session.getSpeechRate(),
            session.getActualDurationMin(), session.getPlannedDurationMin(),
            formatTurnTriggers(session)
        ));
    }
}

六、实时反馈架构:流式 ASR 双通道

演练进行中的实时提示(翻页建议、漏讲提醒)和结束后的整体评分,对语音识别的要求不同——前者要快(秒级),后者要准(整段重识别)。我们采用双通道:

学员语音 ──┬──▶ 实时通道:流式 ASR ──▶ 翻页状态机 / 实时提示
           │    (Web RTC 采集 → 云端流式识别,延迟 300-800ms)
           │
           └──▶ 离线通道:整段音频留存 ──▶ 演练结束后录音文件识别
                (词级时间戳、标点、数字归一化全开,评分更准)

为什么评分用离线通道重跑? 流式识别为了低延迟做了很多妥协(无标点、时间戳精度低、长句切分随意),直接影响填充词统计和停顿检测的准确度。离线通道的录音文件识别提供词级时间戳和完整标点,是 FillerDensity、PauseAnomaly 这些细粒度信号的正确数据源。两个通道的成本可以接受——流式按分钟计费,离线识别只对演练录音跑一次。

实时提示的"打扰度"控制在产品层有明确规则:翻页建议用浮层(视觉信号,不插入语音);漏讲提醒只在翻页前一刻出现(该页即将结束但你还有 2 个关键词没讲——给学员一次补救机会,而不是全程打断);口头禅、语速问题只在结束报告中反馈(演讲中纠正这些会严重干扰节奏)。

七、效果数据与总结

这套演讲陪练引擎在内测阶段的关键数据(以 31 页"甲方方案汇报"演练为例):

指标 数值
陪练创建耗时 上传 PPT + AI 全篇讲稿 + AI 评分标准,约 15 分钟
演练时长 中位数 38 分钟(预算 40 分钟)
翻页判定准确率 显式指令 100%,内容跳跃跟随 P95 准确(错位多发生在关键词跨页复用时)
漏讲关键词检出率 与人工标注对比 91%
脱稿度评分合理性 与 3 位人工评委的相关系数 0.82
口头禅检出 ASR 标点修复后,误报率 < 8%
评分-报告产出延迟 演练结束后 ≤ 90 秒

企学宝技术团队总结的四条核心经验:

内容基准做"双基准"(讲稿 + 关键词)是评分可信的前提。 语义对齐管"讲得全不全",关键词命中管"要点漏没漏"——只用其一都会出现明显的评估盲区。页级时间轴 + 关键词的时间归属判定(第 5 页的关键词必须在第 5 页的时间段里讲)是双基准落地的关键工程细节。

评分标准的技术化本质是"自然语言细则 → 结构化规则"的转换。 让 AI 生成细则时同步生成计算器映射,人工从"翻译规则"降级为"确认规则",这套流程让企业培训管理员(非技术角色)也能搭建可计算的评分体系。信号计算器的枚举是有限的(七八个),但组合出的评分标准是无限的。

翻页感知要把控制权留给学员。 显式指令永远优先、隐式信号只建议不强制、内容跳跃自动跟随但记录触发方式——真实演讲中翻页权在演讲者手里,陪练系统应该模拟这种真实感,同时在报告里反映"3 次翻页都是内容跳跃触发的"这类行为信号。

实时提示的价值在"少而准"而不是"多而全"。 演练场景中任何打断都会破坏心流。翻页建议用视觉浮层、漏讲提醒只在页尾出现一次、表达类问题一律留到报告——克制的信息策略反而让学员信任并依赖这些提示。

演讲陪练与对话陪练共用同一个剧本陪练底盘(会话管理、学员分配、练习计划),差异全部收敛在"内容基准模型 + 评估管道"这一层——这也是引擎架构上最重要的约束:新场景的接入成本应该体现在配置里,而不是新系统里。

目录
相关文章
|
23天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9365 30
|
9天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
8天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
638 1
|
21天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2478 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
4天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
413 0
|
3天前
|
存储 缓存 测试技术
QWT 3D 模块重构记:旧渲染管线改进介绍及实时瀑布图示例
QWT 3D模块完成重大重构:解耦窗口与图形(Plot+Item),摒弃OpenGL 1.x固定管线,迁至Core Profile着色器渲染;支持多图元同场景叠加,并以实时3D瀑布图验证——滑动窗口、固定色域、64帧×256点达61fps。兼容Qt 5.12+/6,LGPL协议。
157 120
|
15天前
|
Windows
【保姆级】.NET Framework 3.5离线安装教程(2026最新实测)
.NET Framework 3.5是微软经典运行库,专为兼容老软件、老游戏设计。Win7自带,Win10/11需手动启用(在线或离线安装)。它与4.x系列并存不冲突,缺它会导致“找不到组件”报错。本文详解两种安装方式及常见问题解决,小白也能轻松上手。(239字)
1874 0
|
10天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
1194 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面