系列: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 学习笔记》
- ChatGPT 玩得很溜,却做不出一个 Agent?
- 多轮对话,让 Agent 记住你(in-token 实证记忆机制)
- 给 Agent 一张「人设卡」(五件套立规矩,AI 导购实测对比)
- 给 Agent 装上「手」——工具调用入门(Function Calling 从猜到查)
- 给 Agent 一库「知识」——RAG 检索入门(补水也能找到保湿)
- AI 答得飞快,一问细节就瞎编?Top-K 调优 + 幻觉测试(附代码)
- 双剑合璧——工具 + RAG,做一个会查会答的客服(从会说到会查会答)
- 一分为多——意图路由与多 Agent 协作(把客服拆成一队)
- 集思广益——多 Agent 投票与结果汇总(用多数人的智慧压住随机性)
- 给 Agent 打个分——评测体系入门(零成本四维质检,编价格一票否决)
- 让 AI 当裁判——LLM Judge 语义评测进阶(规则查不到的「答非所问」,交给 AI 二审)
- 从评测到进化——把评测结果喂回 Prompt(0.75 到 1.0,只差一句 Prompt)
- 从 Demo 到上线——把 Agent 部署成服务(四道闸,把「能跑的脚本」变成「敢上线的服务」)
- 把 Agent 放到云上——容器化部署与上线运营(六步运营闭环,让 Agent 越上线越好用)