coze-coding-utils 0.2.7__tar.gz → 0.2.8a1__tar.gz

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.
Files changed (38) hide show
  1. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/PKG-INFO +1 -1
  2. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/pyproject.toml +1 -1
  3. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/node_log.py +31 -5
  4. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/write_log.py +26 -16
  5. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/runtime_ctx/context.py +18 -0
  6. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/.gitignore +0 -0
  7. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/LICENSE +0 -0
  8. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/README.md +0 -0
  9. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/__init__.py +0 -0
  10. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/error/__init__.py +0 -0
  11. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/error/classifier.py +0 -0
  12. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/error/codes.py +0 -0
  13. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/error/exceptions.py +0 -0
  14. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/error/patterns.py +0 -0
  15. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/file/__init__.py +0 -0
  16. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/file/file.py +0 -0
  17. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/helper/__init__.py +0 -0
  18. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/helper/agent_helper.py +0 -0
  19. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/helper/graph_helper.py +0 -0
  20. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/helper/stream_runner.py +0 -0
  21. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/__init__.py +0 -0
  22. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/common.py +0 -0
  23. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/config.py +0 -0
  24. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/err_trace.py +0 -0
  25. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/loop_trace.py +0 -0
  26. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/log/parser.py +0 -0
  27. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/messages/__init__.py +0 -0
  28. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/messages/client.py +0 -0
  29. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/messages/server.py +0 -0
  30. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/__init__.py +0 -0
  31. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/converter/__init__.py +0 -0
  32. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/converter/request_converter.py +0 -0
  33. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/converter/response_converter.py +0 -0
  34. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/handler.py +0 -0
  35. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/types/__init__.py +0 -0
  36. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/types/request.py +0 -0
  37. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/openai/types/response.py +0 -0
  38. {coze_coding_utils-0.2.7 → coze_coding_utils-0.2.8a1}/src/coze_coding_utils/runtime_ctx/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coze-coding-utils
3
- Version: 0.2.7
3
+ Version: 0.2.8a1
4
4
  Summary: Utilities for Coze coding client runtime context and helpers.
5
5
  Project-URL: Homepage, https://code.byted.org/stone/coze-coding-client
6
6
  Author: Bytedance Stone Team
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coze-coding-utils"
7
- version = "0.2.7"
7
+ version = "0.2.8a1"
8
8
  description = "Utilities for Coze coding client runtime context and helpers."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -99,7 +99,7 @@ def create_log_entry(level="info", message="", timestamp=None, log_id=None, late
99
99
  input_data="", output_data="", node_id="", project_id="", commit_id="",
100
100
  execute_mode="run", caller="", node_type="", node_title="",
101
101
  token="", cost="", error_code="", error_message="", event_type="", execution_id="", node_name="",
102
- method=""):
102
+ method="", worktree_name="", user_id=""):
103
103
  """
104
104
  创建符合要求的日志条目
105
105
  :param level: 日志级别
@@ -124,6 +124,8 @@ def create_log_entry(level="info", message="", timestamp=None, log_id=None, late
124
124
  :param execution_id: 执行唯一ID(可选)
125
125
  :param node_name: 节点名称
126
126
  :param method: 方法名称
127
+ :param worktree_name: 当前 worktree 名(主仓为空字符串),便于多 worktree 并发排查
128
+ :param user_id: 当前用户 ID(沙箱 owner / 调用方),便于多用户并发排查
127
129
  :return: 格式化的日志字典
128
130
  """
129
131
  if timestamp is None:
@@ -156,10 +158,12 @@ def create_log_entry(level="info", message="", timestamp=None, log_id=None, late
156
158
  "execute_id": execution_id,
157
159
  "node_name": node_name,
158
160
  "method": method,
161
+ "worktree_name": worktree_name,
162
+ "user_id": user_id,
159
163
  }
160
164
 
161
165
 
