通信成本、延迟与收益对照
正在读取学习记录…
多Agent的成本包括模型调用的输入与输出、角色间通信、汇总和等待。收益则要回到同一批任务,比较新增通过了哪些样例,以及这些改善是否值得增加的消耗。
通信不是免费背景音。一位专家的输出交给主管,会再次成为主管的输入;完整聊天记录群发给三位专家,也会重复占用三份输入。增加角色之后,单次调用短了,总输入仍可能明显变长。
延迟还要区分总工作量和关键路径。总工作量是所有任务时长之和;关键路径是必须顺着依赖等完的最长线路。两个任务同时开始,等待较慢的那个就能汇总,未必需要把两段时间相加。
比较三份固定工作量
单Agent一次答复,预置输入1000、输出200,时长4秒。协作方案包含计划、订单、规则和汇总四次调用。串行版本让规则等待订单,并行版本让二者都只等待计划。
价格假设为每千输入token 1计费单位、每千输出token 2计费单位。秒数与用量是预置数据,用来练习求和和依赖计算,程序没有发模型请求,也没有测网络耗时。
另外预置四条期待答案和两份回答记录。A应为120,B应为人工核查,C应为原支付渠道,D应为需要订单号。逐项相等得到固定记录通过数,不把它外推成模型排行榜或真实质量提升。
保存完整程序并运行
在自己的学习目录打开PowerShell,创建一个新的子目录:
mkdir l6-cost
cd l6-cost把下面完整代码保存为 cost_demo.py。固定数据和运行入口都已包含,文件名保留 .py 后缀。
"""用预置用量、时长与回答记录计算协作取舍。"""
from dataclasses import dataclass
from decimal import Decimal
INPUT_RATE = Decimal("1") # 假设计费单位/千个输入token
OUTPUT_RATE = Decimal("2") # 假设计费单位/千个输出token
@dataclass(frozen=True)
class Call:
id: str
input_tokens: int
output_tokens: int
seconds: int
deps: tuple = ()
SINGLE = [Call("answer", 1000, 200, 4)]
PARALLEL = [
Call("plan", 300, 100, 1),
Call("order", 900, 200, 4, ("plan",)),
Call("policy", 800, 200, 3, ("plan",)),
Call("merge", 600, 150, 2, ("order", "policy")),
]
SERIAL = [PARALLEL[0], PARALLEL[1],
Call("policy", 800, 200, 3, ("order",)), PARALLEL[3]]
EXPECTED = {"A": "120", "B": "人工核查", "C": "原支付渠道", "D": "需要订单号"}
SINGLE_ANSWERS = {"A": "120", "B": "直接退款", "C": "原支付渠道", "D": "120"}
TEAM_ANSWERS = {"A": "120", "B": "人工核查", "C": "原支付渠道", "D": "120"}
def totals(calls):
inputs = sum(call.input_tokens for call in calls)
outputs = sum(call.output_tokens for call in calls)
fee = (Decimal(inputs) * INPUT_RATE + Decimal(outputs) * OUTPUT_RATE) / 1000
return inputs, outputs, fee
def critical_path(calls):
finish, remaining = {}, list(calls)
if len({call.id for call in calls}) != len(calls):
raise ValueError("调用编号重复")
while remaining:
ready = [call for call in remaining if set(call.deps) <= set(finish)]
if not ready:
raise ValueError("缺少依赖或存在循环")
for call in ready:
finish[call.id] = max((finish[dep] for dep in call.deps), default=0) + call.seconds
remaining.remove(call)
return max(finish.values(), default=0)
def passed(answers):
# 对固定记录逐条核对,不调用模型,也不给模型输出伪造评分。
return sum(answers.get(key) == value for key, value in EXPECTED.items())
def allowed(calls, limit):
return totals(calls)[2] <= Decimal(str(limit))
def main():
for name, calls, answers in [("单Agent", SINGLE, SINGLE_ANSWERS),
("串行协作", SERIAL, TEAM_ANSWERS),
("并行协作", PARALLEL, TEAM_ANSWERS)]:
inputs, outputs, fee = totals(calls)
print(f"{name}:输入={inputs};输出={outputs};费用={fee:.2f};路径={critical_path(calls)}秒;固定记录通过={passed(answers)}/4")
print(f"并行总工作时长:{sum(call.seconds for call in PARALLEL)}秒")
print("费用上限3.00允许并行协作:" + str(allowed(PARALLEL, "3.00")))
extra = [key for key, value in EXPECTED.items()
if TEAM_ANSWERS.get(key) == value and SINGLE_ANSWERS.get(key) != value]
failures = [key for key, value in EXPECTED.items() if TEAM_ANSWERS.get(key) != value]
print("增加的通过样例:" + ",".join(extra) + ";仍失败的样例:" + ",".join(failures))
if __name__ == "__main__":
main()保存后执行 dir,确认能看到 cost_demo.py,再运行:
ls
python -X utf8 cost_demo.py程序输出:
单Agent:输入=1000;输出=200;费用=1.40;路径=4秒;固定记录通过=2/4
串行协作:输入=2600;输出=650;费用=3.90;路径=10秒;固定记录通过=3/4
并行协作:输入=2600;输出=650;费用=3.90;路径=7秒;固定记录通过=3/4
并行总工作时长:10秒
费用上限3.00允许并行协作:False
增加的通过样例:B;仍失败的样例:D费用为什么从1.40增加到3.90
输入token和输出token按调用求和,再乘对应单价。主管读取专家结果的开销已经包含在merge调用的输入里,不能再漏掉,也不能在求和之后重复计一次。
费用 = (总输入token × 输入千token单价 + 总输出token × 输出千token单价) / 1000
单Agent = (1000 × 1 + 200 × 2) / 1000 = 1.40
协作 = ((300+900+800+600) × 1 + (100+200+200+150) × 2) / 1000
= (2600 + 1300) / 1000 = 3.90代码用Decimal进行十进制金额计算,避免二进制小数边界影响费用上限。allowed将上限3.00与总费用3.90比较,返回False。这个函数是计划调用前的额度判断;真实请求还要预留输出上限并按实际用量对账。
串行和并行用量相同,所以费用相同。并行安排改变的是等待关系,没有减少发给模型的内容。
每笔费用怎样找到对应的工作
总费用3.90可以拆回四次调用。按代码中的单价,计划plan花0.50,查订单order花1.30,查规则policy花1.20,汇总merge花0.90。四笔相加仍是3.90。总费用突然上涨时,先比较哪一笔的输入或输出变多,才能找到需要调整的步骤。
接到实际任务后,可以把这些调用记录到同一条Trace,即一次任务的执行轨迹中;其中每次调用对应一个Span,即具体操作的记录。
| 调用记录中的内容 | 用来回答什么问题 |
|---|---|
| trace_id,整次任务的关联编号 | 这笔费用属于哪一次用户请求? |
| span_id与步骤名,例如order或merge | 是查订单花的,还是汇总花的? |
| 本次尝试的输入、输出用量与计价规则 | 这笔费用怎样算出来? |
| 开始时间、结束时间与调用状态 | 花了多久,成功、失败还是仍在等待? |
查订单失败后重新调用,会出现两次尝试记录:统计任务成功率时仍算一次用户任务,统计费用时则核对两次尝试的实际计费情况。供应商尚未返回用量的调用先保留“待对账”状态,拿到用量再补费用。这样,质量统计与费用统计都能回到具体记录。Trace、Span与任务指标会在评测部分展开。
7秒和10秒分别怎样算
串行计划依次经过plan、order、policy、merge,路径是1+4+3+2=10秒。并行路径先计划1秒,再等两项查询中较慢的4秒,最后汇总2秒,总共7秒。
单次调用完成时间 = 所有依赖的最晚完成时间 + 本次调用时长
并行关键路径 = 1 + max(4, 3) + 2 = 7秒
并行总工作量 = 1 + 4 + 3 + 2 = 10秒critical_path保存每个调用的完成时间,只有依赖全部计算出来的调用才能加入。缺依赖和循环都会拒绝计算,防止给错误流程报出一个好看的时长。
这组秒数是依赖排程的算术结果。真实系统还有排队、限流、重试、启动和流式输出延迟。要测用户体感,应记录请求开始、首个有效输出与最终交付时间;不要把总工作量当墙上时钟经过的时间。
新增通过的样例是否值得
单Agent固定回答在A与C相符,协作记录还在B相符,D仍失败。收益来自一条明确样例B,不是“整体更加智能”这类无法复核的判断。
若每条样例都按上述相同费用假设计价,处理四条的总费用分别为5.60与15.60。每通过一条样例分摊费用分别为5.60/2=2.80和15.60/3=5.20。这个计算说明通过数增加不必然带来更低的单位成功成本;实际判断还需考虑失败损失和任务价值。
有些任务很容易,加入计划者与审查者只重复工作;有些任务必须独立核对多份资料,协作更有价值。可以按问题类型路由:简单问题走单Agent,证据复杂时再进入专家协作。在同一评估集上比较正确性、人工接管率、费用与延迟,才能知道分流是否有效。
改一个依赖,观察等待时间
在SERIAL里的policy调用,把deps从("order",)改为("plan",),运行python -X utf8 cost_demo.py。串行协作那行路径变为7秒,用量、费用和固定记录通过数都不变,因为调用内容没有变化。
把main中费用上限3.00的实参改为3.90,再运行同一命令,预算判断变为True。输出文字也应同步改成“费用上限3.90允许并行协作”,避免参数已经变化、标签还报旧数字。
结束时将SERIAL中policy的依赖恢复为("order",),上限实参与输出文字都恢复为3.00,再运行。串行路径回到10秒,并行路径仍为7秒,上限判断回到False,然后运行配套检查。
若并行后费用仍高,先看总输入、上下文重复和汇总次数;若用户等待更久,看关键路径与排队,而不是只看专家是否同时启动。若新增通过样例为零,优先检查单Agent基线是否已经足够。
参考阅读:Anthropic多Agent研究系统关注通信与资源取舍,厂商报告不能代替自己的样例对照;Building effective agents阅读增加复杂度与任务收益的关系。
运行配套检查
把下面测试完整保存为 test_cost_demo.py,与主程序放在同一目录。6项检查覆盖所有调用用量、串并行路径、基线记录、十进制预算、非法依赖和空调用。
查看测试代码:test_cost_demo.py
"""检查计费求和、关键路径、固定记录与预算门禁。"""
import sys
import unittest
from decimal import Decimal
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import cost_demo as demo
class CostTests(unittest.TestCase):
def test_token_totals_include_all_communication(self):
self.assertEqual(demo.totals(demo.PARALLEL), (2600, 650, Decimal("3.9")))
def test_parallel_and_serial_same_fee_different_path(self):
self.assertEqual(demo.totals(demo.PARALLEL), demo.totals(demo.SERIAL))
self.assertEqual(demo.critical_path(demo.PARALLEL), 7)
self.assertEqual(demo.critical_path(demo.SERIAL), 10)
def test_single_baseline_and_fixed_case_counts(self):
self.assertEqual(demo.totals(demo.SINGLE)[2], Decimal("1.4"))
self.assertEqual(demo.passed(demo.SINGLE_ANSWERS), 2)
self.assertEqual(demo.passed(demo.TEAM_ANSWERS), 3)
def test_budget_boundary_uses_decimal(self):
self.assertFalse(demo.allowed(demo.PARALLEL, "3.89"))
self.assertTrue(demo.allowed(demo.PARALLEL, "3.90"))
def test_missing_dependency_and_cycle_rejected(self):
for calls in ([demo.Call("A", 1, 1, 1, ("missing",))],
[demo.Call("A", 1, 1, 1, ("B",)), demo.Call("B", 1, 1, 1, ("A",))]):
with self.subTest(calls=calls), self.assertRaises(ValueError):
demo.critical_path(calls)
def test_empty_calls_have_zero_cost_and_path(self):
self.assertEqual(demo.totals([]), (0, 0, Decimal("0")))
self.assertEqual(demo.critical_path([]), 0)
if __name__ == "__main__":
unittest.main()python -X utf8 -m unittest -v test_cost_demo.py测试名称后应显示 ok,末尾显示 Ran 6 tests 和 OK。若找不到模块,先用 dir 确认两个文件保存在同一目录。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 两个独立查询分别2秒和3秒,汇总1秒,并行路线的固定计划时长是多少
两个独立查询分别2秒和3秒,汇总1秒,并行路线的固定计划时长是多少?
对应知识点:L6-cost
简答题 · 解释机制
协作版的总Token怎样统计?
对应知识点:L6-cost
代码题 · 核对给定数据
写一个表达式计算并行查询后的汇总路线长度。
对应知识点:L6-cost
场景题 · 分析具体变化
协作版成功多1条却花费翻倍,需要哪些数据决定是否保留?
对应知识点:L6-cost
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。