coze_lab 0.1.50 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -695,6 +695,32 @@ def _assistant_bounds(turn):
|
|
|
695
695
|
return min(values), max(values)
|
|
696
696
|
|
|
697
697
|
|
|
698
|
+
def extract_token_events(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
699
|
+
"""扫 rollout event_msg/token_count 事件, 与 turn 完全解耦.
|
|
700
|
+
|
|
701
|
+
每条事件独立采集, 依赖 _line_number 做全局水位线幂等. 只收 last_token_usage 非空的事件
|
|
702
|
+
(空的是心跳/占位, 不代表真实模型调用). total_token_usage 一并保留供 tag 上报.
|
|
703
|
+
"""
|
|
704
|
+
events: List[Dict[str, Any]] = []
|
|
705
|
+
for entry in entries:
|
|
706
|
+
if entry.get("type") != "event_msg":
|
|
707
|
+
continue
|
|
708
|
+
payload = entry.get("payload") or {}
|
|
709
|
+
if payload.get("type") != "token_count":
|
|
710
|
+
continue
|
|
711
|
+
info = payload.get("info") or {}
|
|
712
|
+
last_usage = info.get("last_token_usage")
|
|
713
|
+
if not isinstance(last_usage, dict) or not last_usage:
|
|
714
|
+
continue
|
|
715
|
+
events.append({
|
|
716
|
+
"line": entry.get("_line_number", 0),
|
|
717
|
+
"timestamp": entry.get("timestamp"),
|
|
718
|
+
"usage": last_usage,
|
|
719
|
+
"total_usage": info.get("total_token_usage") or {},
|
|
720
|
+
})
|
|
721
|
+
return events
|
|
722
|
+
|
|
723
|
+
|
|
698
724
|
def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
699
725
|
"""Group raw JSONL entries into conversation turns.
|
|
700
726
|
|
|
@@ -1361,7 +1387,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1361
1387
|
# tool span 挂到固定 root、当场 flush。codex 的实时单元是 tool_call(turn 内平铺,按 call_id 关联
|
|
1362
1388
|
# result)。全局水位线 state["rt_last_tool"] 跟踪已发的 tool_call 数。
|
|
1363
1389
|
|
|
1364
|
-
def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_override=None, is_terminal=False):
|
|
1390
|
+
def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_override=None, is_terminal=False, token_events=None):
|
|
1365
1391
|
import cozeloop
|
|
1366
1392
|
from cozeloop.spec.tracespec import Runtime
|
|
1367
1393
|
token = get_fresh_token()
|
|
@@ -1494,6 +1520,62 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1494
1520
|
if sent > 0:
|
|
1495
1521
|
state["rt_last_upload_ts"] = time.time()
|
|
1496
1522
|
|
|
1523
|
+
# --- token_count 独立增量, 与 tool span 同一 root 下, 按行号水位线幂等 ---
|
|
1524
|
+
# 每条 token_count 挂一个 model_usage_<line> span:
|
|
1525
|
+
# last_token_usage → set_input_tokens/set_output_tokens (SDK 官方求和字段)
|
|
1526
|
+
# total_token_usage → tags (只观察, 不参与后端聚合, 否则会重复累加)
|
|
1527
|
+
# 只在 flush 成功后推进 rt_last_token_line, 保证上报失败下次能重发.
|
|
1528
|
+
token_sent = 0
|
|
1529
|
+
last_token_line = state.get("rt_last_token_line", -1)
|
|
1530
|
+
pending_tokens = [ev for ev in (token_events or []) if ev["line"] > last_token_line]
|
|
1531
|
+
max_flushed_line = last_token_line
|
|
1532
|
+
for ev in pending_tokens:
|
|
1533
|
+
line = ev["line"]
|
|
1534
|
+
last = ev.get("usage") or {}
|
|
1535
|
+
total = ev.get("total_usage") or {}
|
|
1536
|
+
in_tk = _usage_int(last, "input_tokens")
|
|
1537
|
+
out_tk = _usage_int(last, "output_tokens")
|
|
1538
|
+
start_dt = _parse_ts_value(ev.get("timestamp"))
|
|
1539
|
+
tk_span = client.start_span(name=f"model_usage_{line}", span_type="model",
|
|
1540
|
+
start_time=start_dt, child_of=root_ctx)
|
|
1541
|
+
tk_span.set_runtime(Runtime(library="codex-cli"))
|
|
1542
|
+
try: tk_span.set_model_name(model_name)
|
|
1543
|
+
except Exception: pass
|
|
1544
|
+
if start_dt is not None:
|
|
1545
|
+
_set_finish_time_safe(tk_span, start_dt)
|
|
1546
|
+
if in_tk > 0:
|
|
1547
|
+
try: tk_span.set_input_tokens(in_tk)
|
|
1548
|
+
except Exception: pass
|
|
1549
|
+
if out_tk > 0:
|
|
1550
|
+
try: tk_span.set_output_tokens(out_tk)
|
|
1551
|
+
except Exception: pass
|
|
1552
|
+
tk_tags = {
|
|
1553
|
+
"token_event_line": line,
|
|
1554
|
+
"token_source": "codex_token_count",
|
|
1555
|
+
"cached_input_tokens": _usage_int(last, "cached_input_tokens"),
|
|
1556
|
+
"reasoning_output_tokens": _usage_int(last, "reasoning_output_tokens"),
|
|
1557
|
+
"total_tokens": _usage_int(last, "total_tokens"),
|
|
1558
|
+
}
|
|
1559
|
+
if total:
|
|
1560
|
+
tk_tags["total_input_tokens"] = _usage_int(total, "input_tokens")
|
|
1561
|
+
tk_tags["total_output_tokens"] = _usage_int(total, "output_tokens")
|
|
1562
|
+
tk_tags["total_cached_input_tokens"] = _usage_int(total, "cached_input_tokens")
|
|
1563
|
+
tk_tags["total_reasoning_output_tokens"] = _usage_int(total, "reasoning_output_tokens")
|
|
1564
|
+
tk_tags["total_all_tokens"] = _usage_int(total, "total_tokens")
|
|
1565
|
+
tk_span.set_tags(tk_tags)
|
|
1566
|
+
tk_span.finish()
|
|
1567
|
+
client.flush()
|
|
1568
|
+
if upload_events:
|
|
1569
|
+
hook_log(f"[rt] upload failed after token flush line={line} detail={upload_events[-1][:500]}")
|
|
1570
|
+
if max_flushed_line > last_token_line:
|
|
1571
|
+
state["rt_last_token_line"] = max_flushed_line
|
|
1572
|
+
return None
|
|
1573
|
+
max_flushed_line = line
|
|
1574
|
+
token_sent += 1
|
|
1575
|
+
if token_sent > 0:
|
|
1576
|
+
state["rt_last_token_line"] = max_flushed_line
|
|
1577
|
+
state["rt_last_upload_ts"] = time.time()
|
|
1578
|
+
|
|
1497
1579
|
if is_terminal:
|
|
1498
1580
|
# 收尾:补最终 assistant 输出
|
|
1499
1581
|
last_out = ""
|
|
@@ -1519,7 +1601,7 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1519
1601
|
hook_log(f"[rt] upload failed state not advanced detail={upload_events[-1][:500]}")
|
|
1520
1602
|
return None
|
|
1521
1603
|
|
|
1522
|
-
hook_log(f"[rt] sent {sent} tool span(s), last_tool={state['rt_last_tool']}, terminal={is_terminal}")
|
|
1604
|
+
hook_log(f"[rt] sent {sent} tool span(s), {token_sent} token span(s), last_tool={state['rt_last_tool']}, last_token_line={state.get('rt_last_token_line', -1)}, terminal={is_terminal}")
|
|
1523
1605
|
return True
|
|
1524
1606
|
except Exception as e:
|
|
1525
1607
|
hook_log(f"[rt] error: {e}")
|
|
@@ -1637,7 +1719,9 @@ def main():
|
|
|
1637
1719
|
|
|
1638
1720
|
# Group entries into turns
|
|
1639
1721
|
turns = group_messages_into_turns(entries)
|
|
1640
|
-
|
|
1722
|
+
# 独立扫 token_count 事件, 与 turn 解耦, 走独立行号水位线上报.
|
|
1723
|
+
token_events = extract_token_events(entries)
|
|
1724
|
+
debug_log(f"Grouped into {len(turns)} turns, {len(token_events)} token events")
|
|
1641
1725
|
|
|
1642
1726
|
# If this is a subagent, save data for parent to include later
|
|
1643
1727
|
if is_subagent:
|
|
@@ -1677,7 +1761,8 @@ def main():
|
|
|
1677
1761
|
# 方案 B 实时:把新完成的 tool_call 作为 span 挂稳定 root,结束即可见。不节流。
|
|
1678
1762
|
ok = send_steps_realtime(turns, session_id, state, model_name=model_name,
|
|
1679
1763
|
coze_tags_override=coze_tags_override,
|
|
1680
|
-
is_terminal=is_terminal_event
|
|
1764
|
+
is_terminal=is_terminal_event,
|
|
1765
|
+
token_events=token_events)
|
|
1681
1766
|
if ok is None:
|
|
1682
1767
|
hook_log(f"realtime send failed session_id={session_id}")
|
|
1683
1768
|
debug_log("Realtime send failed, state not advanced")
|