一、引言

在群面模拟系统中,AI候选人需要像真实面试者一样"抢话"发言——既不能过于沉默,也不能喧宾夺主。如何让多个AI智能体在讨论中自然地轮流发言、有节奏地推进讨论?本文从源码层面拆解AgentEngine的核心设计:异步循环收集、五维权重评分和发言阈值机制。

二、整体架构概览

2.1 线程模型

系统配置了两个专用的线程池:

@Bean(name = "agentExecutor")

public Executor agentExecutor() {

    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    executor.setCorePoolSize(10);    // 核心线程

    executor.setMaxPoolSize(50);     // 最大线程

    executor.setQueueCapacity(200);  // 队列容量

    executor.setThreadNamePrefix("agent-");

    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

    return executor;

}

@Bean(name = "speechExecutor")

public Executor speechExecutor() {

    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    executor.setCorePoolSize(5);

    executor.setMaxPoolSize(20);

    executor.setQueueCapacity(100);

    executor.setThreadNamePrefix("speech-");

    return executor;

}

- agentExecutor:负责运行每个房间的Agent主循环(决策循环)

- speechExecutor:负责执行具体智能体的发言(LLM调用 + WebSocket广播)

这种分离的核心思想是:决策循环是轻量级的纯计算,发言是重量级的LLM调用,互不阻塞。

 三、异步循环收集机制

3.1 每房间一个独立的事件循环

@Async("agentExecutor")

public void runAgentLoop(String roomId, Agent.AgentContext context) {

    while (true) {

        // 1. 检查房间状态

        String status = roomOps.getRoomStatus(roomId);

        if (status == null || !"ACTIVE".equals(status)) break;



        // 2. 检查阶段是否该自动推进

        if (loopPhase == Phase.ICEBREAK && hasAllCandidatesSpokenInIcebreak(roomId)) {

            roomOps.changePhase(roomId, Phase.STATEMENT.name());

            Thread.sleep(300);

            continue;

        }



        // 3. 收集所有智能体的发言请求

        List<SpeakRequest> requests = collectSpeakRequests(roomId, freshContext);

        pendingRequests.put(roomId, requests);



        // 4. 如果有人正在发言,跳过本轮

        if (speakingInProgress.get(roomId)) {

            Thread.sleep(300);

            continue;

        }



        // 5. 决策下一个发言人

        SpeakRequest winner = decideNextSpeaker(roomId, requests);



        // 6. 异步执行发言

        if (winner != null) {

            executeAgentSpeech(roomId, winner, freshContext);

        }



        // 7. 节流等待

        Thread.sleep(500);

    }

}

关键设计点:

- @Async("agentExecutor")让每个房间的循环运行在独立的线程中

- 轮询模式:每500ms一个周期,而不是事件驱动——这避免了复杂的并发同步

- speakingInProgress 锁:用ConcurrentHashMap做轻量级互斥,确保同一时间只有一个智能体在发言

- lastCompletedSpeaker:记录上一个发言者,避免连续发言

3.2 收集发言分数的流程

private List<SpeakRequest> collectSpeakRequests(String roomId, Agent.AgentContext context) {

    for (Agent agent : agents) {

        // 破冰阶段每人只说一次

        if (currentPhase == Phase.ICEBREAK && hasSpokenInCurrentPhase(...)) {

            continue;

        }



        // 构建上下文(当前阶段、已用时间、沉默时长、最近事件)

        SpeakContext speakContext = new SpeakContext(

            currentPhase,

            elapsedTime,

            silenceDuration,

            recentEvents,

            Map.of()

        );



        // 调用智能体的评分函数

        double score = agent.calculateSpeakScore(speakContext);



        // 过滤:超过阈值才加入候选池

        if (score >= threshold) {

            requests.add(request);

        }

    }



    // 破冰阶段保底:如果没人达标,选最高分

    if (currentPhase == Phase.ICEBREAK && requests.isEmpty()) {

        requests.add(allScores.get(0));

    }



    return requests;

}

