生成式 AI 从试验走向规模化后,成本治理不再只是月底查看账单。真正棘手的问题是:如何按用户实时判断消费额度,在接近预算上限时限制更昂贵的模型,同时不打断正在进行的会话。
Jamf 的做法将三类 AWS 能力组合起来:用 IAM Customer Managed Policies 控制模型访问,用 Amazon Athena 构建成本视图,再由无服务器 AWS Lambda 循环持续应用分层模型限制。这个方案的重点不是单点限流,而是把用户身份、实际支出和模型权限连接成一个近实时闭环。
把成本治理拆成三个组件
1. 用 IAM Customer Managed Policies 表达模型层级
不同模型的调用成本不同。可以将模型访问权限分成多个层级,例如基础模型、标准模型和高级模型。用户所处的消费层级决定其可以调用哪些模型。
Customer Managed Policy 的好处是策略由组织自行维护,能够配合内部的用户分组、权限审批和自动化流程。策略本身只负责回答“这个身份当前能调用什么”,不负责计算累计消费。
一个可改造的策略示例:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowBedrockBasicModels",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1",
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0"
]
}
]
}
实际部署时,应替换区域、模型 ARN 和策略名称,并把策略绑定到用户、角色或权限组。更严格的实现还需要明确拒绝高价模型,避免其他宽泛的 Allow 策略绕过分层限制。
2. 用 Athena 构建成本视图
Lambda 不应直接从零开始推算每个用户的账单。可以先将 AWS 成本与使用量数据整理到可查询的数据集,再通过 Athena 按用户、模型和时间窗口聚合成本。
成本归属的关键是让 Bedrock 请求能够映射回用户身份。可以通过应用层用户 ID、角色会话名称、请求标签或其他审计字段建立关联。实际字段取决于企业的身份链路和成本数据落地方式,不能假设所有账单数据天然包含业务用户信息。
一个用于验证聚合逻辑的 Athena SQL 示例:
SELECT
line_item_usage_account_id AS account_id,
resource_id AS principal_id,
product_product_name AS service_name,
SUM(line_item_unblended_cost) AS spend_usd
FROM cur_database.cur_table
WHERE product_product_name = 'Amazon Bedrock'
AND line_item_usage_start_date >= current_date - INTERVAL '1' DAY
GROUP BY 1, 2, 3
ORDER BY spend_usd DESC;
表名和字段名需要根据实际 Cost and Usage Report 配置调整。生产环境还应考虑数据延迟、重复记录、退款项和多账户汇总等问题。Athena 查询结果可以写入一个轻量状态表,例如 DynamoDB,供 Lambda 快速读取和比较。
Lambda 如何形成近实时控制回路
Lambda 的职责可以保持清晰:定期读取 Athena 成本视图或其缓存结果,判断用户当前处于哪个消费区间,然后更新对应的 IAM Customer Managed Policy 或用户所属权限组。
可以这样实践一个简化的策略选择函数。它是可运行的 Python 示例,但 AWS API 调用部分需要根据组织的身份模型补齐:
from dataclasses import dataclass
@dataclass(frozen=True)
class SpendTier:
name: str
limit_usd: float
policy_arn: str
TIERS = [
SpendTier("standard", 10.0, "arn:aws:iam::123456789012:policy/BedrockStandard"),
SpendTier("advanced", 50.0, "arn:aws:iam::123456789012:policy/BedrockAdvanced"),
SpendTier("blocked", float("inf"), "arn:aws:iam::123456789012:policy/BedrockBasicOnly"),
]
def choose_policy(spend_usd: float) -> SpendTier:
if spend_usd < TIERS[0].limit_usd:
return TIERS[0]
if spend_usd < TIERS[1].limit_usd:
return TIERS[1]
return TIERS[2]
def lambda_handler(event, context):
# event 示例:{"principal_id": "user-42", "spend_usd": 53.2}
tier = choose_policy(float(event["spend_usd"]))
return {
"principal_id": event["principal_id"],
"tier": tier.name,
"policy_arn": tier.policy_arn,
}
完整实现通常需要调用 IAM API,完成旧策略解绑、新策略绑定,并记录变更审计。例如,应用层可以维护一个用户到 IAM 角色或权限组的映射,Lambda 只更新该映射对应的权限对象。这样可以减少直接操作单个用户策略带来的管理复杂度。
轮询频率决定了控制的及时性和系统成本。频率越高,越接近实时,但 Athena 查询、Lambda 执行以及 IAM 变更都会增加开销。还要接受一个事实:账单数据本身存在采集和汇总延迟,因此“近实时”不等于每次调用后立即得到精确成本。
为什么不会打断活跃会话
分层限制的边界通常是后续模型调用,而不是强制终止已经建立的会话。一个正在进行的会话可以继续完成当前请求;当用户下一次调用模型时,新的 IAM 权限才会决定请求是否允许或需要降级。
这种行为对用户体验很重要,也让策略变更更容易解释:用户不是突然被系统踢出,而是在达到消费阈值后只能使用较低成本的模型,或者获得明确的访问拒绝。应用层仍应捕获 Bedrock 的授权错误,并向用户提供可操作的提示,例如当前额度、可用模型和申请临时提升额度的入口。
落地时要验证的边界
- 身份映射:确认每一次 Bedrock 调用都能稳定关联到用户或团队,否则成本只能停留在账户级别。
- 策略绕过:检查角色继承、资源策略和其他 IAM
Allow是否允许用户绕过模型分层。 - 数据延迟:将成本数据延迟纳入阈值设计,必要时设置缓冲额度,避免用户在短时间内超出预期。
- 策略变更幂等性:Lambda 重试时不应重复堆叠策略,也不应因为部分失败让用户失去所有模型权限。
- 审计与回滚:记录用户、旧层级、新层级、触发成本和时间,保留人工恢复或临时豁免机制。
- 多账户治理:如果 Bedrock 分布在多个 AWS 账户,需要统一成本视图和身份标识,再执行集中判断。
结语:把预算变成运行时权限
Jamf 的方案提供了一个有实用价值的架构思路:Athena 负责提供成本事实,Lambda 负责持续判断,IAM Customer Managed Policies 负责把判断结果落实为模型访问权限。三者组合后,预算不再只是报表上的数字,而成为运行时可以执行的控制信号。
采用这类方案时,建议从少量模型和一个明确的成本维度开始,先验证身份映射、账单延迟和策略切换,再逐步扩展到多团队、多账户和更复杂的配额规则。成本治理的核心不是一味拒绝请求,而是在可预测的限制下,让用户继续使用合适的模型。