162
- def log_workflow_start(project_id, commit_id, log_id=None, execute_id="", input_data="", method=""):
166
+ def log_workflow_start(project_id, commit_id, log_id=None, execute_id="", input_data="", method="", worktree_name="", user_id=""):
163
167
  """
164
168
  记录流程开始日志
165
169
  :param project_id: 项目ID
@@ -167,6 +171,8 @@ def log_workflow_start(project_id, commit_id, log_id=None, execute_id="", input_
167
171
  :param is_test_run: 是否试运行
168
172
  :param log_id: 日志ID(可选)
169
173
  :param execute_id: 执行唯一ID(可选)
174
+ :param worktree_name: 当前 worktree 名
175
+ :param user_id: 当前用户 ID
170
176
  """
171
177
  event_type = "test_run_start" if not is_prod() else "run_start"
172
178
  execute_mode = "test_run" if not is_prod() else "run"
@@ -184,13 +190,15 @@ def log_workflow_start(project_id, commit_id, log_id=None, execute_id="", input_
184
190
  execution_id=execute_id,
185
191
  input_data=input_data,
186
192
  method=method,
193
+ worktree_name=worktree_name,
194
+ user_id=user_id,
187
195
  )
188
196
 
189
197
  write_log(log_entry)
190
198
 
191
199
 
192
200
  def log_workflow_end(execution_id, output=None, total_time=None, status="success", token_consumed=None,
193
- error_reason=None, error_code=None, is_test_run=False, log_id="", method=""):
201
+ error_reason=None, error_code=None, is_test_run=False, log_id="", method="", worktree_name="", user_id=""):
194
202
  """
195
203
  记录流程结束日志
196
204
  :param execution_id: 执行唯一ID
@@ -201,6 +209,8 @@ def log_workflow_end(execution_id, output=None, total_time=None, status="success
201
209
  :param error_reason: 错误原因
202
210
  :param error_code: 错误码
203
211
  :param is_test_run: 是否试运行
212
+ :param worktree_name: 当前 worktree 名
213
+ :param user_id: 当前用户 ID
204
214
  """
205
215
  level = "error" if status == "error" else "info"
206
216
  execute_mode = "test_run" if is_test_run else "run"
@@ -222,6 +232,8 @@ def log_workflow_end(execution_id, output=None, total_time=None, status="success
222
232
  execution_id=execution_id,
223
233
  log_id=log_id,
224
234
  method=method,
235
+ worktree_name=worktree_name,
236
+ user_id=user_id,
225
237
  )
226
238
 
227
239
  write_log(log_entry)
@@ -271,7 +283,9 @@ class Logger(BaseCallbackHandler):
271
283
  event_type="node_start",
272
284
  log_id=self.runtime_ctx.logid,
273
285
  method=self.runtime_ctx.method,
274
- node_type="condition"
286
+ node_type="condition",
287
+ worktree_name=self.runtime_ctx.worktree_name,
288
+ user_id=self.runtime_ctx.user_id,
275
289
  )
276
290
  write_log(log_entry)
277
291
  return
@@ -290,6 +304,8 @@ class Logger(BaseCallbackHandler):
290
304
  log_id=self.runtime_ctx.logid,
291
305
  node_name=node_info.name,
292
306
  method=self.runtime_ctx.method,
307
+ worktree_name=self.runtime_ctx.worktree_name,
308
+ user_id=self.runtime_ctx.user_id,
293
309
  )
294
310
  write_log(log_entry)
295
311
 
@@ -321,7 +337,9 @@ class Logger(BaseCallbackHandler):
321
337
  event_type="node_end",
322
338
  log_id=self.runtime_ctx.logid,
323
339
  method=self.runtime_ctx.method,
324
- node_type="condition"
340
+ node_type="condition",
341
+ worktree_name=self.runtime_ctx.worktree_name,
342
+ user_id=self.runtime_ctx.user_id,
325
343
  )
326
344
  write_log(log_entry)