为什么是轮询而不是推送?

1. 解耦:AgentEngine不需要维护与每个Agent的连接

2. 一致性:在单次循环中收集所有分数,避免因并发导致的时间窗口不一致

3. 容错:某个Agent计算出错不影响其他Agent的评分收集

 四、五维权重评分设计

4.1 评分公式

score = W1_ROLE(0.25) × roleScore

      + W2_CONTENT(0.25) × contentRelevance

      + W3_EMOTION(0.15) × emotionActivation

      + W4_TIMING(0.20) × timingWindow

      + W5_PHASE(0.15) × phaseWeight

五维权重之和 = **1.0**,其中角色和内容权重最高。

4.2 维度一:角色匹配度(Role Score)- 权重0.25

不同角色在不同阶段有不同的发言倾向:

| 角色 | 破冰 | 陈述 | 辩论 | 收敛 | 总结 |

|------|------|------|------|------|------|

| **Leader** | 0.7 | 0.8 | **0.9** | **0.9** | 0.8 |

| **Coordinator** | **0.8** | 0.6 | 0.5 | **0.9** | 0.7 |

| **Viewpoint** | 0.3 | **0.9** | **0.9** | 0.5 | 0.6 |

| **Executor** | 0.5 | 0.5 | 0.6 | **0.7** | 0.5 |

| **Supporter** | 0.6 | 0.4 | 0.3 | **0.6** | 0.4 |

设计意图:

- **Leader**在辩论和收敛阶段最有发言动力(引导方向、总结决策)

- **Coordinator**在破冰和收敛阶段最活跃(破冰暖场、调和分歧)

- **Viewpoint**在陈述和辩论阶段最高(输出观点、深度分析)

- **Executor**在收敛阶段关注方案可行性

- **Supporter**整体分数偏低,体现"补位"角色特性

4.3 维度二:内容相关性(Content Relevance)- 权重0.25

private List<SpeakRequest> collectSpeakRequests(String roomId, Agent.AgentContext context) {

    for (Agent agent : agents) {

        // 破冰阶段每人只说一次

        if (currentPhase == Phase.ICEBREAK && hasSpokenInCurrentPhase(...)) {

            continue;

        }



        // 构建上下文(当前阶段、已用时间、沉默时长、最近事件)

        SpeakContext speakContext = new SpeakContext(

            currentPhase,

            elapsedTime,

            silenceDuration,

            recentEvents,

            Map.of()

        );



        // 调用智能体的评分函数

        double score = agent.calculateSpeakScore(speakContext);



        // 过滤:超过阈值才加入候选池

        if (score >= threshold) {

            requests.add(request);

        }

    }



    // 破冰阶段保底:如果没人达标,选最高分

    if (currentPhase == Phase.ICEBREAK && requests.isEmpty()) {

        requests.add(allScores.get(0));

    }



    return requests;

}

当前实现使用模拟值,实际可演化为:

- 基于向量相似度计算当前话题与Agent知识领域的相关性

- 基于NLP分析最近的讨论主题是否匹配Agent的角色定位

4.4 维度三:情绪激活度(Emotion Activation)- 权重0.15

protected double calculateEmotionActivation(SpeakContext context) {

    Double attackLevel = state.getEmotionVector().get("attackLevel");

    return attackLevel != null ? attackLevel : 0.3;

}

基于MBTI人格的情绪向量:

| 情绪维度 | 含义 | 计算方式 |

|---------|------|---------|

| tension | 紧张度 | E→0.4, I→0.5 |

| confidence | 自信度 | N→0.7, S→0.5 |

| **attackLevel** | 攻击性 | E+T→0.6, 其他→0.3 |

| stability | 情绪稳定度 | J→0.7, P→0.5 |

例如:ENTJ(攻击性0.6)在辩论阶段会比INFP(攻击性0.3)更主动发言。

