MetricsContext 上下文管理器整体设计思路
一、MetricsContext 上下文管理器整体设计思路
本质:利用 Python
作用:不用在各个函数手动传参、重复写计时、异常 try-except,代码侵入极低,自动完成指标收集。
__enter__ / __exit__ 实现上下文管理器,一次 LLM 请求从入参、路由分发、模型调用、异常捕获、结果返回全链路绑定同一个上下文实例,统一埋点采集指标,最终完成耗时统计、Token 计费、异常上报。
1. 第一步:定义 MetricsContext 核心数据结构
存储全链路关键指标,分为四类:链路基础信息、性能指标、计费 Token 指标、异常指标。
python
运行
from dataclasses import dataclass, field
import time
from typing import Optional
@dataclass
class MetricsContext:
# 1. 链路唯一标识
trace_id: str
task_type: str # 文案生成/摘要/图片提示词
model_name: str # Flash / Doubao-1.8
# 2. 性能耗时指标
start_time: float = field(init=False)
end_time: Optional[float] = None
total_latency_ms: Optional[int] = None
# 3. Token & 成本指标(用于火山引擎计费核算)
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
unit_price_input: float = 0.0 # 输入单价
unit_price_output: float = 0.0 # 输出单价
request_cost: float = 0.0 # 本次调用实际费用
# 4. 异常、稳定性指标
success: bool = True
error_code: Optional[str] = None
error_msg: Optional[str] = None
retry_count: int = 0 # 指数退避重试次数
def __enter__(self):
"""进入上下文:自动记录请求开始时间"""
self.start_time = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出上下文:自动计算耗时、捕获异常、统计指标"""
self.end_time = time.perf_counter()
self.total_latency_ms = int((self.end_time - self.start_time) * 1000)
# 如果发生异常,自动标记失败并记录异常信息
if exc_type is not None:
self.success = False
self.error_code = exc_type.__name__
self.error_msg = str(exc_val)
# 退出时自动执行成本计算
self.calc_cost()
# 统一上报指标到监控/日志系统
self.report_metrics()
def calc_cost(self):
"""基于火山引擎定价模型计算本次调用成本"""
input_cost = self.prompt_tokens * self.unit_price_input
output_cost = self.completion_tokens * self.unit_price_output
self.request_cost = input_cost + output_cost
def report_metrics(self):
"""统一埋点上报:Prometheus指标 + 结构化日志"""
# 上报维度:模型、任务类型、是否成功
# 指标:耗时、token用量、调用次数、失败率、调用成本
pass
上下文管理器使用方式
python
运行
# 业务调用处极简使用,自动计时、异常捕获、计费、上报
with MetricsContext(trace_id="xxx", task_type="article_gen", model_name="Flash") as ctx:
resp = llm_service.call()
# 从返回结果回填token数据
ctx.prompt_tokens = resp.usage.prompt_tokens
ctx.completion_tokens = resp.usage.completion_tokens
ctx.total_tokens = resp.usage.total_tokens
# 根据当前调用模型匹配对应单价
ctx.unit_price_input = price_config["Flash"]["input"]
ctx.unit_price_output = price_config["Flash"]["output"]
二、全链路指标采集设计(覆盖各个业务节点)
1. 网关 / 接口入口节点
- 记录
trace_id、请求来源、任务类型 - 限流、熔断发生时直接标记失败指标,不进入 LLM 调用
2. 多模型路由节点
- 记录最终路由选择的模型(Flash / Doubao-1.8)
- 可统计不同任务的模型分配占比,为后续模型策略调优提供依据
3. LLM 调用节点
- 采集:输入 Token、输出 Token、总 Token
- 采集:接口实际推理耗时、重试次数
- 捕获超时、服务抖动、参数错误等异常,记录错误码用于统计接口错误率
4. 结构化解析节点
- 统计 JSON 解析失败、Pydantic 格式校验失败次数,归类为业务异常指标
- 同样录入当前上下文,纳入整体错误率统计
5. 任务结束统一汇总指标维度
- 性能维度:各模型平均推理延迟、P95/P99 耗时
- 成本维度:各模型日均总 Token 消耗、总调用费用、单任务平均成本
- 稳定性维度:各模型调用成功率、错误率、重试触发频次
三、基于火山引擎 API 定价模型做成本核算设计
1. 维护全局定价配置表
预先配置不同模型官方计费单价(按每千 Token 计费):
python
运行
PRICE_CONFIG = {
"Flash": {
"input": 0.0005, # 每千输入token价格
"output": 0.001
},
"Doubao-1.8": {
"input": 0.002,
"output": 0.004
}
}
2. 上下文内自动核算单次调用成本
- 根据路由选中的
model_name匹配对应输入、输出单价; - 用实际消耗的输入、输出 Token 分别计算费用累加得到单次调用成本;
- 按天、按任务类型、按模型维度聚合总费用。
3. 成本数据落地应用
- 统计简单任务使用 Flash 的成本节省金额,量化项目中 ** 推理成本下降 40%** 的数据来源;
- 分析高耗时、高 Token 消耗的任务,优化提示词、裁剪上下文;
- 反向迭代模型路由策略:比如某类中等难度任务,可用轻量模型替代旗舰进一步降本。
四、最终落地价值
- 上下文管理器实现无侵入全链路埋点,自动采集耗时、Token、异常三大类核心指标;
- 依托火山官方定价完成精细化按次、按模型成本核算;
- 通过多维度指标报表,指导模型路由策略优化、提示词精简、资源扩容缩容,实现技术优化的数据可量化。
五、面试精简话术
基于 Python 上下文管理器封装 MetricsContext,绑定单次请求全局链路标识,在接口网关、模型路由、LLM 调用、结构化解析各节点统一采集推理耗时、Token 消耗、异常信息;依托火山引擎官方计费单价模型在上下文内自动完成单次调用成本计算,多维度聚合统计各模型调用耗时、错误率、日均资源开销,通过量化数据指导多模型路由策略迭代与算力资源优化,实现降本效果可观测、可追溯...
__enter__ / __exit__ 是什么意思
一、通俗解释
__enter__ 和 __exit__ 是 Python 实现 ** 上下文管理器(with 语法)** 的两个内置魔术方法。
with 类() as 变量: 的方式使用,自动做「前置初始化」和「后置收尾 / 异常兜底」。1. __enter__:进入 with 代码块时自动执行
- 执行时机:刚进入
with内部代码之前触发 - 作用:做初始化操作,返回值会赋值给
as后面的变量放到你的MetricsContext里:进入请求时自动记录开始时间,初始化链路指标。
示例流程:
python
运行
with MetricsContext(...) as ctx:
# 1. 先执行 __enter__,记录start_time,返回实例赋值给ctx
# 2. 再执行这里面的业务代码
2. __exit__:离开 with 代码块时一定会执行
三种场景都会触发:
- 代码正常执行完毕,顺利退出;
- 代码里出现异常报错;
- 执行
return、break提前跳出代码块。
参数:
__exit__(exc_type, exc_val, exc_tb)- 没异常:三个参数全是
None - 出现异常:分别是异常类型、异常对象、异常堆栈信息
结合你的监控场景做三件事:
- 记录结束时间,计算接口耗时;
- 捕获异常,自动标记请求失败、记录错误信息;
- 自动核算调用成本、上报监控指标。
二、不用 try-finally 也能保证收尾
如果不用上下文管理器,你必须手动写:
python
运行
ctx = MetricsContext(...)
ctx.start_time = time.perf_counter()
try:
# 业务调用
except Exception as e:
# 异常记录
finally:
# 计算耗时、上报指标
用
with + __enter__/__exit__ 等价于自动包裹 try-finally,代码更简洁、无侵入,不会忘记写收尾逻辑。三、极简小示例
python
运行
class Demo:
def __enter__(self):
print("进入代码块,执行__enter__")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("离开代码块,执行__exit__,无论是否报错都会执行")
if exc_type:
print(f"捕获到异常:{exc_val}")
with Demo() as d:
print("执行业务逻辑")
# raise Exception("出错了")
四、结合你项目总结
__enter__:请求开始,初始化链路、记录起始时间,拿到上下文实例;__exit__:请求结束(正常 / 异常都执行),自动统计耗时、捕获异常、计算 LLM 调用成本、上报监控指标;- 好处:全链路埋点无侵入,不会因为漏写
finally导致监控、计费数据丢失。
需要我给你整理一段面试简短话术吗?

浙公网安备 33010602011771号