327
345
  return
@@ -340,6 +358,8 @@ class Logger(BaseCallbackHandler):
340
358
  log_id=self.runtime_ctx.logid,
341
359
  node_name=node_info.name,
342
360
  method=self.runtime_ctx.method,
361
+ worktree_name=self.runtime_ctx.worktree_name,
362
+ user_id=self.runtime_ctx.user_id,
343
363
  )
344
364
  write_log(log_entry)
345
365
 
@@ -354,6 +374,8 @@ class Logger(BaseCallbackHandler):
354
374
  execute_id=self.runtime_ctx.run_id,
355
375
  input_data=_serialize_data(inputs),
356
376
  method=self.runtime_ctx.method,
377
+ worktree_name=self.runtime_ctx.worktree_name,
378
+ user_id=self.runtime_ctx.user_id,
357
379
  )
358
380
 
359
381
  def _on_graph_end(self, outputs: Dict[str, Any]):
@@ -367,6 +389,8 @@ class Logger(BaseCallbackHandler):
367
389
  log_id=self.runtime_ctx.logid,
368
390
  is_test_run=not is_prod(),
369
391
  method=self.runtime_ctx.method,
392
+ worktree_name=self.runtime_ctx.worktree_name,
393
+ user_id=self.runtime_ctx.user_id,
370
394
  )
371
395
 
372
396
  def on_chain_error(
@@ -409,6 +433,8 @@ class Logger(BaseCallbackHandler):
409
433
  error_message=str(error),
410
434
  node_name=node_name,
411
435
  method=self.runtime_ctx.method,
436
+ worktree_name=self.runtime_ctx.worktree_name,
437
+ user_id=self.runtime_ctx.user_id,
412
438
  )
413
439
  write_log(error_log_entry)
414
440
 