4.5 维度四:时机窗口(Timing Window)- 权重0.20

protected double calculateTimingWindow(SpeakContext context) {

    long timeSinceLastSpeak = System.currentTimeMillis() - state.getLastSpeakTime();

    if (timeSinceLastSpeak < 3000) {

        return 0.1;   // 刚说完,冷却中

    } else if (timeSinceLastSpeak < 5000) {

        return 0.5;   // 短暂冷却期

    } else if (context.silenceDuration() > 400) {

        return 0.9;   // 冷场了,抢话机会

    }

    return 0.6;       // 正常状态

}

设计逻辑:

1. 刚发言完3秒内→ 分数极低(0.1),配合冷却期防止连续发言

2. 3-5秒缓冲期→ 中等分数(0.5),可以再次发言但不优先

3. 沉默超过400ms→ 高分(0.9),防止讨论冷场

4. 正常状态→ 基准分(0.6)

4.6 维度五:阶段权重(Phase Weight)- 权重0.15

protected double calculatePhaseWeight(Phase phase) {

    return switch (phase) {

        case ICEBREAK -> 0.3;      // 破冰阶段,发言动力低(每人只说一次)

        case STATEMENT -> 0.8;     // 陈述阶段,每个人都想输出观点

        case DEBATE -> 0.7;        // 辩论阶段,高参与度

        case CONVERGENCE -> 0.5;   // 收敛阶段,趋于稳定

        case SUMMARY -> 0.9;       // 总结阶段,都想做总结者

    };

}

- 破冰(0.3)最低——每人只说一次自我介绍

- 总结(0.9)最高——每个人都想做那个画句号的人

- 陈述(0.8)和辩论(0.7)次高——核心讨论环节

五、发言阈值与仲裁机制

5.1 双层阈值设计

private static final double SPEAK_THRESHOLD = 0.65;        // 常规阈值

private static final double ICEBREAK_SPEAK_THRESHOLD = 0.50; // 破冰阈值

为什么是0.65?

假设一个普通场景:角色分0.6 + 内容分0.5 + 情绪分0.3 + 时机分0.6 + 阶段分0.8,加权计算:

score = 0.25×0.6 + 0.25×0.5 + 0.15×0.3 + 0.20×0.6 + 0.15×0.8

     = 0.15 + 0.125 + 0.045 + 0.12 + 0.12

     = 0.56

要达到0.65,需要在多个维度都处于活跃状态,例如:

Leader辩论阶段:0.25×0.9 + 0.25×0.7 + 0.15×0.6 + 0.20×0.9 + 0.15×0.7

              = 0.225 + 0.175 + 0.090 + 0.180 + 0.105

              = 0.775  ← 远超阈值,很可能会发言

设计目标:

- 0.65作为"软门槛":智能体需要"有一定的表达欲"才能进入候选池

- 破冰阶段降低到0.50:因为破冰的自我介绍不需要太强的表达动机

- 结合冷却期惩罚(×0.3):刚发言完的智能体分数会被压到0.2以下,自然进入静默

5.2 发言仲裁器

private SpeakRequest decideNextSpeaker(String roomId, List<SpeakRequest> requests) {

    if (requests == null || requests.isEmpty()) return null;

    requests.sort((a, b) -> Double.compare(b.getSpeakScore(), a.getSpeakScore()));

    // 检查发言时间比例,确保无人超过35%

    for (SpeakRequest request : requests) {

        if (currentSpeaker 中已有此人) continue;  // 正在发言,跳过

        if (是上一个发言者) continue;              // 避免连说

        double ratio = getAgentSpeakingTime(events, request.getAgentId()) / totalSpeakingTime;

        if (ratio < MAX_SPEAK_RATIO) {             // MAX_SPEAK_RATIO = 0.35

            return request;

        }

    }

    // 备选:找非重复发言者

    return selectNonRepeatingWinner(requests, lastSpeakerId);

}

仲裁规则优先级:

1. **非重复优先**:避免同一人连续发言

