coze_lab 0.1.10 → 0.1.12
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
|
@@ -331,10 +331,21 @@ def is_tool_result_message(msg: Dict[str, Any]) -> bool:
|
|
|
331
331
|
)
|
|
332
332
|
|
|
333
333
|
def extract_tool_result_from_message(msg: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
334
|
-
"""Extract tool_result items from a user message.
|
|
334
|
+
"""Extract tool_result items from a user message.
|
|
335
|
+
|
|
336
|
+
把外层 user 消息的 timestamp 附到每个 tool_result item 上(_result_ts),
|
|
337
|
+
用作 tool span 的真实完成时间——否则同一 step 内所有 tool 时间会挤成一样。
|
|
338
|
+
"""
|
|
335
339
|
content = msg.get("message", {}).get("content", [])
|
|
340
|
+
result_ts = msg.get("timestamp")
|
|
336
341
|
if isinstance(content, list):
|
|
337
|
-
|
|
342
|
+
out = []
|
|
343
|
+
for item in content:
|
|
344
|
+
if isinstance(item, dict) and item.get("type") == "tool_result":
|
|
345
|
+
if result_ts and "_result_ts" not in item:
|
|
346
|
+
item["_result_ts"] = result_ts
|
|
347
|
+
out.append(item)
|
|
348
|
+
return out
|
|
338
349
|
return []
|
|
339
350
|
|
|
340
351
|
|
|
@@ -450,6 +461,16 @@ def _parse_ts(msg):
|
|
|
450
461
|
return None
|
|
451
462
|
|
|
452
463
|
|
|
464
|
+
def _parse_ts_str(ts):
|
|
465
|
+
"""ISO8601 字符串 → datetime(带时区)。失败返回 None。"""
|
|
466
|
+
if not ts or not isinstance(ts, str):
|
|
467
|
+
return None
|
|
468
|
+
try:
|
|
469
|
+
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
470
|
+
except Exception:
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
|
|
453
474
|
def _ts_ms(dt):
|
|
454
475
|
"""datetime → 毫秒时间戳(int);None → None。"""
|
|
455
476
|
return int(dt.timestamp() * 1000) if dt is not None else None
|
|
@@ -839,6 +860,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
839
860
|
}
|
|
840
861
|
# 真实耗时写 tag(SDK finish 不支持显式结束时间,duration 用 tag 兜底)
|
|
841
862
|
if root_start_dt is not None and root_end_dt is not None:
|
|
863
|
+
root_span.set_finish_time(root_end_dt)
|
|
842
864
|
root_tags["real_start_ms"] = _ts_ms(root_start_dt)
|
|
843
865
|
root_tags["real_end_ms"] = _ts_ms(root_end_dt)
|
|
844
866
|
root_tags["latency_ms"] = _ts_ms(root_end_dt) - _ts_ms(root_start_dt)
|
|
@@ -897,6 +919,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
897
919
|
"source": "claude_code",
|
|
898
920
|
}
|
|
899
921
|
if turn_start_dt is not None and turn_end_dt is not None:
|
|
922
|
+
turn_span.set_finish_time(turn_end_dt)
|
|
900
923
|
_turn_tags["real_start_ms"] = _ts_ms(turn_start_dt)
|
|
901
924
|
_turn_tags["real_end_ms"] = _ts_ms(turn_end_dt)
|
|
902
925
|
_turn_tags["latency_ms"] = _ts_ms(turn_end_dt) - _ts_ms(turn_start_dt)
|
|
@@ -930,6 +953,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
930
953
|
model_span.set_runtime(Runtime(library="claude-code"))
|
|
931
954
|
model_span.set_model_name(model_name)
|
|
932
955
|
if step_start_dt is not None and step_end_dt is not None:
|
|
956
|
+
model_span.set_finish_time(step_end_dt)
|
|
933
957
|
model_span.set_tags({
|
|
934
958
|
"real_start_ms": _ts_ms(step_start_dt),
|
|
935
959
|
"real_end_ms": _ts_ms(step_end_dt),
|
|
@@ -1028,13 +1052,30 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
1028
1052
|
span_type = "agent" if is_agent else "tool"
|
|
1029
1053
|
span_name = f"agent_{tool_name}" if is_agent else f"tool_{tool_name}"
|
|
1030
1054
|
|
|
1055
|
+
# tool 真实时间:start=模型发起调用(step_start_dt),
|
|
1056
|
+
# end=该 tool 对应 tool_result 的 timestamp(工具完成时刻)。
|
|
1057
|
+
# 不同 tool 的 result 时间不同,避免所有 tool span 时间挤成一样。
|
|
1058
|
+
tool_id = tool_call.get("id")
|
|
1059
|
+
tool_end_dt = None
|
|
1060
|
+
for result in step.get("tool_results", []):
|
|
1061
|
+
if result.get("tool_use_id") == tool_id:
|
|
1062
|
+
tool_end_dt = _parse_ts_str(result.get("_result_ts"))
|
|
1063
|
+
break
|
|
1064
|
+
|
|
1031
1065
|
with client.start_span(name=span_name, span_type=span_type, start_time=step_start_dt) as tool_span:
|
|
1032
1066
|
tool_span.set_runtime(Runtime(library="claude-code"))
|
|
1067
|
+
# 设真实完成时间,让每个 tool 的 duration 反映实际耗时。
|
|
1068
|
+
if tool_end_dt is not None:
|
|
1069
|
+
tool_span.set_finish_time(tool_end_dt)
|
|
1033
1070
|
tags = {
|
|
1034
1071
|
"tool_name": tool_name,
|
|
1035
1072
|
"tool_call_id": tool_call.get("id"),
|
|
1036
1073
|
"step_index": j,
|
|
1037
1074
|
}
|
|
1075
|
+
if step_start_dt is not None and tool_end_dt is not None:
|
|
1076
|
+
tags["real_start_ms"] = _ts_ms(step_start_dt)
|
|
1077
|
+
tags["real_end_ms"] = _ts_ms(tool_end_dt)
|
|
1078
|
+
tags["latency_ms"] = _ts_ms(tool_end_dt) - _ts_ms(step_start_dt)
|
|
1038
1079
|
if is_agent:
|
|
1039
1080
|
tags["agent_name"] = agent_id
|
|
1040
1081
|
tool_span.set_tags(tags)
|
|
@@ -1043,7 +1084,6 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
1043
1084
|
)
|
|
1044
1085
|
|
|
1045
1086
|
# Find matching tool result
|
|
1046
|
-
tool_id = tool_call.get("id")
|
|
1047
1087
|
for result in step.get("tool_results", []):
|
|
1048
1088
|
if result.get("tool_use_id") == tool_id:
|
|
1049
1089
|
result_content = result.get("content", "")
|
|
@@ -1345,14 +1385,16 @@ def main():
|
|
|
1345
1385
|
# Group messages into turns and send to CozeLoop — only if coze-context present.
|
|
1346
1386
|
turns = group_messages_into_turns(new_messages)
|
|
1347
1387
|
if turns:
|
|
1348
|
-
|
|
1349
|
-
|
|
1388
|
+
# coze-context 只出现在首条用户消息里(cozelab 注入的 agent 才有)。增量 hook 触发时,
|
|
1389
|
+
# 新消息往往全是工具调用/结果(不带 coze-context),故必须连同历史 turns 一起判断——
|
|
1390
|
+
# 只要整个会话出现过 coze-context,就说明是被注入的 agent,后续增量都应上报。
|
|
1391
|
+
def _turn_has_ctx(turn):
|
|
1392
|
+
return bool(coze_context_tags(
|
|
1350
1393
|
(turn.get("user_message", {}).get("message", {}) or {}).get("content")
|
|
1351
|
-
)
|
|
1352
|
-
|
|
1353
|
-
)
|
|
1394
|
+
))
|
|
1395
|
+
has_coze_ctx = any(_turn_has_ctx(t) for t in turns) or any(_turn_has_ctx(t) for t in history_turns)
|
|
1354
1396
|
if not has_coze_ctx:
|
|
1355
|
-
debug_log("No coze-context found in any turn, skipping upload.")
|
|
1397
|
+
debug_log("No coze-context found in any turn (incl. history), skipping upload.")
|
|
1356
1398
|
return
|
|
1357
1399
|
print(f"[CozeLoop] 开始上报: session={session_id}, turns={len(turns)}", file=sys.stderr)
|
|
1358
1400
|
send_turns_to_cozeloop(turns, session_id, history_turns)
|