coze_lab 0.1.47 → 0.1.49
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
|
@@ -1472,7 +1472,12 @@ def send_steps_realtime(turns, session_id, history_turns, state, coze_tags_overr
|
|
|
1472
1472
|
if token:
|
|
1473
1473
|
os.environ["COZELOOP_API_TOKEN"] = token
|
|
1474
1474
|
workspace_id = os.environ.get("COZELOOP_WORKSPACE_ID", "") or _DEFAULT_WORKSPACE_ID
|
|
1475
|
-
|
|
1475
|
+
upload_events: List[str] = []
|
|
1476
|
+
client_kwargs = {
|
|
1477
|
+
"ultra_large_report": True,
|
|
1478
|
+
"upload_timeout": 120,
|
|
1479
|
+
"trace_finish_event_processor": _make_finish_event_processor(upload_events),
|
|
1480
|
+
}
|
|
1476
1481
|
if workspace_id:
|
|
1477
1482
|
client_kwargs["workspace_id"] = workspace_id
|
|
1478
1483
|
if token:
|
|
@@ -1539,6 +1544,9 @@ def send_steps_realtime(turns, session_id, history_turns, state, coze_tags_overr
|
|
|
1539
1544
|
# 实测:root finish 后,子 span 仍可用其 header 挂到同一 trace_id 下,不影响后续增量。
|
|
1540
1545
|
root_span.finish()
|
|
1541
1546
|
client.flush()
|
|
1547
|
+
if upload_events:
|
|
1548
|
+
debug_log(f"[rt] upload failed after root flush: {upload_events[-1][:500]}")
|
|
1549
|
+
return None
|
|
1542
1550
|
debug_log(f"[rt] root created+finished trace_id={getattr(root_ctx,'trace_id','?')}")
|
|
1543
1551
|
|
|
1544
1552
|
# ---- 发新完成的 step ----
|
|
@@ -1622,6 +1630,9 @@ def send_steps_realtime(turns, session_id, history_turns, state, coze_tags_overr
|
|
|
1622
1630
|
sent += 1
|
|
1623
1631
|
# 每个 step 立即 flush —— 这是“结束即可见”的关键。
|
|
1624
1632
|
client.flush()
|
|
1633
|
+
if upload_events:
|
|
1634
|
+
debug_log(f"[rt] upload failed after step flush: {upload_events[-1][:500]}")
|
|
1635
|
+
return None
|
|
1625
1636
|
|
|
1626
1637
|
new_last = last_global + sent
|
|
1627
1638
|
state["rt_last_global_step"] = new_last
|
|
@@ -1650,8 +1661,15 @@ def send_steps_realtime(turns, session_id, history_turns, state, coze_tags_overr
|
|
|
1650
1661
|
fin.set_output(last_output)
|
|
1651
1662
|
fin.finish()
|
|
1652
1663
|
client.flush()
|
|
1664
|
+
if upload_events:
|
|
1665
|
+
debug_log(f"[rt] upload failed after final flush: {upload_events[-1][:500]}")
|
|
1666
|
+
return None
|
|
1653
1667
|
debug_log(f"[rt] finalized, total sent steps={new_last}")
|
|
1654
1668
|
|
|
1669
|
+
if upload_events:
|
|
1670
|
+
debug_log(f"[rt] upload failed, state not advanced. Last failure: {upload_events[-1][:500]}")
|
|
1671
|
+
return None
|
|
1672
|
+
|
|
1655
1673
|
debug_log(f"[rt] sent {sent} new step(s), last_global={new_last}/{total_steps}, terminal={is_terminal}")
|
|
1656
1674
|
return True
|
|
1657
1675
|
except Exception as e:
|
|
@@ -131,6 +131,127 @@ else:
|
|
|
131
131
|
|
|
132
132
|
# --- Configuration ---
|
|
133
133
|
DEBUG = os.environ.get("CC_COZELOOP_DEBUG", "").lower() == "true"
|
|
134
|
+
_DEFAULT_WORKSPACE_ID = "7649231955045072915" # hardcoded spaceID fallback
|
|
135
|
+
|
|
136
|
+
# 实时 tool span 上报的批量间隔(秒):距上次上报不足此值则本次不发,把已结束的 tool span
|
|
137
|
+
# 攒到下次一起发(最多每 5s 一批)。终态(Stop/SubagentStop)不受限,立即收尾。
|
|
138
|
+
REALTIME_BATCH_INTERVAL = float(os.environ.get("COZELOOP_REALTIME_BATCH_INTERVAL", "5"))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# --- coze-context parsing -------------------------------------------------
|
|
142
|
+
# User messages may embed a block like:
|
|
143
|
+
# <coze-context>
|
|
144
|
+
# account_id: 0
|
|
145
|
+
# agent_id: 7644920552473395499
|
|
146
|
+
# session_id: 7644919579054997796
|
|
147
|
+
# message_id: 04dd5246-...
|
|
148
|
+
# </coze-context>
|
|
149
|
+
# We parse its key:value pairs and inject them into the trace.
|
|
150
|
+
|
|
151
|
+
_COZE_CTX_OPEN = "<coze-context>"
|
|
152
|
+
_COZE_CTX_CLOSE = "</coze-context>"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def parse_coze_context(text: str) -> Dict[str, str]:
|
|
156
|
+
"""Extract the LAST <coze-context> block's key:value pairs from text.
|
|
157
|
+
|
|
158
|
+
Returns {} if no block is present. Tag keys are prefixed with
|
|
159
|
+
'coze_' by the caller; here we return raw keys as written.
|
|
160
|
+
"""
|
|
161
|
+
if not text or _COZE_CTX_OPEN not in text:
|
|
162
|
+
return {}
|
|
163
|
+
# Take the last occurrence (latest context wins).
|
|
164
|
+
open_idx = text.rfind(_COZE_CTX_OPEN)
|
|
165
|
+
close_idx = text.find(_COZE_CTX_CLOSE, open_idx)
|
|
166
|
+
if close_idx == -1:
|
|
167
|
+
return {}
|
|
168
|
+
body = text[open_idx + len(_COZE_CTX_OPEN):close_idx]
|
|
169
|
+
# The block may arrive with real newlines, OR with literal backslash-n
|
|
170
|
+
# (e.g. when the whole message is an embedded JSON string that was never
|
|
171
|
+
# un-escaped). Normalize both forms before splitting into lines.
|
|
172
|
+
body = body.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\r", "\n")
|
|
173
|
+
result: Dict[str, str] = {}
|
|
174
|
+
for line in body.splitlines():
|
|
175
|
+
line = line.strip()
|
|
176
|
+
if not line or ":" not in line:
|
|
177
|
+
continue
|
|
178
|
+
key, _, value = line.partition(":")
|
|
179
|
+
key = key.strip()
|
|
180
|
+
value = value.strip()
|
|
181
|
+
if key:
|
|
182
|
+
result[key] = value
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def turn_coze_context(turn: Dict[str, Any]) -> Dict[str, str]:
|
|
187
|
+
"""Extract coze-context from a grouped turn with fallbacks for Codex rollout shapes."""
|
|
188
|
+
texts = [turn.get("user_message_text", "")]
|
|
189
|
+
for msg in turn.get("input_messages", []):
|
|
190
|
+
if isinstance(msg, dict):
|
|
191
|
+
content = msg.get("content", "")
|
|
192
|
+
if isinstance(content, str):
|
|
193
|
+
texts.append(content)
|
|
194
|
+
elif isinstance(content, list):
|
|
195
|
+
texts.append(extract_message_content_text({"content": content}))
|
|
196
|
+
elif isinstance(content, dict):
|
|
197
|
+
texts.append(extract_message_content_text({"content": [content]}))
|
|
198
|
+
user_payload = turn.get("user_message")
|
|
199
|
+
if isinstance(user_payload, dict):
|
|
200
|
+
texts.append(extract_message_content_text(user_payload))
|
|
201
|
+
for text in texts:
|
|
202
|
+
ctx = parse_coze_context(text)
|
|
203
|
+
if ctx:
|
|
204
|
+
return ctx
|
|
205
|
+
return {}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# --- trace upload failure / logid capture ---------------------------------
|
|
209
|
+
def _extract_logid(msg: str) -> str:
|
|
210
|
+
"""Pull the server logid out of an SDK error message, if present.
|
|
211
|
+
|
|
212
|
+
SDK failure messages embed it as 'logid=XXXX' (sometimes within brackets).
|
|
213
|
+
"""
|
|
214
|
+
if not msg:
|
|
215
|
+
return ""
|
|
216
|
+
marker = "logid="
|
|
217
|
+
idx = msg.find(marker)
|
|
218
|
+
if idx == -1:
|
|
219
|
+
return ""
|
|
220
|
+
rest = msg[idx + len(marker):]
|
|
221
|
+
logid = []
|
|
222
|
+
for ch in rest:
|
|
223
|
+
if ch.isalnum():
|
|
224
|
+
logid.append(ch)
|
|
225
|
+
else:
|
|
226
|
+
break
|
|
227
|
+
return "".join(logid)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _make_finish_event_processor(upload_events: Optional[List[str]] = None):
|
|
231
|
+
"""Return a trace_finish_event_processor that surfaces failures + logid.
|
|
232
|
+
|
|
233
|
+
The CozeLoop SDK calls this for each flush event; on failure we print the
|
|
234
|
+
server logid to stderr so it can be handed to platform support for tracing
|
|
235
|
+
the root cause (e.g. via `bytedcli log get-logid-log <logid>`).
|
|
236
|
+
"""
|
|
237
|
+
def _processor(info):
|
|
238
|
+
try:
|
|
239
|
+
if not getattr(info, "is_event_fail", False):
|
|
240
|
+
hook_log("upload success")
|
|
241
|
+
return
|
|
242
|
+
detail = getattr(info, "detail_msg", "") or ""
|
|
243
|
+
if upload_events is not None:
|
|
244
|
+
upload_events.append(detail or "trace export failed")
|
|
245
|
+
logid = _extract_logid(detail)
|
|
246
|
+
if logid:
|
|
247
|
+
hook_log(f"upload failed logid={logid} detail={detail[:500]}")
|
|
248
|
+
print(f"[CozeLoop] 上报失败 logid={logid} (可用 bytedcli log get-logid-log {logid} 排查)", file=sys.stderr)
|
|
249
|
+
else:
|
|
250
|
+
hook_log(f"upload failed detail={detail[:500]}")
|
|
251
|
+
print(f"[CozeLoop] 上报失败: {detail[:300]}", file=sys.stderr)
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
return _processor
|
|
134
255
|
|
|
135
256
|
|
|
136
257
|
def _normalize_api_base_url(raw: str) -> str:
|
|
@@ -1251,7 +1372,12 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1251
1372
|
if token:
|
|
1252
1373
|
os.environ["COZELOOP_API_TOKEN"] = token
|
|
1253
1374
|
workspace_id = os.environ.get("COZELOOP_WORKSPACE_ID", "") or _DEFAULT_WORKSPACE_ID
|
|
1254
|
-
|
|
1375
|
+
upload_events: List[str] = []
|
|
1376
|
+
client_kwargs = {
|
|
1377
|
+
"ultra_large_report": True,
|
|
1378
|
+
"upload_timeout": 120,
|
|
1379
|
+
"trace_finish_event_processor": _make_finish_event_processor(upload_events),
|
|
1380
|
+
}
|
|
1255
1381
|
if workspace_id:
|
|
1256
1382
|
client_kwargs["workspace_id"] = workspace_id
|
|
1257
1383
|
if token:
|
|
@@ -1307,6 +1433,9 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1307
1433
|
root_ctx = client.get_span_from_header(state["rt_root_header"])
|
|
1308
1434
|
root_span.finish() # 立即 finish 落库(后端按 root_span 查),子 span 仍可挂同 trace
|
|
1309
1435
|
client.flush()
|
|
1436
|
+
if upload_events:
|
|
1437
|
+
hook_log(f"[rt] upload failed after root flush detail={upload_events[-1][:500]}")
|
|
1438
|
+
return None
|
|
1310
1439
|
hook_log(f"[rt] root created trace_id={getattr(root_ctx,'trace_id','?')}")
|
|
1311
1440
|
|
|
1312
1441
|
# 线性展开所有 tool_call(带 result 的才算完成),按全局序增量发
|
|
@@ -1361,6 +1490,9 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1361
1490
|
tspan.finish()
|
|
1362
1491
|
sent += 1
|
|
1363
1492
|
client.flush() # 每个 tool_call 立即 flush → 结束即可见
|
|
1493
|
+
if upload_events:
|
|
1494
|
+
hook_log(f"[rt] upload failed after tool flush detail={upload_events[-1][:500]}")
|
|
1495
|
+
return None
|
|
1364
1496
|
|
|
1365
1497
|
state["rt_last_tool"] = last_tool + sent
|
|
1366
1498
|
if sent > 0:
|
|
@@ -1382,8 +1514,15 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1382
1514
|
fin.set_output(truncate_text(last_out))
|
|
1383
1515
|
fin.finish()
|
|
1384
1516
|
client.flush()
|
|
1517
|
+
if upload_events:
|
|
1518
|
+
hook_log(f"[rt] upload failed after final flush detail={upload_events[-1][:500]}")
|
|
1519
|
+
return None
|
|
1385
1520
|
hook_log(f"[rt] finalized total_tools={state['rt_last_tool']}")
|
|
1386
1521
|
|
|
1522
|
+
if upload_events:
|
|
1523
|
+
hook_log(f"[rt] upload failed state not advanced detail={upload_events[-1][:500]}")
|
|
1524
|
+
return None
|
|
1525
|
+
|
|
1387
1526
|
hook_log(f"[rt] sent {sent} tool span(s), last_tool={state['rt_last_tool']}, terminal={is_terminal}")
|
|
1388
1527
|
return True
|
|
1389
1528
|
except Exception as e:
|