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.
- coze_coding_utils/__init__.py +1 -1
- coze_coding_utils/error/__init__.py +31 -0
- coze_coding_utils/error/classifier.py +320 -0
- coze_coding_utils/error/codes.py +356 -0
- coze_coding_utils/error/exceptions.py +439 -0
- coze_coding_utils/error/patterns.py +939 -0
- coze_coding_utils/error/test_classifier.py +0 -0
- coze_coding_utils/file/__init__.py +0 -0
- coze_coding_utils/file/file.py +327 -0
- coze_coding_utils/helper/__init__.py +0 -0
- coze_coding_utils/helper/agent_helper.py +599 -0
- coze_coding_utils/helper/graph_helper.py +231 -0
- coze_coding_utils/log/__init__.py +0 -0
- coze_coding_utils/log/common.py +8 -0
- coze_coding_utils/log/config.py +10 -0
- coze_coding_utils/log/err_trace.py +88 -0
- coze_coding_utils/log/loop_trace.py +72 -0
- coze_coding_utils/log/node_log.py +487 -0
- coze_coding_utils/log/parser.py +255 -0
- coze_coding_utils/log/write_log.py +183 -0
- coze_coding_utils/messages/__init__.py +0 -0
- coze_coding_utils/messages/client.py +48 -0
- coze_coding_utils/messages/server.py +173 -0
- coze_coding_utils/openai/__init__.py +5 -0
- coze_coding_utils/openai/converter/__init__.py +6 -0
- coze_coding_utils/openai/converter/request_converter.py +165 -0
- coze_coding_utils/openai/converter/response_converter.py +467 -0
- coze_coding_utils/openai/handler.py +298 -0
- coze_coding_utils/openai/types/__init__.py +37 -0
- coze_coding_utils/openai/types/request.py +24 -0
- coze_coding_utils/openai/types/response.py +178 -0
- {coze_coding_utils-0.2.1.dist-info → coze_coding_utils-0.2.2a1.dist-info}/METADATA +2 -2
- coze_coding_utils-0.2.2a1.dist-info/RECORD +37 -0
- coze_coding_utils-0.2.1.dist-info/RECORD +0 -7
- {coze_coding_utils-0.2.1.dist-info → coze_coding_utils-0.2.2a1.dist-info}/WHEEL +0 -0
- {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,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
|
+
|