AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

通信成本、延迟与收益对照

我的学习 →

正在读取学习记录…

多Agent的成本包括模型调用的输入与输出、角色间通信、汇总和等待。收益则要回到同一批任务,比较新增通过了哪些样例,以及这些改善是否值得增加的消耗。

通信不是免费背景音。一位专家的输出交给主管,会再次成为主管的输入;完整聊天记录群发给三位专家,也会重复占用三份输入。增加角色之后,单次调用短了,总输入仍可能明显变长。

延迟还要区分总工作量和关键路径。总工作量是所有任务时长之和;关键路径是必须顺着依赖等完的最长线路。两个任务同时开始,等待较慢的那个就能汇总,未必需要把两段时间相加。

比较三份固定工作量

单Agent一次答复,预置输入1000、输出200,时长4秒。协作方案包含计划、订单、规则和汇总四次调用。串行版本让规则等待订单,并行版本让二者都只等待计划。

价格假设为每千输入token 1计费单位、每千输出token 2计费单位。秒数与用量是预置数据,用来练习求和和依赖计算,程序没有发模型请求,也没有测网络耗时。

另外预置四条期待答案和两份回答记录。A应为120,B应为人工核查,C应为原支付渠道,D应为需要订单号。逐项相等得到固定记录通过数,不把它外推成模型排行榜或真实质量提升。

保存完整程序并运行

在自己的学习目录打开PowerShell,创建一个新的子目录:

Bash
mkdir l6-cost
cd l6-cost

把下面完整代码保存为 cost_demo.py。固定数据和运行入口都已包含,文件名保留 .py 后缀。

Python
"""用预置用量、时长与回答记录计算协作取舍。"""
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,再运行:

Bash
ls
python -X utf8 cost_demo.py

程序输出:

TXT
单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调用的输入里,不能再漏掉,也不能在求和之后重复计一次。

TXT
费用 = (总输入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与任务指标会在评测部分展开。

单Agent路径4秒;串行计划订单规则汇总共10秒;订单规则并行后关键路径7秒而总工作量仍10秒
相同用量下,并行可改变依赖等待时间;费用与质量仍需单独计算和对照。

7秒和10秒分别怎样算

串行计划依次经过plan、order、policy、merge,路径是1+4+3+2=10秒。并行路径先计划1秒,再等两项查询中较慢的4秒,最后汇总2秒,总共7秒。

TXT
单次调用完成时间 = 所有依赖的最晚完成时间 + 本次调用时长
并行关键路径 = 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
Python
"""检查计费求和、关键路径、固定记录与预算门禁。"""
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()
Bash
python -X utf8 -m unittest -v test_cost_demo.py

测试名称后应显示 ok,末尾显示 Ran 6 tests 和 OK。若找不到模块,先用 dir 确认两个文件保存在同一目录。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.1.0

选择题 · 两个独立查询分别2秒和3秒,汇总1秒,并行路线的固定计划时长是多少

两个独立查询分别2秒和3秒,汇总1秒,并行路线的固定计划时长是多少?

对应知识点:L6-cost

选择一个答案
练习 2 / 4 · v1.1.0

简答题 · 解释机制

协作版的总Token怎样统计?

对应知识点:L6-cost

练习 3 / 4 · v1.1.0

代码题 · 核对给定数据

写一个表达式计算并行查询后的汇总路线长度。

对应知识点:L6-cost

练习 4 / 4 · v1.1.0

场景题 · 分析具体变化

协作版成功多1条却花费翻倍,需要哪些数据决定是否保留?

对应知识点:L6-cost

图片查看器
100%

文章配图 · 按 Esc 关闭