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

Token、时间与费用预算:调用前预留,返回后结算

我的学习 →

正在读取学习记录…

1. 三种预算有不同单位

Agent 每多走一轮,就可能多发一次模型或工具请求。Token表示模型处理文本的用量,时间表示用户等了多久,费用表示按计价规则产生的金额。输入和输出都会占用Token;具体费用还可能区分缓存输入等档位。

这像出门办事要检查电量、钱包和时间:每一项都要够。程序把三种单位分别记录,例如100个Token、50分钱、10秒,分别比较。

余额100和50,预留60和20,按实际40和12结算,余额变为60和38
预留先占住预计额度,返回实际用量后退回差额;时间按累计耗时判断。

预留是在发送前占住预计额度,结算是在返回后修正余额。这样下一次调用能在出发前判断额度是否足够。下面数字用于手算,真实金额可依据 供应商计价说明和返回用量计算。

2. 手算后再运行

Budget(tokens=100, cents=50, seconds=10) 设置任务的初始额度:100个Token、50分钱和10秒总时限。调用 reserve 时,tokens 和 cents 是本次想预留的用量,estimated_seconds 是本次预计耗时,elapsed 是任务已经用掉的时间。例如预计再用3秒、已经用了3秒,就拿6秒与10秒总时限比较。实际调用时还要通过请求超时控制等待时间。

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-budget" -Force
cd "F:\学习\l3-budget"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

在新文件夹创建 budget.py,写入完整代码:

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-budget" -Force
cd "F:\学习\l3-budget"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

Python
"""用三个独立额度控制调用,并在返回用量后退回预留差额。"""
import json
import math

def amounts(tokens, cents):
    # 数量使用非负整数,便于按统一单位结算。
    if any(type(value) is not int or value < 0 for value in (tokens, cents)):
        raise ValueError("Token与金额须为非负整数")

def duration(seconds):
    # 秒数可有小数,但须为有限的非负数。
    if type(seconds) not in (int, float) or not math.isfinite(seconds) or seconds < 0:
        raise ValueError("秒数须为有限的非负数")

class Budget:
    def __init__(self, tokens, cents, seconds):
        amounts(tokens, cents)
        duration(seconds)
        self.tokens = tokens
        self.cents = cents
        self.seconds = seconds

    def reserve(self, tokens, cents, estimated_seconds, elapsed=0):
        amounts(tokens, cents)
        duration(estimated_seconds)
        duration(elapsed)
        # 各单位分别比较;一次调用须同时满足三个条件。
        if tokens > self.tokens or cents > self.cents:
            return False
        if elapsed + estimated_seconds > self.seconds:
            return False
        self.tokens -= tokens
        self.cents -= cents
        return True

    def settle(self, reserved_tokens, reserved_cents, used_tokens, used_cents):
        amounts(reserved_tokens, reserved_cents)
        amounts(used_tokens, used_cents)
        # 差额回到余额。实际用量超过预留时,余额也会相应减少。
        self.tokens += reserved_tokens - used_tokens
        self.cents += reserved_cents - used_cents

if __name__ == "__main__":
    budget = Budget(tokens=100, cents=50, seconds=10)
    # 为第一次调用预留60个Token、20分钱和预计3秒。
    print("第一次预留:", budget.reserve(tokens=60, cents=20, estimated_seconds=3))
    budget.settle(60, 20, 40, 12)
    print(json.dumps({"tokens_left": budget.tokens, "cents_left": budget.cents}))
    # 任务已用3秒;检查第二次新调用能否再预留70个Token、20分钱和预计3秒。
    print("第二次预留:", budget.reserve(tokens=70, cents=20, estimated_seconds=3, elapsed=3))
Bash
ls
python budget.py

输出:

TXT
第一次预留: True
{"tokens_left": 60, "cents_left": 38}
第二次预留: False

第一次预留60Token和20分,余额变40和30。实际用了40和12,退回20Token和8分,余额为60和38。第二次想用70Token,超过60的余额,因此返回 False。

Token和金额使用非负整数,秒数使用有限的非负数。amounts 与 duration 先检查这些条件,发现负数或类型错误就返回清晰错误,余额保持原值。

reserve 先比较,全部通过再扣除。elapsed 是任务已用时间,与本次预计时间相加判断。结算加回“预留-实际”,如果实际更多,余额就进一步减少。

3. 分别改动用量和时间

找到主程序里打印“第二次预留”的一行,将 tokens=70 改成 tokens=50,其他参数保持原样。保存后重新运行整个文件:每次运行都会从100个Token、50分钱重新开始。第一次预留和结算后余额仍为60个Token、38分钱;第二次预留50个Token、20分钱成功,余额变为10个Token、18分钱。这是第二次新调用占住额度,上一次已经在 settle 中退回了差额。

