集思广益——多 Agent 投票与结果汇总(用多数人的智慧压住随机性)

41 阅读14分钟

系列:AgentScope 2.0 学习笔记 · 第 009 篇

难度:进阶

适合谁:需要「答案稳定」的业务场景开发者(推荐/定价/风控判断),被 AI 回答忽左忽右困扰的人

前置:建议先读 008《一分为多——意图路由与多 Agent 协作》

阅读收获:用多 Agent 投票压住单模型随机性,用汇总产出共识

运行环境:WSL Ubuntu-24.04 + Python 3.11+ + AgentScope 2.x


你问过 AI 客服同一个问题两次吗?第一次它说「这款 268 元」,你回头再问一次,它改口「328 元」。价格差 60 块,语气一样笃定——你心里一定咯噔一下:这客服到底靠不靠谱?

这不是客服的锅,是大模型的天性:随机性。同一个问题,问两次,答案可能不一样。对闲聊无伤大雅,但对客服、推荐、定价、风控这类「答案必须稳」的场景,忽左忽右是要命的——用户不会觉得是概率问题,只会觉得你在忽悠。

怎么压住随机性?一个朴素但有效的思路:多问几个 Agent,然后投票。一个人可能看走眼,三个人同时看走眼的概率就小得多。这就是「集思广益」。


一、008 之后,新问题来了

008 篇,我们把单兵客服拆成了导购 + 售后。但拆完之后,又冒出个新问题:同一个问题,Agent 给出的答案靠谱吗?

大模型有个天生的毛病——随机性。同一个问题,问两次,答案可能不一样。对客服这种场景,答案忽左忽右是要命的:这次说「268 元」,下次说「328 元」,用户还以为你在忽悠。

怎么压住这种随机性?一个朴素但有效的思路:多问几个 Agent,然后投票。一个人可能看走眼,三个人同时看走眼的概率就小得多。这就是「集思广益」。


二、顺序对话的陷阱:互相吹捧

等等,008 篇不是已经有「多 Agent」了吗?为什么还要投票?

因为 008 的「意图路由」是分工(各管一摊),而投票是共识(同一摊,多人一起看)。两者解决的是不同的问题。

那「共识」为什么不能靠「顺序对话」?——让 Agent A 先答,Agent B 看完 A 的答案再答,Agent C 再看 B 的……

我在学习里试过,顺序对话有个致命毛病:后面的人容易顺着前面的人说。A 说「选 B」,B 为了不得罪 A 就说「我也觉得 B」,C 跟着附和——最后「一致通过」,但这种一致是趋同出来的假一致,不是真正的共识。

投票就不同了:每个 Agent 独立作答,互不看到别人的答案。三个独立判断凑到一起,才谈得上「多数人的智慧」。

这里多说一句为什么「独立」这么重要。你去开过那种「先发言再投票」的会就知道:第一个人说完,后面的人多少会被带偏——这就是锚定效应。大模型也一样,你让 B 先看 A 的答案,B 就很难真正独立。所以投票要的就是「背靠背」:在你开口之前,我不知道别人怎么想。 这是共识机制的地基,破不得。


三、投票机制:从简单多数到置信度加权

最朴素的投票是「简单多数」:三票里两票选 B,就定 B。

但简单多数有个问题:它忽略了「信心」。一个 Agent 犹豫着投 B(信心 1 分),另一个 Agent 斩钉截铁投 A(信心 5 分)——简单多数会把 B 当赢家,但 A 那个 5 分才是更可信的信号。

所以进阶做法是置信度加权:让每个 Agent 投票时附上置信度(1~5 分),统计时按置信度求和,而不是简单数票。信心足的票,权重更大。

这一步在代码里只是几行——让模型回复「选项字母 + 置信度」,再用 Counter 按置信度累加。

打个比方你就懂了:简单多数是「一人一票」,置信度加权是「资深专家票重、新手票轻」。同样是一票,含金量可以不一样——这正是它比简单多数聪明的地方。真实业务里,这个思路还能继续延伸:不同模型、不同来源的判断,可以按历史准确率给权重,让「靠谱的」说话更有分量。


四、平票怎么办?交给仲裁

加权之后,还可能平票(两个选项总分相同)。这时候怎么办?

答案是仲裁:再请一个 Agent,把平票的选项摆给它,让它做最终裁决。这就是「投票 → 加权 → 仲裁」的三级共识机制——层层兜底,保证总能出结果。

你会发现这套逻辑很眼熟:简单多数(快)→ 置信度加权(准)→ LLM 仲裁(兜底),和 008 篇路由的「规则优先、LLM 兜底」是同一个味道——能用便宜的手段解决,就别上贵的;便宜的搞不定,再请大模型出山。


