feat(tier2): C1 trace 中间件接 with_studio(开 Studio 即旁路推 OTLP span)

C1 此前建了 observability.with_studio + studio_sink(OTLP→Studio),但没 wire 进真正跑的
Tier2TraceMiddleware(它在 worker/middleware.py、C1 没动)。本提交把中间件新建 adapter 的路径从
TraceAdapter(...) 换成 with_studio(...):drop-in——TIER2_STUDIO_URL/infra_config[studio] 取不到时
studio sink 自动 no-op,行为与原 TraceAdapter 完全一致(CLI/服务两线不开 Studio 即无感);取到地址则
每步在原 sink 外旁路推一份 OTLP span 给 AgentScope Studio(observe-only,best-effort,不阻塞主链)。
存 _studio_sink 句柄供收口 flush。配合 mini-desktop 起 Studio(创始人:now)即得真跑可视化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-24 11:49:03 +00:00
parent b1217301c8
commit ffd22f22c2

View File

@ -64,14 +64,14 @@ except Exception: # pragma: no cover —— 直跑兜底:worker 包未就位时
# make_phase_marker / PHASE_* 是 U3 给三道新钩子产「相位段」typed event 用的(走 adapter 同一条 ingest 路)。
try:
from observability.trace import ( # type: ignore
TraceAdapter, make_phase_marker, PHASE_REASONING, PHASE_ACTING, PHASE_MODEL_CALL)
TraceAdapter, with_studio, make_phase_marker, PHASE_REASONING, PHASE_ACTING, PHASE_MODEL_CALL)
except Exception: # pragma: no cover —— 直跑/路径未就位兜底:把 gen-worker/ 加进 sys.path 再取
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from observability.trace import ( # type: ignore
TraceAdapter, make_phase_marker, PHASE_REASONING, PHASE_ACTING, PHASE_MODEL_CALL)
TraceAdapter, with_studio, make_phase_marker, PHASE_REASONING, PHASE_ACTING, PHASE_MODEL_CALL)
# 成本折算(¥ 累进硬熔断用):compute 折单条 usage → ¥(纯函数,显式传参);活价取不到时回落次数闸。
# newapi_pricing.fetch_pricing_params 一步取齐 new-api 计费三件套(失败 best-effort 返回 None)。
@ -521,8 +521,13 @@ class Tier2TraceMiddleware(MiddlewareBase):
"""
self.trace_id = trace_id
# 复用传入 adapter 或新建;adapter 是 trace 的产出件(H1/H2),本 middleware 只负责把事件喂进去。
self.adapter = adapter or TraceAdapter(
# 新建走 with_studio(C1 接线):若 TIER2_STUDIO_URL / infra_config[studio] 可解析,则每步在原 sink
# 之外旁路推一份 OTLP span 给 AgentScope Studio(observe-only);取不到地址 → studio sink 自动 no-op,
# 行为 == 原 TraceAdapter(sink=...),零影响(故 CLI/服务两条线都白拿 Studio 可观测、不开即无感)。
self.adapter = adapter or with_studio(
trace_id=trace_id, sink=sink, keep_in_memory=keep_in_memory)
# studio sink 句柄(收口 flush 残留 span 用;with_studio 把它挂在 adapter._studio_sink,无则 None)。
self._studio_sink = getattr(self.adapter, "_studio_sink", None)
# 兜底层告警计数(adapter 自身抛异常的极端情况;正常应为 0,因 adapter 内部已 best-effort)。
self._middleware_dropped = 0