@@ -11,10 +11,10 @@ request_context: ContextVar[Optional[Context]] = ContextVar('request_context', d
11
11
 
12
12
 
13
13
  class ContextFilter(logging.Filter):
14
-
14
+
15
15
  def filter(self, record: logging.LogRecord) -> bool:
16
16
  ctx = request_context.get()
17
-
17
+
18
18
  if ctx:
19
19
  record.log_id = ctx.logid or ''
20
20
  record.run_id = ctx.run_id or ''
@@ -22,6 +22,8 @@ class ContextFilter(logging.Filter):
22
22
  record.project_id = ctx.project_id or ''
23
23
  record.method = ctx.method or ''
24
24
  record.x_tt_env = ctx.x_tt_env or ''
25
+ record.worktree_name = ctx.worktree_name or ''
26
+ record.user_id = ctx.user_id or ''
25
27
  else:
26
28
  record.log_id = ''
27
29
  record.run_id = ''
@@ -29,7 +31,9 @@ class ContextFilter(logging.Filter):
29
31
  record.project_id = ''
30
32
  record.method = ''
31
33
  record.x_tt_env = ''
32
-
34
+ record.worktree_name = ''
35
+ record.user_id = ''
36
+
33
37
  return True
34
38
 
35
39
 
@@ -44,7 +48,7 @@ class APSchedulerFilter(logging.Filter):
44
48
 
45
49
 
46
50
  class JsonFormatter(logging.Formatter):
47
-
51
+
48
52
  def format(self, record: logging.LogRecord) -> str:
49
53
  log_data = {
50
54
  'message': record.getMessage(),
@@ -57,29 +61,32 @@ class JsonFormatter(logging.Formatter):
57
61
  'project_id': getattr(record, 'project_id', ''),
58
62
  'method': getattr(record, 'method', ''),
59
63
  'x_tt_env': getattr(record, 'x_tt_env', ''),
64
+ 'worktree_name': getattr(record, 'worktree_name', ''),
65
+ 'user_id': getattr(record, 'user_id', ''),
60
66
  'lineno': record.lineno,
61
67
  'funcName': record.funcName,
62
68
  }
63
69
 
64
70
  if record.exc_info:
65
71
  log_data['exc_info'] = self.formatException(record.exc_info)
66
-
72
+
67
73
  for key, value in record.__dict__.items():
68
- if key not in ['name', 'msg', 'args', 'created', 'filename', 'funcName',
69
- 'levelname', 'levelno', 'lineno', 'module', 'msecs',
74
+ if key not in ['name', 'msg', 'args', 'created', 'filename', 'funcName',
75
+ 'levelname', 'levelno', 'lineno', 'module', 'msecs',
70
76
  'message', 'pathname', 'process', 'processName', 'relativeCreated',
71
77
  'thread', 'threadName', 'exc_info', 'exc_text', 'stack_info',
72
78
  'log_id', 'run_id', 'space_id', 'project_id', 'method',
73
- 'x_tt_env', 'rpc_persist_rec_rec_biz_scene',
79
+ 'x_tt_env', 'worktree_name', 'user_id',
80
+ 'rpc_persist_rec_rec_biz_scene',
74
81
  'rpc_persist_coze_record_root_id', 'rpc_persist_rec_root_entity_type',
75
82
  'rpc_persist_rec_root_entity_id']:
76
83
  log_data[key] = value
77
-
84
+
78
85
  return json.dumps(log_data, ensure_ascii=False)
79
86
 
80
87
 
81
88
  class PlainTextFormatter(logging.Formatter):
82
-
89
+
83
90
  def format(self, record: logging.LogRecord) -> str:
84
91
  log_data = {
85
92
  'message': record.getMessage(),
@@ -92,24 +99,27 @@ class PlainTextFormatter(logging.Formatter):
92
99
  'project_id': getattr(record, 'project_id', ''),
93
100
  'method': getattr(record, 'method', ''),
94
101
  'x_tt_env': getattr(record, 'x_tt_env', ''),
102
+ 'worktree_name': getattr(record, 'worktree_name', ''),
103
+ 'user_id': getattr(record, 'user_id', ''),
95
104
  'lineno': record.lineno,
96
105
  'funcName': record.funcName,
97
106
  }
98
-
107
+
99
108
  if record.exc_info:
100
109
  log_data['exc_info'] = self.formatException(record.exc_info)
101
-
110
+
102
111
  for key, value in record.__dict__.items():
103
- if key not in ['name', 'msg', 'args', 'created', 'filename', 'funcName',
104
- 'levelname', 'levelno', 'lineno', 'module', 'msecs',
112
+ if key not in ['name', 'msg', 'args', 'created', 'filename', 'funcName',
113
+ 'levelname', 'levelno', 'lineno', 'module', 'msecs',
105
114
  'message', 'pathname', 'process', 'processName', 'relativeCreated',
106
115
  'thread', 'threadName', 'exc_info', 'exc_text', 'stack_info',
107
116
  'log_id', 'run_id', 'space_id', 'project_id', 'method',
108
- 'x_tt_env', 'rpc_persist_rec_rec_biz_scene',
117
+ 'x_tt_env', 'worktree_name', 'user_id',
118
+ 'rpc_persist_rec_rec_biz_scene',
109
119
  'rpc_persist_coze_record_root_id', 'rpc_persist_rec_root_entity_type',
110
120
  'rpc_persist_rec_root_entity_id']:
111
121
  log_data[key] = value
112
-
122
+
113
123
  return json.dumps(log_data, ensure_ascii=False)
114
124
 
115
125
 
@@ -20,10 +20,17 @@ HEADER_RPC_PERSIST_RES_REC_ROOT_ENTITY_ID = (
20
20
  HEADER_RPC_PERSIST_RES_REC_EXT_INFO = (
21
21
  "rpc-persist-res-rec-ext-info" # 扩展信息,json字符串格式
22
22
  )
23
+ # 多 worktree / 多用户排查诊断用:上游可通过 header 透传这两个字段,让本次 ctx 持有它们,
24
+ # 下游的 LangGraph callback / 业务日志直接 record 出来,多会话并发时按字段过滤排查。
25
+ HEADER_X_COZE_WORKTREE_NAME = "x-coze-worktree-name"
26
+ HEADER_X_COZE_USER_ID = "x-coze-user-id"
23
27
 
24
28
  # Environment variable keys
25
29
  ENV_SPACE_ID = "COZE_PROJECT_SPACE_ID"
26
30
  ENV_PROJECT_ID = "COZE_PROJECT_ID"
31
+ # 沙箱拉起产物子进程时注入。worktree_name 主仓为空字符串;user_id 为创建沙箱的用户 ID。
32
+ ENV_WORKTREE_NAME = "COZE_WORKTREE_NAME"
33
+ ENV_USER_ID = "COZE_USER_ID"
27
34
 
28
35
 
29
36
  @dataclass(slots=True)
@@ -36,6 +43,11 @@ class Context:
36
43
  logid: str = ""
37
44
  method: str = ""
38
45
 
46
+ # 多 worktree / 多用户支持:默认读环境变量(沙箱拉起产物时注入),上游 HTTP 请求 header 可覆盖。
47
+ # 用 "" 表示主仓 / 未提供,避免 None 在 logging record 里出现 'None' 字面值。
48
+ worktree_name: str = ""
49
+ user_id: str = ""
50
+
39
51
  x_run_mode: Optional[str] = None
40
52
  x_tt_env: Optional[str] = None
41
53
  x_use_ppe: Optional[str] = None
@@ -58,6 +70,8 @@ def new_context(
58
70
  space_id=os.getenv(ENV_SPACE_ID, ""),
59
71
  project_id=os.getenv(ENV_PROJECT_ID, ""),
60
72
  method=method,
73
+ worktree_name=os.getenv(ENV_WORKTREE_NAME, ""),
74
+ user_id=os.getenv(ENV_USER_ID, ""),
61
75
  )
62
76
  if headers:
63
77
  norm = {k.casefold(): v for k, v in headers.items()}
@@ -67,6 +81,8 @@ def new_context(
67
81
  HEADER_X_USE_PPE: "x_use_ppe",
68
82
  HEADER_X_TT_ENV_FE: "x_tt_env_fe",
69
83
  HEADER_X_RUN_MODE: "x_run_mode",
84
+ HEADER_X_COZE_WORKTREE_NAME: "worktree_name",
85
+ HEADER_X_COZE_USER_ID: "user_id",
70
86
  HEADER_RPC_PERSIST_RES_REC_BIZ_SCENE: "rpc_persist_res_rec_biz_scene",
71
87
  HEADER_RPC_PERSIST_COZE_RECORD_ROOT_ID: "rpc_persist_coze_record_root_id",
72
88
  HEADER_RPC_PERSIST_RES_REC_ROOT_ENTITY_TYPE: "rpc_persist_res_rec_root_entity_type",
@@ -91,6 +107,8 @@ def default_headers(ctx: Context | None) -> Dict[str, str]:
91
107
  "x_use_ppe": HEADER_X_USE_PPE,
92
108
  "x_tt_env_fe": HEADER_X_TT_ENV_FE,
93
109
  "x_run_mode": HEADER_X_RUN_MODE,
110
+ "worktree_name": HEADER_X_COZE_WORKTREE_NAME,
111
+ "user_id": HEADER_X_COZE_USER_ID,
94
112
  "rpc_persist_res_rec_biz_scene": HEADER_RPC_PERSIST_RES_REC_BIZ_SCENE,
95
113
  "rpc_persist_coze_record_root_id": HEADER_RPC_PERSIST_COZE_RECORD_ROOT_ID,
96
114
  "rpc_persist_res_rec_root_entity_type": HEADER_RPC_PERSIST_RES_REC_ROOT_ENTITY_TYPE,