接着单独观察时间限制。将文件末尾从 if __name__ == "__main__": 开始的主程序全部替换为下面代码,保留前面的 Budget 类与函数。这里重新设置初始额度,再设置已用8秒、预计再用3秒。8+3超过10秒总时限,所以不会进入结算分支:

Python
if __name__ == "__main__":
    budget = Budget(tokens=100, cents=50, seconds=10)
    # 任务已用8秒,本次预计还需3秒,超过10秒总时限。
    if budget.reserve(tokens=60, cents=20, estimated_seconds=3, elapsed=8):
        budget.settle(60, 20, 40, 12)
    else:
        print("时间预算不足,保留当前结果")
    print("剩余Token和金额:", budget.tokens, budget.cents)

运行 python budget.py,看到“时间预算不足,保留当前结果”和“剩余Token和金额:100 50”。拒绝预留时,余额保持原值。观察结束后恢复第2节的主程序。

当前例子由你传入耗时,实际服务可用 time.monotonic读取时间间隔,并设置请求超时。

4. 保留检查用例

同一目录创建 test_budget.py:

下面的测试检查预留、结算、额度不足和非法输入,核对每次操作后的余额。

测试代码:test_budget.py
Python
"""检查budget示例的正常路径和边界结果。"""
import unittest
from budget import Budget
class Tests(unittest.TestCase):
    def test_reserve_settle(self):
        b=Budget(100,50,10)
        self.assertTrue(b.reserve(60,20,3)); b.settle(60,20,40,12)
        self.assertEqual((b.tokens,b.cents),(60,38))
    def test_reject(self):
        b=Budget(100,50,10); self.assertFalse(b.reserve(101,20,3))
        self.assertEqual(b.tokens,100); self.assertFalse(b.reserve(60,20,11))
    def test_invalid_values_leave_balance(self):
        b=Budget(100,50,10)
        for values in [(-60,20,3),(60,-20,3),(60,20,-3),(True,20,3),(60,20,float("nan"))]:
            with self.assertRaises(ValueError): b.reserve(*values)
            self.assertEqual((b.tokens,b.cents),(100,50))
    def test_invalid_settlement(self):
        b=Budget(100,50,10)
        with self.assertRaises(ValueError): b.settle(60,20,-40,12)

if __name__ == "__main__":
    unittest.main()
Bash
python -m unittest test_budget -v

测试同时检查正常路径和改变条件后的结果。遇到导入错误时,先核对两个文件是否在同一目录;遇到断言失败时,比较预期值和实际值,再回到相应分支。

5. 未知用量与并发预留怎样处理

发生的情况处理方式原因
Token余额不足减少上下文或输出长度降低下一次用量
费用余额不足暂停或选择更低成本方案限制后续支出
预计时间超过截止时间返回已有部分避免继续等待
请求超时,用量未知保留预留并标记待对账远端可能已处理
实际用量超过预留扣除差额并检查估算后续余额要反映消耗

网络超时后,服务可能已经处理并计费。这笔预留适合保留到用量或账单可查询时再结算。立刻退回全部额度,可能让下一轮继续花同一笔钱。

当前对象在一个进程内顺序使用。两个任务若同时看到余额60,各自想用50,就需要把“检查并扣除”作为不可分割操作。数据库事务或原子计数可以把并发预留集中处理。每次预留还可关联请求编号,结算时确认是否已经结过,避免重复退款。

预估也有误差。可以限制输出长度、模型种类和并发数,再根据返回用量对账。余额变负是一条暂停后续调用、核对预估的信号。金额换算也应选择一致单位;微小费用可使用更细的整数单位或十进制数。

逐项核对这次预算的加减

阶段Token余额金额余额发生的计算
初始化10050读取配置额度
预留4030100−60,50−20
实际返回4030获得用量40与金额12
结算603840+(60−40),30+(20−12)
再申请70Token6038超过余额,保持原值

预留与结算的输入需要配对。示例手工传入四个数,方便看公式;应用可为每次预留保存请求编号和预留值,结算时读取它,再标记该请求已结算。这样响应重发时也能识别同一笔用量。

时间预算采用截止时间更方便:任务开始记录start,总时限10秒,每轮读取now,elapsed等于now−start。下一请求的超时取工具上限与剩余时间中较小者,等待结束后再读取实际耗时。预估帮助决定是否出发,实际时间帮助决定下一轮。

不同模型的输入、输出与缓存计价可能分别计算。把用量计算和预算判断拆成两个函数,先得到统一金额,再检查余额。文章里的分钱仅为手算单位,细小金额可以选择更细的整数单位,避免浮点累计误差。

回顾与下一步

分别比较,先预留,按实际结算,未知结果待对账。下一篇 任务状态把这些运行结果组织成调用方容易处理的表示。

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

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

正在读取本地记录…

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

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“Token、时间与费用预算”,哪种做法更稳妥?

对应知识点:L3-budget

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

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L3-budget

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L3-budget

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L3-budget

图片查看器
100%

文章配图 · 按 Esc 关闭