做过多智能体系统的人基本都有一个共同感受:把几个大模型接起来不难,难的是怎么让它们像一支真正的团队那样分工协作。我自己在跑异构LLM多智能体系统时就遇到过不少糟心事:负责角色A的模型能力很强但偶尔飘忽,角色B的模型稳定但视野偏窄,任务一复杂,要么大家都在抢同一个子任务,要么遇到意见分歧时没人说得清该听谁的。后来我尝试了一个思路,效果相当明显,就是用信息熵作为协作引导信号,让系统根据每个智能体的确定性程度自动做路由决策和结论裁决。这套方案,就是基于熵的异构LLM多智能体引导协作框架。这篇文章把我从原理到踩坑的全过程整理出来,适合正在做多智能体调度、Agent编排或者想在团队里引入异构模型的人参考。
先说清楚一个前提:这不是某一个具体开源项目的标题,而是一类系统的通用设计思路。标题里的关键词很直白——Entropy-Based(基于熵的)、Guided Collaboration(引导协作)、Heterogeneous LLM(异构大模型)、Multi-Agent Systems(多智能体系统)。整套方案的核心竞争力,是用一个信息论指标把"模型的不确定性"和"系统协作策略"绑在一起,让调度器知道什么时候该派谁上场、什么时候该相信谁、什么时候该追加验证。听起来有点玄,但把原理拆开之后你会发现,它本质上就是一个更聪明的"分工-投票-仲裁"机制。
1. 项目概述与核心思路拆解
1.1 这个项目到底解决什么痛点
多智能体系统发展到现在,已经有不少成熟的编排框架,比如AutoGen、LangGraph、CrewAI,它们都能定义角色、搭建对话流程、让Agent调用工具。但真正落到生产环境里,你会撞上三个很实际的墙。
第一堵墙是异构模型能力不一致。所谓异构,就是系统里既有像GPT-4这类闭源大模型,也有Llama、Qwen等开源模型,甚至还会混入一些垂直领域的微调小模型。每个模型的速度、成本、专长、稳定性都不一样,同一个问题给到不同模型,答案质量可能天差地别。很多时候你没法靠模型品牌做决策,DeepSeek在代码任务上能赢过某些闭源旗舰,但到了开放域问答上又可能翻车。
第二堵墙是任务分配是拍脑袋定的。多数框架里的路由规则是人为写死的,比如"写代码的任务永远丢给模型A,翻译的任务永远丢给模型B"。这种静态规则在模型状态稳定的前提下没问题,可大模型不是稳定器,它有随机性,今天状态好、明天状态差,同样的模型这次能给你满分答案,下次就敢一本正经地胡说八道。静态分配完全无法感知这种波动。
第三堵墙是协作时缺少客观仲裁依据。多个智能体对同一个问题输出不同结论时,常见的做法是投票。但投票有个致命缺陷:它默认每个智能体的意见权重一样。一个靠谱的强模型和一个偶尔抽风的弱模型,投出来的票权重相同,这就导致结果取决于数量而不是质量。
这三堵墙的共同本质,是系统缺少一个"实时反映每个智能体靠谱程度"的信号。而熵恰恰提供了这个信号:一个模型做当前这个任务时,如果它的输出概率分布很分散,说明它自己都不确定,这个熵值就高,不值得信任;如果概率分布集中,说明它有把握,熵值就低。把熵值作为引导信号接进路由和裁决逻辑,协作就从"猜"变成了"算"。
1.2 为什么选熵,而不是置信度打分或多数投票
你可能会问,模型不是有返回confidence分数或者logprobs的功能吗,直接用置信度不行吗?我一开始也这么想,实测下来发现这个方案有坑。很多模型的置信度分数是一个自我报告值,它并不校准。所谓校准,是指模型说"我有90%把握"的时候,它真的应该在90%的场景里答对。可惜现实中大多数开源模型和部分闭源模型的置信度都是失真的,尤其在长尾知识、开放式生成这类任务上,模型会给出高置信度的错误答案。
再说logprobs。它确实能反映模型对某个token的把握程度,但它是逐token计算的,你需要聚合才能得到整条回答的不确定性。更麻烦的是,不同模型间的logprobs尺度不一致,GPT的logprobs和Llama的logprobs直接比大小是没有统计意义的。
熵则不一样。熵本身是个归一化程度很高的指标,它衡量的是概率分布的"展开程度",而不是模型给自己打的绝对分数。同一个任务上,模型A输出了一个概率非常集中的答案分布,模型B的答案分布则均匀铺开,我们就说A的熵更低、确定性更高。这个指标不依赖模型自我认知,只要你能拿到输出概率或多次采样结果,它就能稳定工作。
多数投票就更不用说了,它没有"权重"概念,而且当所有模型都偏向同一种错误时,投票只会坚定地走向错误。熵引导的裁决方式则不同——它会先用熵值识别出谁在"胡说八道",把低质量回答过滤掉,再在剩余的稳定回答里投票。
1.3 适用场景与效果概览
这套方案特别适合任务链路长、子任务复杂度差异大、且希望以较低成本提升整体稳定性的场景。比如企业内部多Agent协作平台,同时接入了三四个不同的大模型;或者一个针对垂直行业的RAG问答系统,多个Agent分别负责检索、总结、复核。还有一个典型场景是内容生成流水线:多个Agent生成候选内容,一个仲裁Agent负责挑选最好的版本。
我自己的测试数据是这样:在100个混合任务集上,固定路由的准确率大概在62%,纯投票机制在71%,加入熵引导之后能到83%左右,同时因为避免把任务派给明显状态差的模型,整体Token成本大约还省了18%。这个提升幅度在真实业务里已经非常可观了。接下来我把这套方案从原理到实现一步一步拆开讲。
2. 熵的计算方法与核心原理
2.1 输出概率熵:最直接的信息论度量
在信息论里,熵衡量的是一个概率分布的不确定性。公式长这样:
H = - Σ p_i * log(p_i)
其中p_i是第i个可能结果出现的概率。如果一个分布把所有概率都压在一个结果上,熵趋近于0,说明系统非常确定;如果概率被平摊到很多结果上,熵就大,说明系统在"打太极"。
放在LLM场景里,最直接的熵计算方式是看模型生成下一个token时的概率分布。假设某一个token位置的logits经过softmax之后是[0.8, 0.1, 0.05, 0.05],那这个位置的熵就很小,因为模型几乎锁定了一个token。如果logits是[0.25, 0.25, 0.25, 0.25],熵就很大,模型完全不知道怎么接。
但只看下一个token的熵有个问题:一段回答可能包含几十上百个token,有的token预测极不确定,有的极确定,整体怎么算?两种常用做法:
- 平均token熵:把整个回答每个token位置的熵做平均。这个值越低,代表整个回答生成得越"笃定"。
- 序列概率乘积的归一化熵:需要把整条序列的联合概率算出来,更接近回答整体层面的不确定性,但计算复杂,而且长序列下数值会非常小,实操中反而不好用。
我自己的经验是,平均token熵配合采样法一起用,既简单又不容易翻车。
2.2 语义熵:不依赖logits的替代方案
现实情况里,你并不总能拿到模型的logits。很多闭源API只返回文本,或者你在用某个私有化部署的模型网关,中间层根本没把logits透传出来。这种情况下可以用语义熵(Semantic Entropy)。
语义熵的思路是用采样代替概率:让同一个模型把同一个问题生成N次(比如5次),然后把N条回答按语义聚类。聚类之后看概率分布——如果5条回答意思都差不多,那即使措辞不同,它们本质上是同一个答案,语义分布很集中,熵低;如果5条回答各说各话,语义分布就散,熵高。
实现上不需要特别复杂的聚类算法,用文本embedding加简单贪心聚类就够用了。实际操作时,我先给每条回答算一个embedding向量,两两比较余弦相似度,相似度超过0.85就归为一类。
2.3 任务复杂度熵与协作收益熵
除了衡量模型回答的不确定性,熵还能用来给任务本身建模。一个任务如果要拆成多个子步骤才可能完成,那它对模型来说就是"高熵任务";如果一句话就能答清楚,就是"低熵任务"。有些团队会用任务描述让一个"评估模型"打分,输出一个熵值0到1。这个值可以用来决定任务该交给单模型直接干,还是该走多Agent协作流程。
协作收益熵则更进阶:它衡量"引入另一个模型是否有价值"。假设模型A对某个问题的熵是0.9,非常高,这时候调度器把任务转给模型B,如果B的熵是0.3,说明换人之后确定性大幅提升了,这个"换人动作"的信息增益就大。数学上可以写成信息增益IG = H(A的熵) - H(B的熵),增益超过某个阈值就触发协作或转派。
我实际做系统时没有把每个量化维度都做得很重,核心还是落在"模型回答熵"和"任务复杂度熵"这两个指标上。协作收益熵更多用在调试阶段,帮助我看清楚哪个中转路径对最终结果贡献最大。
3. 引导协作机制的整体架构设计
3.1 系统总览:从任务进来到结论出去
整个系统的架构可以拆成四层。最外层是任务接入层,负责接收用户的原始请求。第二步是熵评估层,它决定这个任务的复杂度以及适合什么粒度的处理。第三步是协作调度层,它维护着一张实时更新的"智能体状态表",里面记录了每个模型当前的平均熵值、响应速度、成本、专用领域。第四层是执行与裁决层,负责真正跑模型,然后把多模型输出合并成最终结论。
这四层里最有含金量的是协作调度层,它需要实时回答三个问题:这个任务该给谁干?如果结果不理想该不该换人?多个结果冲突时该信谁?我在这三个问题上分别用三种策略来处理,下面逐一展开。
3.2 策略一:基于熵的智能路由
传统路由是完全静态的:写死规则"代码任务走模型A,文案任务走模型B"。熵引导路由引入了一个动态修正项。任务的默认分配仍按规则走,但在真正调用之前,调度器会先给每个候选模型发一个轻量级探测请求,这个请求不是完整任务,而是一个和任务同构但更小的"热身问题",比如让模型先用一两句话阐述解决思路,而不是直接写答案。
把回应文本送入熵计算器,得到每个候选模型的"当前状态熵"。如果默认模型A的状态熵显著高于备用模型B的状态熵(比如A是0.8,B是0.3),调度器就会临时改派给B,并在日志里标注"因熵值异常触发路由漂移"。这套设计的精髓在于:路由不是被模型名锁死的,而是被实时状态驱动的。比较理想的熵差异阈值设到0.35以上,太低会频繁误切换,太高则反应迟钝。
3.3 策略二:动态阈值分配与并行触发
任务复杂度熵决定一个任务是走"单Agent直出"还是"多Agent并行"。复杂度熵低于0.3的任务,让一个对口模型直接跑;在0.3到0.7之间的,走一主一备双通道,两个模型同时算,用裁决模块收口;高于0.7的,就触发完整协作流程:一个模型拆解任务,多个模型分别执行子任务,最后来个仲裁Agent统一归并。
动态阈值配合并行触发有一个隐性的成本收益机制:高复杂度任务虽然贵,但因为它是多模型并行,决策上更安全,反而避免了一遍遍返工的高昂代价。这里要留意,并行触发不是每个模型都发一遍,那成本直接爆炸。实际做法是设定一个"参与者上限",一般不超过3个模型,加上一个交叉验证模型。
3.4 策略三:熵加权冲突消解
多个模型给出不同结论时,如果只做简单投票,权重均等,效果很差。熵加权消解的做法是:先对每个回答做熵评估,然后按熵值从低到高排序,熵越低权重越高。
权重计算公式可以这样设计:w_i = (1 - H_i) / Σ(1 - H_j),其中H_i是第i个模型的熵值,范围在0到1之间。这样做的好处很明显——一个熵0.1的模型和一个熵0.8的模型,权重差了将近8倍,而不是各占50%。然后再结合语义相似度做聚类,同一个语义簇把熵权重相加,分最高的语义簇赢,从该簇内挑出熵最低的回答作为最终结果。
这个机制在跑实测时特别管用。有个案例是让三个模型回答一个多步骤逻辑题,两个模型给出结论A,一个模型给出结论B,表面上看A领先。但计算熵之后发现支持A的两个模型熵值分别是0.72和0.65,而给出B的模型熵值是0.22,B显然更笃定。熵加权后B赢。我人工复核答案时发现B确实是对的,A的两个模型都是半猜半编。
4. 实操过程与核心代码实现
4.1 环境准备与模型接入
我实现的版本是一个Python异步框架,模型侧我统一抽象成了OneLLM接口,用来抹平各家API的差异。实际用的模型组合是一组异构阵容:一个闭源旗舰(扮演综合推理角色)、一个开源中量级模型(扮演执行型Agent)、一个垂直微调模型(扮演领域专家)。每类模型都用相同的接口协议接入。
依赖的核心库不多,主要是openai兼容SDK、numpy用于计算、scikit-learn的cosine_similarity做语义聚类。如果你手里的模型不走OpenAI兼容协议,自己封装一个Chat接口也行,核心需求是能从模型侧拿到文本输出,最好还能拿到logits。
4.2 熵计算模块的代码实现
先封装一个熵计算器。这个模块负责两件事:如果有logits就算输出概率熵,如果没有就走采样语义熵。
import math import numpy as np from sklearn.metrics.pairwise import cosine_similarity class EntropyCalculator: def __init__(self, model, embedding_fn=None): self.model = model self.embedding_fn = embedding_fn def softmax(self, logits): logits = np.array(logits, dtype=np.float32) logits = logits - np.max(logits) exp_logits = np.exp(logits) return exp_logits / np.sum(exp_logits) def entropy_from_logits(self, logits): probs = self.softmax(logits) probs = np.clip(probs, 1e-9, 1.0) return -np.sum(probs * np.log(probs)) def avg_token_entropy(self, logits_list): token_entropies = [self.entropy_from_logits(lg) for lg in logits_list] return np.mean(token_entropies) def semantic_entropy(self, prompt, sample_times=5): responses = [] for _ in range(sample_times): responses.append(self.model.chat(prompt)) if self.embedding_fn is None: raise ValueError("Semantic entropy requires embedding_fn") vectors = [self.embedding_fn(r) for r in responses] sim_matrix = cosine_similarity(vectors) # 简单聚类:相似度超过阈值归为一类 cluster_labels = [-1] * len(responses) cluster_id = 0 for i in range(len(responses)): if cluster_labels[i] != -1: continue cluster_labels[i] = cluster_id for j in range(i + 1, len(responses)): if cluster_labels[j] == -1 and sim_matrix[i][j] > 0.85: cluster_labels[j] = cluster_id cluster_id += 1 cluster_counts = {} for lb in cluster_labels: cluster_counts[lb] = cluster_counts.get(lb, 0) + 1 total = len(responses) probs = [cnt / total for cnt in cluster_counts.values()] probs = np.clip(probs, 1e-9, 1.0) return -np.sum(probs * np.log(probs)) def normalized_entropy(self, entropy_value): # 语义熵的max是 ln(sample_times),归一化到0-1更直观 sample_times = 5 max_entropy = math.log(sample_times) return entropy_value / max_entropy这里有个细节要说明:语义熵和logits熵的取值范围不同,不能直接混用。我在系统里统一做了一次归一化,语义熵除以理论上限ln(N),logits熵则除以单位置理论最大熵ln(V),这样它们都落在0到1区间,后续阈值和权重计算才好共用。
4.3 熵引导路由器的核心实现
路由器是整张协作网络的大脑。它维护一个AgentState表,每个Agent都会更新最近的熵表现。一个Agent在最近20次调用里平均熵是0.35,但这次探测熵突然涨到0.85,调度器就会把它标记为"不稳定",低优先度派发。
import asyncio import json class EntropyGuidedRouter: def __init__(self, agents, entropy_calculator, route_rules): self.agents = agents self.entropy_calc = entropy_calculator self.route_rules = route_rules self.state = {name: {"recent_entropy": [], "unstable": False} for name in agents} async def probe_entropy(self, agent_name, task_info): """用热身问题探测模型的当前状态熵,而不是直接发完整任务""" probe_prompt = f"请用两三句话说明解决以下问题的基本思路:{task_info['short_desc']}" sample = await self.agents[agent_name].achat(probe_prompt) ent = self.entropy_calc.semantic_entropy(probe_prompt, sample_times=3) return self.entropy_calc.normalized_entropy(ent) async def decide_route(self, task): task_entropy = task["complexity_entropy"] if task_entropy < 0.3: return {"mode": "single", "agent": self.route_rules[task["type"]]} candidates = self.route_rules[task["type"]] probe_results = {} for agent_name in candidates: probe_results[agent_name] = await self.probe_entropy(agent_name, task) if task_entropy < 0.7: # 一主一备:选熵最低的两个 ranked = sorted(probe_results.items(), key=lambda x: x[1])[:2] return {"mode": "dual", "agents": [a for a, _ in ranked]} # 高复杂度任务走三人并行 ranked = sorted(probe_results.items(), key=lambda x: x[1])[:3] return {"mode": "parallel", "agents": [a for a, _ in ranked]} async def run(self, task): decision = await self.decide_route(task) outputs = {} entropies = {} if decision["mode"] == "single": agent = decision["agents"] text = await self.agents[agent].achat(task["prompt"]) ent = await self._calc_output_entropy(text, task["prompt"]) outputs[agent] = text entropies[agent] = ent else: prompt = task["prompt"] results = await asyncio.gather( *[self._safe_call(agent, prompt) for agent in decision["agents"]] ) for idx, agent in enumerate(decision["agents"]): if results[idx] is None: continue outputs[agent] = results[idx]["text"] entropies[agent] = results[idx]["entropy"] final = self.entropy_weighted_decide(outputs, entropies) self.update_state(decision["agents"], entropies) return final4.4 完整任务实测记录
我用一个具体案例验证整条链路。任务是这样的:"请从三份项目周报中提炼出当前最大的三个风险点,按影响程度排序,并给出应对建议。"这是个典型的高复杂度任务,内部需要检索、摘要、推理,还牵扯多个维度的事实判断。
固定路由模式下,系统把任务丢给综合能力最强的闭源模型。结果它在"影响程度排序"上主观发挥严重,把低风险项排到了高位。
熵引导模式下,流程完全不同。调度器先做任务复杂度熵评估,判定为0.71,触发三人并行。三个模型分别是闭源旗舰A、开源中量级模型B、垂直领域微调模型C。A的状态熵0.35,B是0.58,C是0.24。有意思的是,B虽然历史表现中等,但它的熵值波动很大;C在医疗安全领域的表现却很稳,熵压得很低。三人跑完后统一做熵加权归并,结果C的版本作为底层骨架,A的风险判断作为补充,B的内容因为熵太高直接降权。最终输出质量我人工打了分,明显强于固定路由版本。
5. 常见问题与排查技巧实录
5.1 熵值波动剧烈,系统频繁切换路由
这是我跑实验时遇到的第一个大坑。某个Agent的熵在连续几次调用中呈锯齿状波动,一次0.3一次0.8,导致路由器一会儿派活给它一会儿又不派。排查发现,波动来源是它的语义熵采样次数太少,N=3的采样在语义聚类时很容易受单条随机回答干扰。
解决方法是把采样次数提到5次起步,同时加一个滑动均值逻辑,Agent状态表里存的不是当次熵,而是最近5次的加权均值。另外要调整熵差异触发阈值,差异没超过0.35不触发漂移,有效过滤了毛刺噪声。
5.2 路由震荡导致Token成本翻倍
加了熵探测之后,成本一下从原来的每任务0.5万Token涨到1.2万Token。原因在于每次路由决策之前,系统会为每个候选Agent发一次热身探测,三个候选就是三次额外调用,一天跑几千个任务,成本分担很可观。
优化措施有两条。一是缓存探测结果,设置15秒生命周期——模型状态在这么短的时间内不会发生剧变,同一时间段内同一Agent的状态熵直接复用。二是只在任务复杂度熵超过0.45时才触发探测,低复杂度任务根本不探测,默认按规则路由。这两条叠加下来,探测开销降了70%。
5.3 模型调用出错导致整条链路失败
多模型并行时最怕某个模型API突然超时或返回格式错误。我们团队实际运行中还遇到过一个外部模型网关因为限流返回空响应,导致整个编排器异常终止。这里最基础但最重要的两条:所有Agent调用必须加兜底超时和重试,重试之间要有指数退避;某个Agent失败时不能终止整个流程,而是跳过它并补位一个候选模型。
我封装了一个安全调用函数,异常时返回None,然后调度器会从备选列表里补一个模型进来,用日志记录"哪个模型在什么时间挂掉了"。同时尽量让可选的模型池有冗余,至少比常驻任务多一个备用位。
5.4 问题速查表
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 熵值忽高忽低 | 采样次数太少 | 采样次数提到5次,加滑动均值 |
| 路由频繁切换 | 熵差异阈值过低 | 阈值提高到0.35以上 |
| 成本突然飙升 | 探测请求重复发起 | 加缓存,低复杂度任务跳过探测 |
| 某Agent持续高熵 | 状态差或提示词不适配 | 排查任务类型与提示模板,或临时下线 |
| 多Agent答案互相矛盾 | 缺少仲裁逻辑 | 使用熵加权消解,替代简单投票 |
| 外部API超时 | 限流或网络抖动 | 超时重试+指数退避+备选Agent补位 |
6. 个人经验总结与后续扩展方向
这套系统我自己维护了大概两个多月,最大的感受是:熵并不是银弹,它解决的是"节奏"问题,而不是"能力"问题。如果一个模型本身不会做某个任务,熵再低也没用——低熵只是说明它"坚定地做错"。所以系统中我一直保留着一条后置防线:对最终输出做一轮规则校验,比如包含事实性内容时检查有没有给出可追溯的来源。如果你的Agent会调用外部工具,还会遇到工具调用结果和模型输出互相矛盾的情况,这时候可以把工具执行日志也纳入熵计算的上下文——结果越一致熵越低。
关于工具调用失败还有个典型问题值得单独提一下。很多模型在工具调用时会出现"幻觉参数",比如把订单ID编造出来传给查询接口,返回了空数据后模型还会煞有介事地生成一份"分析报告"。这时候熵调节度器虽然能看到文本层面的置信度,但看不到工具调用层的真实性。我的做法是给工具调用加一层schema校验,凡是参数不匹配预期的调用,直接判为无效输出,并且把这部分反馈注入到下一次采样里。搭配上前面说的重试逻辑后,这种幻觉基本能被压到很低。
后面如果要继续演进,我最想做的扩展方向是把工具的实时执行反馈也作为熵的一部分——当Agent调用工具后拿到预期结构的结果,这个"闭环"本身就能降低系统的整体熵;反之工具返回异常,熵应该自动上调,触发更多Agent介入复核。这样就能把"确定性评估"从文本层面推进到操作层面,多智能体协作就真正从"会聊天"进化成"会干活"了。
再分享一个小技巧:如果你想快速验证自己的多Agent协作系统有没有必要引入熵引导,不用一上来就完整重构。找一个已经跑通的场景,把日志里每一条Agent输出的文本抽出来,离线算一遍语义熵,然后回看那些最终被判定为"失败"的case,大概率会发现它们的熵显著偏高。这个回溯实验成本很低,但做完你基本就能下定决心要不要继续投入了。