2. **35%时间比例上限**:防止某一智能体垄断讨论

3. **分数排序**:同等条件下选最高分

5.3 冷却期机制

// Agent发言后立即进入冷却

state.setInCooldown(true);

new Thread(() -> {

    Thread.sleep(3000);  // 3秒冷却

    state.setInCooldown(false);

}).start();

// 冷却期分数打三折

if (state != null && state.isInCooldown()) {

    score *= 0.3;

}

这意味着冷却期内的智能体分数被压到0.2以下,基本不可能再次发言,强制轮流。

六、完整的发言决策流程

┌─────────────────────────────────────────────────┐

│              AgentEngine.runAgentLoop()          │

│                 每500ms一个周期                   │

├─────────────────────────────────────────────────┤

│                                                   │

│  ┌────────────────┐     ┌──────────────────┐     │

│  │ 1. 检查房间状态  │────>│ 2. 收集所有Agent  │     │

│  │ 是否为ACTIVE    │     │ 的发言分数        │     │

│  └────────────────┘     └────────┬─────────┘     │

│                                  │               │

│                                  ▼               │

│  ┌─────────────────────────────────────┐         │

│  │ 3. 每个Agent计算五维加权分数          │         │

│  │    role × 0.25 + content × 0.25     │         │

│  │  + emotion × 0.15 + timing × 0.20   │         │

│  │  + phase × 0.15                     │         │

│  └────────────────┬────────────────────┘         │

│                   │                              │

│                   ▼                              │

│  ┌─────────────────────────────────────┐         │

│  │ 4. 阈值过滤 (score ≥ 0.65)          │         │

│  │    破冰阶段: ≥ 0.50                 │         │

│  │    冷却期惩罚: ×0.3                 │         │

│  └────────────────┬────────────────────┘         │

│                   │                              │

│                   ▼                              │

│  ┌─────────────────────────────────────┐         │

│  │ 5. 仲裁器决策下一个发言人            │         │

│  │    - 按分数排序                     │         │

│  │    - 跳过当前发言者/上一位发言者     │         │

│  │    - 检查时间比例 ≤ 35%             │         │

│  └────────────────┬────────────────────┘         │

│                   │                              │

│                   ▼                              │

│  ┌─────────────────────────────────────┐         │

│  │ 6. 异步执行发言(speechExecutor)      │         │

│  │    - 调用DeepSeek API生成内容        │         │

│  │    - WebSocket广播给房间所有成员     │         │

│  │    - 持久化Event到数据库             │         │

│  └─────────────────────────────────────┘         │

└─────────────────────────────────────────────────┘

```

七、设计总结与思考

7.1 为什么这样设计?

| 设计目标 | 实现方式 |

|---------|---------|

| **公平性** | 35%时间上限 + 冷却期 + 非重复优先 |

| **自然感** | 角色阶段匹配 + 冷场抢话 + 随机扰动 |

| **多样性** | 16种MBTI × 5种角色,不同人格发言风格各异 |

| **可扩展** | 五维权重可调,阈值可配,Agent可热插拔 |

7.2 可优化方向

1. **动态阈值**:根据房间人数、阶段进展自动调整阈值

2. **情感衰减**:长期未发言的Agent应获得情感补偿分

3. **内容相关性**:引入NLP语义匹配替代随机模拟

4. **打断检测**:当前已支持用户打断,未来可加入Agent间打断机制

7.3 与真实群面的映射

| 真实群面 | 系统实现 |

|---------|---------|

| 面试者性格各异 | MBTI 16型人格映射到5维向量 |

| 有人喜欢抢话 | 攻击性(attackLevel)高 → 情绪激活度高 |

| 有人喜欢控场 | Leader角色在辩论收敛阶段分数高 |

| 冷场时需要有人救场 | silenceDuration > 400ms → 时机分0.9 |

| 不能一个人说太多 | 35%时间比例上限 + 冷却期 |

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