五、成本意识:集思广益不免费

投票有个绕不开的代价:烧 Token。三个模型各投一票 = 3 次调用;平票还要仲裁 = 4 次;最后汇总推荐 = 5 次。

我在学习里就优化过这件事:把「汇总」从「再调一次 LLM」改成「规则评分选优」(比如看回答长度、有没有价格、有没有红线词),成本从 4 次降到 3 次。

所以集思广益要权衡:问题重要、答案要稳,才值得花这个钱;简单问题直接单 Agent 答就够。别用大炮打蚊子——这也是整个系列反复出现的一条成本红线。

一个实用判断:需要高稳定性的决策型场景(推荐哪款产品、判是否违规、定最终价格),值得投票;开放式的闲聊、简单问答,单 Agent 直接答。 把投票留给真正需要它的地方。

这里再给你一个「省着投」的小技巧:不是每次都三模型齐上,而是先用便宜的模型试跑,答案一致就收工;有分歧了,再拉贵的模型来投。就像请专家会诊——小毛病社区医生看了就行,疑难杂症才惊动专家团队。成本意识不是抠门,是把每一分钱花在刀刃上。


六、运行环境

  • 系统:WSL Ubuntu-24.04
  • Python:3.11+,虚拟环境 venv
  • 框架:AgentScope 2.x
  • API Key:三把都用——DEEPSEEK_API_KEY / HUNYUAN_API_KEY / BAILIAN_API_KEY(三模型各投一票)
wsl -d Ubuntu-24.04
cd /2026_Study/agentscope/
source venv/bin/activate

export DEEPSEEK_API_KEY="sk-xxxx"
export HUNYUAN_API_KEY="sk-xxxx"
export BAILIAN_API_KEY="sk-xxxx"

python 009_voting.py

七、完整代码(单文件自包含)

保存为 009_voting.py。核心是三级共识:投票 → 加权 → 仲裁 → 汇总推荐。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AgentScope 2.0 · 集思广益——多 Agent 投票与结果汇总
  环节1 投票:三个模型独立判断、各投一票 + 置信度;
  环节2 汇总:置信度加权统计 → 平票则 LLM 仲裁 → 生成最终推荐。
