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,231 @@
1
+ import os
2
+ import inspect
3
+ import importlib
4
+ import ast
5
+ import textwrap
6
+ from pydantic import BaseModel
7
+ from typing import get_type_hints,Type,Optional,get_origin,Union,get_args
8
+ from langgraph.graph.state import CompiledStateGraph
9
+ from langgraph.graph import START, END
10
+
11
+
12
+ def get_graph_instance(module_name):
13
+ module = importlib.import_module(module_name)
14
+ for _, obj in inspect.getmembers(module):
15
+ if isinstance(obj, CompiledStateGraph):
16
+ return obj
17
+ return None
18
+
19
+ def get_agent_instance(module_name, ctx):
20
+ module = importlib.import_module(module_name)
21
+ return module.build_agent(ctx)
22
+
23
+ # return: func, input_class, output_class
24
+ def get_graph_node_func_with_inout(graph, node_name):
25
+ for node_id, node in graph.nodes.items():
26
+ if node_id == START or node_id == END:
27
+ continue
28
+
29
+ if node.data:
30
+ _func = node.data.func
31
+ if _func.__name__ != node_name:
32
+ continue
33
+
34
+ # 获取函数签名
35
+ sig = inspect.signature(_func)
36
+ # 获取参数列表
37
+ params = list(sig.parameters.values())
38
+ input_cls = None
39
+ if params:
40
+ input_cls = params[0].annotation
41
+
42
+ output_cls = ParamExtractHelper.get_concrete_return_class(_func)
43
+
44
+ return _func, input_cls, output_cls
45
+
46
+ return None, None, None
47
+
48
+ def is_agent_proj() -> bool:
49
+ return os.getenv("COZE_PROJECT_TYPE", "workflow") == "agent"
50
+
51
+ def is_dev_env() -> bool:
52
+ return os.getenv("COZE_PROJECT_ENV", "") == "DEV"
53
+
54
+
55
+ class ParamExtractHelper:
56
+ @classmethod
57
+ def get_concrete_return_class(cls, func) -> Optional[Type[BaseModel]]:
58
+ """
59
+ 获取函数的返回类型,提取顺序
60
+ 1. Type Hint
61
+ 2. ast源码解析,支持情况
62
+ 2.1 函数被装饰器包裹
63
+ 2.2 return xx(id=x)
64
+ 2.3 return pkg_name.xx(id=x)
65
+ 2.4 返回变量 return some_var, 只查找函数内的赋值语句
66
+ """
67
+
68
+ original_func = inspect.unwrap(func)
69
+
70
+ # 1. type hints
71
+ output_cls = cls._extract_model_from_hints(original_func)
72
+
73
+ # 2. 如果没有type hints,或者是泛型BaseModel, 通过AST解析
74
+ if output_cls is None or (output_cls is BaseModel):
75
+ print(f"Type hint insufficient for {original_func.__name__}, trying AST analysis...")
76
+ ast_cls = cls._extract_model_from_ast(original_func)
77
+ if ast_cls:
78
+ output_cls = ast_cls
79
+
80
+ # 3. 最终校验和输出
81
+ if output_cls and issubclass(output_cls, BaseModel):
82
+ return output_cls
83
+
84
+ return None
85
+
86
+ @classmethod
87
+ def _extract_model_from_hints(cls, func)->Optional[Type[BaseModel]]:
88
+ """从类型标注中提取 Pydantic 模型类"""
89
+ try:
90
+ hints = get_type_hints(func)
91
+ return_type= hints.get('return', None)
92
+ if return_type is None:
93
+ return None
94
+
95
+ # 处理 Optional[T] 或 Union[T, None] 的情况
96
+ origin = get_origin(return_type)
97
+ if origin is Union:
98
+ args = get_args(return_type)
99
+ # 过滤掉 NoneType
100
+ valid_args = [arg for arg in args if arg is not type(None)]
101
+ if len(valid_args) == 1:
102
+ return_type = valid_args[0]
103
+
104
+ # 检查是否是类且是 BaseModel 的子类
105
+ if isinstance(return_type, type) and issubclass(return_type, BaseModel):
106
+ return return_type
107
+ except Exception as e:
108
+ print(f"Error extracting hints: {e}")
109
+
110
+ return None
111
+
112
+ @classmethod
113
+ def _extract_model_from_ast(cls, func) -> Optional[Type[BaseModel]]:
114
+ """
115
+ 解析函数源码,寻找 'return SomeClass()' 语句,并从函数上下文中找到对应的类。
116
+ """
117
+ try:
118
+ # 获取源码并去缩进 (处理类方法或嵌套函数的情况)
119
+ src = textwrap.dedent(inspect.getsource(func))
120
+ tree = ast.parse(src)
121
+ func_def = tree.body[0]
122
+
123
+ # 遍历函数体寻找 return 语句
124
+ # 注意:这里只简单的找最后一个 return 或者所有 return,
125
+ # 复杂的逻辑流(多个不同 return)可能需要更复杂的处理
126
+ return_node = None
127
+ for node in ast.walk(func_def):
128
+ if isinstance(node, ast.Return):
129
+ return_node = node
130
+ break
131
+
132
+ if not return_node or not return_node.value:
133
+ return None
134
+
135
+ return cls._extract_model_from_ast_node(return_node.value, func)
136
+ except Exception as e:
137
+ print(f"Error extracting hints: {e}")
138
+ pass
139
+
140
+ return None
141
+
142
+ @classmethod
143
+ def _extract_model_from_ast_node(cls, node, func) -> Optional[Type[BaseModel]]:
144
+ # 检查 return 的是不是一个函数调用 (例如 SummaryOutput())
145
+ # 结构通常是: return SummaryOutput(id='SummaryOutput')
146
+ if isinstance(node, ast.Call):
147
+ if isinstance(node.func, ast.Name):
148
+ class_name = node.func.id
149
+ # 查找全局命名空间
150
+ globals_dict = func.__globals__
151
+ if class_name in globals_dict:
152
+ possible_cls = globals_dict[class_name]
153
+ if isinstance(possible_cls, type) and issubclass(possible_cls, BaseModel):
154
+ return possible_cls
155
+
156
+ # 查找闭包
157
+ if func.__closure__:
158
+ for cell in func.__closure__:
159
+ if hasattr(cell.cell_contents, '__name__') and cell.cell_contents.__name__ == class_name:
160
+ possible_cls = cell.cell_contents
161
+ if isinstance(possible_cls, type) and issubclass(possible_cls, BaseModel):
162
+ return possible_cls
163
+ elif isinstance(node.func, ast.Attribute):
164
+ # 处理 return module.ClassName() 的情况
165
+ return cls._extract_class_from_attribute(node.func, func)
166
+ elif isinstance(node, ast.Name):
167
+ # 情况2: 返回变量 return some_var
168
+ # 这里需要更复杂的分析来追踪变量的类型
169
+ # 作为简化,我们可以查找函数内的赋值语句
170
+ return cls._find_variable_type(node.id, func)
171
+
172
+ return None
173
+
174
+ @classmethod
175
+ def _extract_class_from_attribute(cls, node: ast.Attribute, func) -> Optional[Type[BaseModel]]:
176
+ """
177
+ 递归解析 AST 属性节点,从 globals 中找到对应的对象。
178
+ 处理如: module.submodule.ClassName
179
+ """
180
+ try:
181
+ global_ns = func.__globals__
182
+ # 1. 获取属性名 (例如 'SummaryOutput')
183
+ attr_name = node.attr
184
+
185
+ # 2. 获取前缀对象 (例如 'schemas' 或 'module.submodule')
186
+ value_node = node.value
187
+
188
+ prefix_obj = None
189
+
190
+ if isinstance(value_node, ast.Name):
191
+ # 基础情况:前缀是一个变量名 (例如 'schemas')
192
+ # 从 globals 中找到这个模块/对象
193
+ prefix_obj = global_ns.get(value_node.id)
194
+
195
+ elif isinstance(value_node, ast.Attribute):
196
+ # 递归情况:前缀依然是一个属性 (例如 'pkg.mod'.Class)
197
+ prefix_obj = cls._extract_class_from_attribute(value_node, global_ns)
198
+
199
+ if prefix_obj:
200
+ # 从模块/对象中获取类
201
+ target_cls = getattr(prefix_obj, attr_name, None)
202
+ if isinstance(target_cls, type) and issubclass(target_cls, BaseModel):
203
+ return target_cls
204
+
205
+ except Exception:
206
+ pass
207
+ return None
208
+
209
+
210
+ @classmethod
211
+ def _find_variable_type(cls, var_name: str, func) -> Optional[Type[BaseModel]]:
212
+ """查找变量的类型(简化版本)"""
213
+ # 这是一个复杂的问题,需要完整的控制流分析
214
+ # 这里提供一个简化的实现,只查找直接赋值
215
+ try:
216
+ source = inspect.getsource(func)
217
+ tree = ast.parse(source)
218
+
219
+ for node in ast.walk(tree):
220
+ if isinstance(node, ast.FunctionDef) and node.name == func.__name__:
221
+ # 查找赋值语句
222
+ for stmt in node.body:
223
+ if (isinstance(stmt, ast.Assign) and
224
+ any(isinstance(target, ast.Name) and target.id == var_name
225
+ for target in stmt.targets)):
226
+ # 找到对目标变量的赋值
227
+ if isinstance(stmt.value, ast.Call):
228
+ return cls._extract_model_from_ast_node(stmt.value, func)
229
+ except:
230
+ pass
231
+ return None
File without changes
@@ -0,0 +1,8 @@
1
+ import os
2
+
3
+
4
+ def is_prod() -> bool:
5
+ return os.getenv("COZE_PROJECT_ENV") == "PROD"
6
+
7
+ def get_execute_mode() -> str:
8
+ return "test_run" if not is_prod() else "run"
@@ -0,0 +1,10 @@
1
+ """
2
+ Application configuration
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+
7
+ # Logging
8
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
9
+
10
+ LOG_DIR = Path(os.getenv("COZE_LOG_DIR", "/tmp/app/work/logs/bypass"))
@@ -0,0 +1,88 @@
1
+ import os
2
+ import sys
3
+ import sysconfig
4
+ import traceback
5
+
6
+ def extract_core_stack(lines_num: int = 5) -> list:
7
+ exc_type, exc_value, exc_tb = sys.exc_info()
8
+ if exc_tb is None:
9
+ return ["当前没有异常上下文"]
10
+
11
+ frames = traceback.extract_tb(exc_tb)
12
+
13
+ stdlib_path = sysconfig.get_paths().get("stdlib")
14
+ py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
15
+ default_stdlib_hint = os.path.join(sys.base_prefix, "lib", f"python{py_ver}")
16
+
17
+ noise_substrings = [
18
+ os.sep + "site-packages" + os.sep,
19
+ os.sep + "dist-packages" + os.sep,
20
+ os.sep + "importlib" + os.sep,
21
+ os.sep + "logging" + os.sep,
22
+ os.sep + "asyncio" + os.sep,
23
+ os.sep + "concurrent" + os.sep + "futures",
24
+ os.sep + "multiprocessing" + os.sep,
25
+ os.sep + "pkgutil.py",
26
+ os.sep + "runpy.py",
27
+ os.sep + "selectors.py",
28
+ os.sep + "threading.py",
29
+ os.sep + "contextlib.py",
30
+ os.sep + "utils" + os.sep + "log" + os.sep + "err_trace.py",
31
+ ]
32
+
33
+ def is_noise(filename: str, name: str) -> bool:
34
+ fn = os.path.normpath(filename)
35
+ if stdlib_path and fn.startswith(os.path.normpath(stdlib_path)):
36
+ return True
37
+ if default_stdlib_hint and fn.startswith(os.path.normpath(default_stdlib_hint)):
38
+ return True
39
+ for s in noise_substrings:
40
+ if s in fn:
41
+ return True
42
+ if name in {"__call__", "<module>", "wrapper"}:
43
+ return True
44
+ return False
45
+
46
+ def short_path(path: str, max_parts: int = 3) -> str:
47
+ try:
48
+ rel = os.path.relpath(path, os.getcwd())
49
+ except Exception:
50
+ rel = path
51
+ parts = rel.split(os.sep)
52
+ if len(parts) > max_parts:
53
+ return os.sep.join(parts[-max_parts:])
54
+ return rel
55
+
56
+ filtered = []
57
+ for fr in frames:
58
+ name = getattr(fr, "name", "") or ""
59
+ if not is_noise(getattr(fr, "filename", ""), name):
60
+ filtered.append(fr)
61
+
62
+ if not filtered:
63
+ filtered = frames
64
+
65
+ if lines_num and lines_num > 0:
66
+ filtered = filtered[-lines_num:]
67
+
68
+ lines_out = ["Traceback (most recent call last):"]
69
+ for fr in filtered:
70
+ name = getattr(fr, "name", "") or ""
71
+ base = f'File "{short_path(fr.filename)}", line {fr.lineno}'
72
+ if name.strip():
73
+ base += f", in {name}"
74
+ lines_out.append(base)
75
+ if getattr(fr, "line", None):
76
+ lines_out.append(f" {fr.line.strip()}")
77
+
78
+ if exc_type:
79
+ try:
80
+ exc_only = traceback.format_exception_only(exc_type, exc_value)
81
+ for ln in exc_only:
82
+ lines_out.append(ln.rstrip("\n"))
83
+ except Exception:
84
+ exc_name = getattr(exc_type, "__name__", str(exc_type))
85
+ exc_msg = str(exc_value) if exc_value is not None else ""
86
+ lines_out.append(f"{exc_name}: {exc_msg}".rstrip())
87
+
88
+ return lines_out
@@ -0,0 +1,72 @@
1
+ import os
2
+ import cozeloop
3
+ from cozeloop.integration.langchain.trace_callback import LoopTracer
4
+ from langchain_core.runnables import RunnableConfig
5
+ from coze_coding_utils.log.common import get_execute_mode
6
+ from coze_coding_utils.log.node_log import Logger
7
+
8
+ space_id = os.getenv("COZE_PROJECT_SPACE_ID", "YOUR_SPACE_ID")
9
+ api_token = os.getenv("COZE_LOOP_API_TOKEN", "YOUR_LOOP_API_TOKEN")
10
+ base_url = os.getenv("COZE_LOOP_BASE_URL", "https://api.coze.cn")
11
+ commit_hash = os.getenv("COZE_PROJECT_COMMIT_HASH","") # 发布版本的hash值
12
+
13
+ cozeloopTracer = cozeloop.new_client(
14
+ workspace_id=space_id,
15
+ api_token=api_token,
16
+ api_base_url=base_url,
17
+ )
18
+ cozeloop.set_default_client(cozeloopTracer)
19
+
20
+
21
+ def init_run_config(graph, ctx):
22
+ tracer = Logger(graph, ctx)
23
+ tracer.on_chain_start = tracer.on_chain_start_graph # 非必须
24
+ tracer.on_chain_end = tracer.on_chain_end_graph
25
+ trace_callback_handler = LoopTracer.get_callback_handler(
26
+ cozeloopTracer,
27
+ add_tags_fn=tracer.get_node_tags,
28
+ modify_name_fn=tracer.get_node_name,
29
+ tags={
30
+ "project_id": ctx.project_id,
31
+ "execute_mode": get_execute_mode(),
32
+ "log_id": ctx.logid,
33
+ "commit_hash": commit_hash,
34
+ }
35
+ )
36
+ config = RunnableConfig(
37
+ callbacks=[
38
+ tracer,
39
+ trace_callback_handler
40
+ ],
41
+ )
42
+ return config
43
+
44
+
45
+ def init_agent_config(graph, ctx):
46
+ config = RunnableConfig(
47
+ callbacks=[
48
+ LoopTracer.get_callback_handler(
49
+ cozeloopTracer,
50
+ tags={
51
+ "project_id": ctx.project_id,
52
+ "execute_mode": get_execute_mode(),
53
+ "log_id": ctx.logid,
54
+ "commit_hash": commit_hash,
55
+ }
56
+ )
57
+ ]
58
+ )
59
+ print("config", config)
60
+ return config
61
+
62
+
63
+ # 保留add_trace_tags函数,作为对trace.set_tags的简单包装
64
+ def add_trace_tags(trace, tags):
65
+ """
66
+ 为trace添加标签
67
+ :param trace: trace对象
68
+ :param tags: 标签字典
69
+ """
70
+ # 使用set_tags方法
71
+ trace.set_tags(tags)
72
+