coze-coding-utils 0.2.1__py3-none-any.whl → 0.2.2a1__py3-none-any.whl

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 (36) hide show
  1. coze_coding_utils/__init__.py +1 -1
  2. coze_coding_utils/error/__init__.py +31 -0
  3. coze_coding_utils/error/classifier.py +320 -0
  4. coze_coding_utils/error/codes.py +356 -0
  5. coze_coding_utils/error/exceptions.py +439 -0
  6. coze_coding_utils/error/patterns.py +939 -0
  7. coze_coding_utils/error/test_classifier.py +0 -0
  8. coze_coding_utils/file/__init__.py +0 -0
  9. coze_coding_utils/file/file.py +327 -0
  10. coze_coding_utils/helper/__init__.py +0 -0
  11. coze_coding_utils/helper/agent_helper.py +599 -0
  12. coze_coding_utils/helper/graph_helper.py +231 -0
  13. coze_coding_utils/log/__init__.py +0 -0
  14. coze_coding_utils/log/common.py +8 -0
  15. coze_coding_utils/log/config.py +10 -0
  16. coze_coding_utils/log/err_trace.py +88 -0
  17. coze_coding_utils/log/loop_trace.py +72 -0
  18. coze_coding_utils/log/node_log.py +487 -0
  19. coze_coding_utils/log/parser.py +255 -0
  20. coze_coding_utils/log/write_log.py +183 -0
  21. coze_coding_utils/messages/__init__.py +0 -0
  22. coze_coding_utils/messages/client.py +48 -0
  23. coze_coding_utils/messages/server.py +173 -0
  24. coze_coding_utils/openai/__init__.py +5 -0
  25. coze_coding_utils/openai/converter/__init__.py +6 -0
  26. coze_coding_utils/openai/converter/request_converter.py +165 -0
  27. coze_coding_utils/openai/converter/response_converter.py +467 -0
  28. coze_coding_utils/openai/handler.py +298 -0
  29. coze_coding_utils/openai/types/__init__.py +37 -0
  30. coze_coding_utils/openai/types/request.py +24 -0
  31. coze_coding_utils/openai/types/response.py +178 -0
  32. {coze_coding_utils-0.2.1.dist-info → coze_coding_utils-0.2.2a1.dist-info}/METADATA +2 -2
  33. coze_coding_utils-0.2.2a1.dist-info/RECORD +37 -0
  34. coze_coding_utils-0.2.1.dist-info/RECORD +0 -7
  35. {coze_coding_utils-0.2.1.dist-info → coze_coding_utils-0.2.2a1.dist-info}/WHEEL +0 -0
  36. {coze_coding_utils-0.2.1.dist-info → coze_coding_utils-0.2.2a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,439 @@
1
+ """
2
+ 自定义异常类和错误分类函数
3
+ """
4
+
5
+ import re
6
+ import traceback
7
+ from typing import Optional, Any, Dict, Tuple
8
+
9
+ from .codes import ErrorCode, ErrorCategory, get_error_description
10
+ from .patterns import match_error_pattern, match_traceback_pattern, match_custom_exception_pattern, ERROR_PATTERNS
11
+
12
+
13
+ class VibeCodingError(Exception):
14
+ """
15
+ VibeCoding统一异常基类
16
+
17
+ Attributes:
18
+ code: 6位错误码
19
+ message: 错误消息
20
+ original_error: 原始异常
21
+ context: 错误上下文信息
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ code: int,
27
+ message: str = "",
28
+ original_error: Optional[BaseException] = None,
29
+ context: Optional[Dict[str, Any]] = None,
30
+ ):
31
+ self.code = code
32
+ self.message = message or get_error_description(code)
33
+ self.original_error = original_error
34
+ self.context = context or {}
35
+
36
+ super().__init__(self.message)
37
+
38
+ @property
39
+ def category(self) -> ErrorCategory:
40
+ """获取错误大类"""
41
+ category = self.code // 100000
42
+ try:
43
+ return ErrorCategory(category)
44
+ except ValueError:
45
+ return ErrorCategory.UNKNOWN_ERROR
46
+
47
+ def to_dict(self) -> Dict[str, Any]:
48
+ """转换为字典格式"""
49
+ return {
50
+ "code": self.code,
51
+ "message": self.message,
52
+ "category": self.category.name,
53
+ "context": self.context,
54
+ "original_error": str(self.original_error) if self.original_error else None,
55
+ }
56
+
57
+ def __str__(self) -> str:
58
+ return f"[{self.code}] {self.message}"
59
+
60
+ def __repr__(self) -> str:
61
+ return f"VibeCodingError(code={self.code}, message={self.message!r})"
62
+
63
+
64
+ def classify_error(error: BaseException, context: Optional[Dict[str, Any]] = None) -> VibeCodingError:
65
+ """
66
+ 将原始异常分类为VibeCodingError
67
+
68
+ Args:
69
+ error: 原始异常
70
+ context: 额外的上下文信息
71
+
72
+ Returns:
73
+ VibeCodingError: 分类后的错误
74
+ """
75
+ if isinstance(error, VibeCodingError):
76
+ if context:
77
+ error.context.update(context)
78
+ return error
79
+
80
+ error_str = str(error)
81
+ error_type = type(error).__name__
82
+ ctx = context or {}
83
+
84
+ ctx["original_type"] = error_type
85
+ ctx["original_message"] = error_str
86
+
87
+ code, message = _classify_by_type_and_message(error_type, error_str, error)
88
+
89
+ return VibeCodingError(
90
+ code=code,
91
+ message=message,
92
+ original_error=error,
93
+ context=ctx,
94
+ )
95
+
96
+
97
+ def _classify_by_type_and_message(
98
+ error_type: str, error_str: str, error: BaseException
99
+ ) -> Tuple[int, str]:
100
+ """
101
+ 根据错误类型和消息分类错误
102
+
103
+ Returns:
104
+ (error_code, error_message)
105
+ """
106
+
107
+ if error_type == "AttributeError":
108
+ return _classify_attribute_error(error_str)
109
+
110
+ if error_type == "TypeError":
111
+ return _classify_type_error(error_str)
112
+
113
+ if "ValidationError" in error_type:
114
+ return _classify_validation_error(error_str)
115
+
116
+ if error_type == "ValueError":
117
+ return _classify_value_error(error_str)
118
+
119
+ if error_type == "KeyError":
120
+ return ErrorCode.CODE_KEY_NOT_FOUND, f"键不存在: {error_str}"
121
+
122
+ if error_type == "IndexError":
123
+ return ErrorCode.CODE_INDEX_OUT_OF_RANGE, f"索引越界: {error_str}"
124
+
125
+ if error_type == "NameError":
126
+ return ErrorCode.CODE_NAME_NOT_DEFINED, f"名称未定义: {error_str}"
127
+
128
+ if error_type in ("ImportError", "ModuleNotFoundError"):
129
+ error_lower = error_str.lower()
130
+ if any(lib in error_lower for lib in ["numpy", "moviepy", "cv2", "opencv", "PIL", "pillow", "torch", "tensorflow"]):
131
+ return ErrorCode.CONFIG_ENV_INVALID, f"依赖库配置错误: {error_str}"
132
+ if "cannot import name" in error_lower:
133
+ return ErrorCode.CONFIG_ENV_INVALID, f"依赖库版本不兼容: {error_str}"
134
+ return ErrorCode.CODE_NAME_IMPORT_ERROR, f"模块导入错误: {error_str}"
135
+
136
+ if error_type == "SyntaxError":
137
+ return ErrorCode.CODE_SYNTAX_INVALID, f"语法错误: {error_str}"
138
+
139
+ if error_type == "IndentationError":
140
+ return ErrorCode.CODE_SYNTAX_INDENTATION, f"缩进错误: {error_str}"
141
+
142
+ if error_type == "NotImplementedError":
143
+ if "async" in error_str.lower() or "awrap" in error_str.lower():
144
+ return ErrorCode.RUNTIME_ASYNC_NOT_IMPL, f"异步方法未实现: {error_str}"
145
+ return ErrorCode.RUNTIME_EXECUTION_FAILED, f"功能未实现: {error_str}"
146
+
147
+ if error_type in ("TimeoutError", "asyncio.TimeoutError"):
148
+ if "subprocess" in error_str.lower():
149
+ return ErrorCode.RUNTIME_SUBPROCESS_TIMEOUT, f"子进程执行超时: {error_str}"
150
+ if "requests" in error_str.lower():
151
+ return ErrorCode.API_NETWORK_TIMEOUT, f"请求超时: {error_str}"
152
+ return ErrorCode.RUNTIME_TIMEOUT, f"执行超时: {error_str}"
153
+
154
+ if error_type == "RuntimeError":
155
+ return _classify_runtime_error(error_str)
156
+
157
+ if "APIError" in error_type or "openai" in error_type.lower():
158
+ return _classify_api_error(error_str)
159
+
160
+ if error_type in ("ConnectionError", "ConnectionRefusedError", "ConnectionResetError"):
161
+ return ErrorCode.API_NETWORK_CONNECTION, f"网络连接错误: {error_str}"
162
+
163
+ if error_type == "FileNotFoundError":
164
+ return ErrorCode.RESOURCE_FILE_NOT_FOUND, f"文件不存在: {error_str}"
165
+
166
+ if error_type in ("IOError", "OSError"):
167
+ return _classify_io_error(error_str)
168
+
169
+ if error_type == "MemoryError":
170
+ return ErrorCode.RUNTIME_MEMORY_ERROR, f"内存不足: {error_str}"
171
+
172
+ if error_type == "RecursionError":
173
+ return ErrorCode.RUNTIME_RECURSION_LIMIT, f"递归深度超限: {error_str}"
174
+
175
+ if "CancelledError" in error_type:
176
+ return ErrorCode.RUNTIME_CANCELLED, "执行被取消"
177
+
178
+ if error_type == "UnboundLocalError":
179
+ return ErrorCode.CODE_NAME_NOT_DEFINED, f"局部变量未定义: {error_str}"
180
+
181
+ if error_type in ("ConnectionError", "ConnectTimeoutError", "NewConnectionError"):
182
+ return ErrorCode.API_NETWORK_CONNECTION, f"网络连接错误: {error_str}"
183
+
184
+ if error_type in ("TimeoutError", "ReadTimeoutError"):
185
+ return ErrorCode.API_NETWORK_TIMEOUT, f"网络超时: {error_str}"
186
+
187
+ if "RecursionError" in error_type or "GraphRecursionError" in error_type:
188
+ return ErrorCode.RUNTIME_RECURSION_LIMIT, f"递归深度超限: {error_str}"
189
+
190
+ if "InvalidUpdateError" in error_type:
191
+ return ErrorCode.CODE_TYPE_WRONG_ARG, f"节点返回类型错误: {error_str}"
192
+
193
+ if error_type == "JSONDecodeError":
194
+ return ErrorCode.VALIDATION_JSON_DECODE, f"JSON解析错误: {error_str}"
195
+
196
+ if error_type == "HTTPError":
197
+ return ErrorCode.API_NETWORK_HTTP_ERROR, f"HTTP请求错误: {error_str}"
198
+
199
+ if error_type == "OSError":
200
+ return _classify_io_error(error_str)
201
+
202
+ if "requests" in error_type.lower() or "MissingSchema" in error_type or "InvalidSchema" in error_type:
203
+ return _classify_requests_error(error_str)
204
+
205
+ if "subprocess" in error_type.lower() or "TimeoutExpired" in error_type:
206
+ return ErrorCode.RUNTIME_SUBPROCESS_TIMEOUT, f"子进程执行超时: {error_str}"
207
+
208
+ if "greenlet" in error_type.lower():
209
+ return ErrorCode.RUNTIME_THREAD_ERROR, f"线程切换错误: {error_str}"
210
+
211
+ if "cv2" in error_type.lower():
212
+ return ErrorCode.RESOURCE_IMAGE_PROCESS_FAILED, f"图像处理错误: {error_str}"
213
+
214
+ if "botocore" in error_type.lower() or "NoSuchBucket" in error_type:
215
+ return ErrorCode.RESOURCE_S3_UPLOAD_FAILED, f"S3存储错误: {error_str}"
216
+
217
+ if error_type == "Exception":
218
+ if "ValidationError" in error_str:
219
+ return _classify_validation_error(error_str)
220
+ if "APIError" in error_str:
221
+ return _classify_api_error(error_str)
222
+ if "InvalidUpdateError" in error_str:
223
+ return ErrorCode.BUSINESS_NODE_FAILED, f"节点返回值无效: {error_str[:200]}"
224
+ refined_code, refined_msg = _refine_fallback_error(error_type, error_str)
225
+ if refined_code is not None:
226
+ return refined_code, refined_msg # pyright: ignore [reportReturnType]
227
+ return _classify_custom_exception(error_str)
228
+
229
+ refined_code, refined_msg = _refine_fallback_error(error_type, error_str)
230
+ if refined_code is not None:
231
+ return refined_code, refined_msg # pyright: ignore [reportReturnType]
232
+
233
+ return ErrorCode.UNKNOWN_EXCEPTION, f"({error_type}): {error_str}"
234
+
235
+
236
+ def _classify_attribute_error(error_str: str) -> Tuple[int, str]:
237
+ """分类 AttributeError"""
238
+ error_lower = error_str.lower()
239
+
240
+ if "model_dump" in error_lower:
241
+ return ErrorCode.CODE_ATTR_MODEL_DUMP, f"对象类型错误,不是Pydantic模型: {error_str}"
242
+
243
+ if "did you mean" in error_lower:
244
+ return ErrorCode.CODE_ATTR_METHOD_NOT_FOUND, f"方法名错误: {error_str}"
245
+
246
+ if "'str' object" in error_str:
247
+ return ErrorCode.CODE_ATTR_WRONG_TYPE, f"字符串类型错误: {error_str}"
248
+
249
+ if "'nonetype' object" in error_lower:
250
+ return ErrorCode.CODE_ATTR_WRONG_TYPE, f"对象为None: {error_str}"
251
+
252
+ return ErrorCode.CODE_ATTR_NOT_FOUND, f"属性不存在: {error_str}"
253
+
254
+
255
+ def _classify_type_error(error_str: str) -> Tuple[int, str]:
256
+ """分类 TypeError"""
257
+ error_lower = error_str.lower()
258
+
259
+ if "missing" in error_lower and ("required" in error_lower or "argument" in error_lower):
260
+ return ErrorCode.CODE_TYPE_MISSING_ARG, f"缺少必需参数: {error_str}"
261
+
262
+ if "takes" in error_lower and ("positional argument" in error_lower or "got" in error_lower):
263
+ return ErrorCode.CODE_TYPE_EXTRA_ARG, f"参数数量错误: {error_str}"
264
+
265
+ if "not callable" in error_lower:
266
+ return ErrorCode.CODE_TYPE_NOT_CALLABLE, f"对象不可调用: {error_str}"
267
+
268
+ if "not iterable" in error_lower:
269
+ return ErrorCode.CODE_TYPE_NOT_ITERABLE, f"对象不可迭代: {error_str}"
270
+
271
+ if "not subscriptable" in error_lower:
272
+ return ErrorCode.CODE_TYPE_NOT_SUBSCRIPTABLE, f"对象不支持下标访问: {error_str}"
273
+
274
+ return ErrorCode.CODE_TYPE_WRONG_ARG, f"类型错误: {error_str}"
275
+
276
+
277
+ def _classify_validation_error(error_str: str) -> Tuple[int, str]:
278
+ """分类 Pydantic ValidationError"""
279
+ error_lower = error_str.lower()
280
+
281
+ if "field required" in error_lower or "missing" in error_lower:
282
+ field_match = re.search(r"for (\w+Input|\w+State)\s*\n(\w+)", error_str)
283
+ if field_match:
284
+ field_name = field_match.group(2)
285
+ return ErrorCode.VALIDATION_FIELD_REQUIRED, f"必填字段缺失: {field_name}"
286
+ return ErrorCode.VALIDATION_FIELD_REQUIRED, f"必填字段缺失: {error_str[:200]}"
287
+
288
+ if "type_error" in error_lower or "input should be" in error_lower:
289
+ return ErrorCode.VALIDATION_FIELD_TYPE, f"字段类型错误: {error_str[:200]}"
290
+
291
+ if "value_error" in error_lower or "value error" in error_lower:
292
+ if "日期" in error_str or "date" in error_lower:
293
+ return ErrorCode.VALIDATION_FIELD_FORMAT, f"日期格式错误: {error_str[:200]}"
294
+ return ErrorCode.VALIDATION_FIELD_VALUE, f"字段值错误: {error_str[:200]}"
295
+
296
+ return ErrorCode.VALIDATION_FIELD_CONSTRAINT, f"验证失败: {error_str[:200]}"
297
+
298
+
299
+ def _classify_value_error(error_str: str) -> Tuple[int, str]:
300
+ """分类 ValueError"""
301
+ error_lower = error_str.lower()
302
+
303
+ if "人脸" in error_str or "face" in error_lower:
304
+ return ErrorCode.RESOURCE_FACE_NOT_DETECTED, error_str
305
+
306
+ return ErrorCode.VALIDATION_FIELD_VALUE, f"值错误: {error_str}"
307
+
308
+
309
+ def _classify_runtime_error(error_str: str) -> Tuple[int, str]:
310
+ """分类 RuntimeError"""
311
+ error_lower = error_str.lower()
312
+
313
+ if "飞书" in error_str or "feishu" in error_lower:
314
+ return ErrorCode.INTEGRATION_FEISHU_API_FAILED, error_str
315
+
316
+ if "微信" in error_str or "wechat" in error_lower:
317
+ return ErrorCode.INTEGRATION_WECHAT_API_FAILED, error_str
318
+
319
+ return ErrorCode.RUNTIME_EXECUTION_FAILED, f"运行时错误: {error_str}"
320
+
321
+
322
+ def _classify_api_error(error_str: str) -> Tuple[int, str]:
323
+ """分类 API 相关错误"""
324
+ error_lower = error_str.lower()
325
+
326
+ if "资源点不足" in error_str or "errbalanceoverdue" in error_lower:
327
+ return ErrorCode.BUSINESS_QUOTA_INSUFFICIENT, f"资源点不足: {error_str[:200]}"
328
+
329
+ if "image format" in error_lower or "image_url" in error_lower:
330
+ return ErrorCode.API_LLM_IMAGE_FORMAT, f"图片格式不支持: {error_str[:200]}"
331
+
332
+ if "video" in error_lower:
333
+ if "404" in error_str:
334
+ return ErrorCode.API_VIDEO_GEN_NOT_FOUND, f"视频生成服务不可用: {error_str[:200]}"
335
+ return ErrorCode.API_VIDEO_GEN_FAILED, f"视频生成失败: {error_str[:200]}"
336
+
337
+ if "rate limit" in error_lower or "too many requests" in error_lower:
338
+ return ErrorCode.API_LLM_RATE_LIMIT, f"请求频率超限: {error_str[:200]}"
339
+
340
+ if "token" in error_lower and ("limit" in error_lower or "exceed" in error_lower):
341
+ return ErrorCode.API_LLM_TOKEN_LIMIT, f"Token超限: {error_str[:200]}"
342
+
343
+ if "auth" in error_lower or "unauthorized" in error_lower or "401" in error_str:
344
+ return ErrorCode.API_LLM_AUTH_FAILED, f"API认证失败: {error_str[:200]}"
345
+
346
+ if "invalid" in error_lower:
347
+ return ErrorCode.API_LLM_INVALID_REQUEST, f"API请求无效: {error_str[:200]}"
348
+
349
+ return ErrorCode.API_LLM_REQUEST_FAILED, f"API请求失败: {error_str[:200]}"
350
+
351
+
352
+ def _classify_io_error(error_str: str) -> Tuple[int, str]:
353
+ """分类 IO 错误"""
354
+ error_lower = error_str.lower()
355
+
356
+ if "no such file" in error_lower:
357
+ return ErrorCode.RESOURCE_FILE_NOT_FOUND, f"文件不存在: {error_str}"
358
+
359
+ if "permission denied" in error_lower:
360
+ return ErrorCode.RESOURCE_FILE_READ_ERROR, f"文件权限错误: {error_str}"
361
+
362
+ return ErrorCode.RESOURCE_FILE_READ_ERROR, f"文件读取错误: {error_str}"
363
+
364
+
365
+ def _classify_requests_error(error_str: str) -> Tuple[int, str]:
366
+ """分类 requests 库相关错误"""
367
+ error_lower = error_str.lower()
368
+
369
+ if "missingschema" in error_lower or "no scheme supplied" in error_lower:
370
+ return ErrorCode.API_NETWORK_URL_INVALID, f"URL格式无效,缺少协议头: {error_str[:200]}"
371
+
372
+ if "invalidschema" in error_lower or "no connection adapters" in error_lower:
373
+ return ErrorCode.API_NETWORK_URL_INVALID, f"URL格式无效: {error_str[:200]}"
374
+
375
+ if "connecttimeout" in error_lower or "connect timeout" in error_lower:
376
+ return ErrorCode.API_NETWORK_TIMEOUT, f"连接超时: {error_str[:200]}"
377
+
378
+ if "readtimeout" in error_lower or "read timeout" in error_lower:
379
+ return ErrorCode.API_NETWORK_TIMEOUT, f"读取超时: {error_str[:200]}"
380
+
381
+ if "connectionerror" in error_lower or "max retries exceeded" in error_lower:
382
+ return ErrorCode.API_NETWORK_CONNECTION, f"连接失败: {error_str[:200]}"
383
+
384
+ if "sslerror" in error_lower or "ssl" in error_lower and "error" in error_lower:
385
+ return ErrorCode.API_NETWORK_SSL_ERROR, f"SSL证书错误: {error_str[:200]}"
386
+
387
+ return ErrorCode.API_NETWORK_HTTP_ERROR, f"HTTP请求错误: {error_str[:200]}"
388
+
389
+
390
+ def _refine_fallback_error(error_type: str, error_str: str) -> Tuple[Optional[int], Optional[str]]:
391
+ """
392
+ 对兜底错误进行进一步细分
393
+
394
+ 使用统一的模式匹配表进行分类,替代原来冗长的 if-else 链。
395
+
396
+ Returns:
397
+ (error_code, error_message) 或 (None, None) 如果无法进一步分类
398
+ """
399
+ error_lower = error_str.lower()
400
+
401
+ if 'traceback' in error_lower:
402
+ code, msg = match_traceback_pattern(error_str)
403
+ if code is not None:
404
+ return code, msg
405
+
406
+ code, msg = match_error_pattern(error_str, ERROR_PATTERNS)
407
+ if code is not None:
408
+ return code, msg
409
+
410
+ return None, None
411
+
412
+
413
+ def _classify_custom_exception(error_str: str) -> Tuple[int, str]:
414
+ """
415
+ 分类自定义 Exception
416
+
417
+ 使用模式匹配表进行分类,如果没有匹配则返回通用业务错误。
418
+ """
419
+ code, msg = match_custom_exception_pattern(error_str)
420
+ if code is not None:
421
+ return code, msg # pyright: ignore [reportReturnType]
422
+
423
+ error_lower = error_str.lower()
424
+
425
+ if "资源点不足" in error_str or "errbalanceoverdue" in error_lower:
426
+ return ErrorCode.BUSINESS_QUOTA_INSUFFICIENT, f"资源点不足: {error_str[:200]}"
427
+
428
+ if "余额" in error_str and ("不足" in error_str or "insufficient" in error_lower):
429
+ return ErrorCode.BUSINESS_BALANCE_OVERDUE, f"余额不足: {error_str[:200]}"
430
+
431
+ if "配额" in error_str:
432
+ if "超" in error_str or "exceed" in error_lower:
433
+ return ErrorCode.BUSINESS_QUOTA_EXCEEDED, f"配额超限: {error_str[:200]}"
434
+ return ErrorCode.BUSINESS_QUOTA_INSUFFICIENT, f"配额不足: {error_str[:200]}"
435
+
436
+ if "失败" in error_str or "failed" in error_lower:
437
+ return ErrorCode.BUSINESS_NODE_FAILED, error_str[:200]
438
+
439
+ return ErrorCode.UNKNOWN_ERROR, error_str[:200]