运行环境:WSL Ubuntu-24.04 + Python 3.11+ + AgentScope 2.x
运行命令:python 009_voting.py
"""

import os
import gc
import re
import asyncio
from collections import Counter
import warnings

warnings.filterwarnings("ignore", category=DeprecationWarning)

from agentscope.agent import Agent
from agentscope.message import Msg
from agentscope.model import DeepSeekChatModel, OpenAIChatModel, DashScopeChatModel
from agentscope.credential import DeepSeekCredential, OpenAICredential, DashScopeCredential


# 1. 投票人设(三个模型共用同一人设,保证「同题不同模型」的对比)
VOTE_PROMPT = """
你是「敏辰」的专业护肤导购。
用户问「敏感泛红肌适合什么产品」,请从以下选项中选择【一个】最推荐的:
A. 玻尿酸保湿霜
B. 积雪草舒缓精华
C. 净颜清透洁面乳
请只回复:选项字母 + 置信度(1-5)。例如「B4」表示选 B、置信度 4。
""".strip()


# 2. 模型构建(三模型,stream=False 干净退出)
def build_models():
    deepseek = DeepSeekChatModel(
        credential=DeepSeekCredential(api_key=os.environ["DEEPSEEK_API_KEY"]),
        model="deepseek-v4-flash",
        stream=False,
    )
    hunyuan = OpenAIChatModel(
        credential=OpenAICredential(
            api_key=os.environ["HUNYUAN_API_KEY"],
            base_url="https://tokenhub.tencentmaas.com/v1",
        ),
        model="hy3",
        stream=False,
    )
    bailian = DashScopeChatModel(
        credential=DashScopeCredential(api_key=os.environ["BAILIAN_API_KEY"]),
        model="qwen-max",
        stream=False,
    )
    return {"DeepSeek": deepseek, "混元": hunyuan, "百炼": bailian}


def extract_text(response) -> str:
    for block in response.content:
        if hasattr(block, "type") and block.type == "text":
            return block.text
    return ""


def parse_vote(text: str):
    """解析模型投票输出,返回 (选项字母, 置信度)。"""
    text = text.strip().upper()
    letter = next((c for c in text if c in "ABC"), "?")
    m = re.search(r"[1-5]", text)
    confidence = int(m.group()) if m else 3  # 解析不到则默认 3
    return letter, confidence


async def arbitrate(model, question: str, candidates: list) -> str:
    """平票仲裁:让 LLM 在并列选项中做最终裁决。"""
    arbitrator = Agent(
        name="仲裁Agent",
        model=model,
        system_prompt="你是投票仲裁员,请在并列选项中选一个最合理的,只回复选项字母。",
    )
    prompt = f"针对问题「{question}」,投票平票,请在 {' / '.join(candidates)} 中选一个。"
    msg = Msg(name="user", content=[{"type": "text", "text": prompt}], role="user")
    response = await arbitrator.reply(msg)
    result = extract_text(response).strip().upper()
    return next((c for c in result if c in "ABC"), candidates[0])


async def cleanup(models):
    """收尾:逐个 await self.client.close()(干净退出三件套)。"""
    closed = 0
    for m in models.values():
        client = getattr(m, "client", None)
        if client is None:
            continue
        try:
            await client.close()
            closed += 1
        except Exception:
            pass
    if closed:
        print(f"\n🧹 已关闭 {closed} 个 HTTP 连接池")


# 3. 投票 + 汇总演示
PRODUCT_NAMES = {
    "A": "玻尿酸保湿霜",
    "B": "积雪草舒缓精华",
    "C": "净颜清透洁面乳",
}


async def demo(models) -> None:
    question = "敏感泛红肌适合什么产品?"

    print("=" * 60)
    print("  集思广益:多 Agent 投票 + 结果汇总")
    print("=" * 60)
    print(f"  📝 问题:{question}\n")

    # 环节1:投票(三模型独立判断,各投一票 + 置信度)
    votes = []
    for name, model in models.items():
        agent = Agent(name="投票Agent", model=model, system_prompt=VOTE_PROMPT)
        msg = Msg(name="user", content=[{"type": "text", "text": question}], role="user")
        response = await agent.reply(msg)
        letter, confidence = parse_vote(extract_text(response))
        votes.append((letter, confidence))
        print(f"  🗳️  {name}: 投 {letter}({PRODUCT_NAMES.get(letter, '?')},置信度 {confidence})")

    # 环节2:置信度加权统计
    weighted = Counter()
    for letter, conf in votes:
        weighted[letter] += conf
    print(f"\n{'─' * 60}")
    print("  📊 加权投票统计(按置信度求和):")
    for option, total in weighted.most_common():
        print(f"     {option}({PRODUCT_NAMES.get(option, '?')}):{total} 分")

    # 平票检测 + 仲裁
    top = weighted.most_common()
    if len(top) > 1 and top[0][1] == top[1][1]:
        tied = [o for o, c in top if c == top[0][1]]
        print(f"\n  ⚖️ 平票({' / '.join(tied)})→ LLM 仲裁")
        winner = await arbitrate(models["DeepSeek"], question, tied)
        print(f"  ⚖️ 仲裁结果:{winner}")
    else:
        winner = top[0][0]

    print(f"\n  🏆 共识结果:{winner}({PRODUCT_NAMES[winner]})")

    # 环节3:基于共识结果,生成最终推荐
    summarizer = Agent(
        name="汇总Agent",
        model=models["DeepSeek"],
        system_prompt=(
            "你是「敏辰」护肤顾问。请基于共识选出的产品,生成一段给用户的"
            "最终推荐话术,说明产品、功效、适合肤质,语气亲切,不承诺疗效。"
        ),
    )
    msg = Msg(
        name="user",
        content=[{"type": "text", "text": f"共识结果:{PRODUCT_NAMES[winner]}。请生成最终推荐。"}],
        role="user",
    )
    resp = await summarizer.reply(msg)
    print(f"\n  💬 最终推荐:{extract_text(resp)}")

    if len(set(l for l, _ in votes)) == 1:
        print("\n  💡 三模型一致(共识自然达成)")
    else:
        print("\n  💡 三模型有分歧(已通过加权/仲裁解决)")


def check_keys():
    need = ["DEEPSEEK_API_KEY", "HUNYUAN_API_KEY", "BAILIAN_API_KEY"]
    missing = [k for k in need if not os.environ.get(k)]
    if missing:
        print("❌ 缺少环境变量:" + ", ".join(missing))
        return False
    return True


async def main():
    if not check_keys():
        return

    models = build_models()

    try:
        await demo(models)
        print("\n" + "=" * 60)
        print("  ✅ 多 Agent 投票 + 汇总完成")
        print("=" * 60)
    finally:
        await cleanup(models)
        del models
        gc.collect()


if __name__ == "__main__":
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        warnings.simplefilter("ignore", FutureWarning)
        warnings.simplefilter("ignore", PendingDeprecationWarning)
        asyncio.run(main())

八、代码拆解:三个关键点

① 独立判断是投票的命根子。 三个 Agent 各自 Agent(...) 创建、各自 reply(),互相看不到别人的答案。这一条不能破——一旦让 B 看到 A 的答案再投,就退化成「顺序对话」的趋同陷阱了。

② 置信度让投票更聪明。 parse_vote() 把模型的「B4」解析成(B, 4),Counter 按置信度累加。于是「A 投 5 分」能压过「B 投 1 分 × 3」这种信心不足的多数——这正是简单多数做不到的。

③ 仲裁是最后一道兜底。 平票时,arbitrate() 把并列选项摆给 DeepSeek,让它一句话裁决。注意它跟路由的「LLM 兜底」一个套路:便宜的(计数)先上,贵的(LLM)兜底。


九、运行结果(真实输出)

以下是我在 WSL 里真实跑出来的完整输出:

📝 问题:敏感泛红肌适合什么产品?

🗳️  DeepSeek: 投 B(积雪草舒缓精华,置信度 5)
🗳️  混元: 投 B(积雪草舒缓精华,置信度 5)
🗳️  百炼: 投 B(积雪草舒缓精华,置信度 5)

📊 加权投票统计(按置信度求和):
   B(积雪草舒缓精华):15 分

🏆 共识结果:B(积雪草舒缓精华)

💬 最终推荐:亲,你选的这款积雪草舒缓精华我特别推荐✨
它核心就是积雪草提取物,主打舒缓修护、稳定皮肤状态,很适合皮肤容易泛红、
发痒、换季敏感的时候用。质地清爽,不黏腻,油皮、干皮、混合皮都挺友好……
(后接完整使用建议与「效果因人而异」的稳妥表述)

🧹 已关闭 3 个 HTTP 连接池

这次跑出来是「三模型一致」——DeepSeek、混元、百炼全都投了 B(积雪草舒缓精华),置信度都是 5,加权后 15 分,共识自然达成,没走到仲裁那一步。

有两点值得单独说一下:

① 一致本身就是一种信号。 三个不同厂商、不同训练路线的模型,面对「敏感泛红肌适合什么」同时指向积雪草舒缓精华,说明这个答案在「语义空间」里是高度收敛的——不是某个模型的偶然,而是问题本身就该这么答。这恰恰是投票想捕捉的东西:多数独立的判断,比单次判断更可信。

② 但「一致」不等于「永远不用仲裁」。 这次没触发平票,是因为题目干净、选项区分度高。换成「干皮选保湿霜还是精华」这种边界模糊的题,三个模型就未必这么齐了——那时仲裁才有用武之地。所以代码里的 arbitrate() 不是摆设,是留给「分歧场景」的保险。

最后一行 🧹 已关闭 3 个 HTTP 连接池,说明三把 Key 的模型连接都干净收尾,没有残留报错。


十、总结与下一步

这一篇,你给多 Agent 协作补上了「共识」这一环:

分工(008)= 各管一摊
共识(本篇)= 同一摊,多人一起看,投票定

核心就一句:独立投票 + 置信度加权 + 平票仲裁,用多数人的智慧压住单模型的随机性。 但也别忘了成本红线——集思广益烧 Token,简单问题别硬上。

到这里,我们已经会「生成答案」了,但还不会「判断答案好不好」。下一篇《AgentScope 2.0 学习笔记:给 Agent 打个分——评测体系入门》,我们把「能不能用」量化成分数:设计测试用例、跑分、看哪条红线被踩了。敬请期待。


十一、写在最后:投票是手段,稳定才是目的

写到最后,想把这篇的「为什么」再拎出来说一遍。投票、加权、仲裁,这些机制听着高级,但它们的终极目标只有一个:让该稳的答案稳下来。 推荐哪款产品、判一条消息违不违规、给一个报价——这些场景里,答案飘一次,信任就碎一块。

这也是我判断「要不要上投票」的标准:答案不稳的代价大不大? 代价大,就值得为它花 Token;代价小,单 Agent 直接答。投票不是炫技,是给「重要决策」买的一份保险。

如果你正在做一个「答案必须稳」的业务场景——智能推荐、自动定价、内容风控、审核判断——这套共识机制的设计和调优有挺多讲究(K 个模型合适、置信度怎么标定、仲裁选谁)。卡住了,或者想聊聊怎么落地,欢迎来评论区讨论。下一篇「评测体系」见。


本文为「AgentScope 2.0 学习笔记」系列第 009 篇,代码已通过 py_compile 语法校验,运行环境见第六节。

GitHub 仓库

系列 : 《AgentScope 2.0 学习笔记》