sycommon-python-lib 0.1.56b3__py3-none-any.whl → 0.1.56b4__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.
sycommon/llm/get_llm.py CHANGED
@@ -1,48 +1,137 @@
1
- from typing import Dict, Type, List, Union, Optional, Callable
2
-
1
+ from typing import Dict, Type, List, Optional, Callable, Any
3
2
  from sycommon.llm.llm_logger import LLMLogger
4
3
  from langchain_core.language_models import BaseChatModel
5
- from langchain_core.runnables import Runnable, RunnableLambda
4
+ from langchain_core.runnables import Runnable, RunnableLambda, RunnableConfig
6
5
  from langchain_core.output_parsers import PydanticOutputParser
7
- from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
6
+ from langchain_core.messages import BaseMessage, HumanMessage
8
7
  from langchain.chat_models import init_chat_model
9
8
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
10
- from pydantic import BaseModel, ValidationError
9
+ from pydantic import BaseModel, ValidationError, Field
11
10
  from sycommon.config.LLMConfig import LLMConfig
11
+ from sycommon.llm.llm_tokens import TokensCallbackHandler
12
+ from sycommon.logging.kafka_log import SYLogger
13
+
14
+
15
+ class StructuredRunnableWithToken(Runnable):
16
+ """带Token统计的Runnable类"""
17
+
18
+ def __init__(self, retry_chain: Runnable):
19
+ super().__init__()
20
+ self.retry_chain = retry_chain
21
+
22
+ def _adapt_input(self, input: Any) -> List[BaseMessage]:
23
+ """适配输入格式"""
24
+ if isinstance(input, list) and all(isinstance(x, BaseMessage) for x in input):
25
+ return input
26
+ elif isinstance(input, BaseMessage):
27
+ return [input]
28
+ elif isinstance(input, str):
29
+ return [HumanMessage(content=input)]
30
+ elif isinstance(input, dict) and "input" in input:
31
+ return [HumanMessage(content=str(input["input"]))]
32
+ else:
33
+ raise ValueError(f"不支持的输入格式:{type(input)}")
12
34
 
35
+ def _get_callback_config(self, config: Optional[RunnableConfig] = None) -> tuple[RunnableConfig, TokensCallbackHandler]:
36
+ """构建包含Token统计的回调配置"""
37
+ # 每次调用创建新的Token处理器实例
38
+ token_handler = TokensCallbackHandler()
13
39
 
14
- def get_llm(model: str = None, streaming: bool = False) -> BaseChatModel:
15
- if not model:
16
- model = "Qwen2.5-72B"
17
- # model = "SyMid"
18
- llmConfig = LLMConfig.from_config(model)
19
- llm = None
20
- if llmConfig:
21
- llm = init_chat_model(
22
- model_provider=llmConfig.provider,
23
- model=llmConfig.model,
24
- base_url=llmConfig.baseUrl,
25
- api_key="-",
26
- temperature=0.1,
27
- streaming=streaming,
28
- )
29
- else:
30
- raise Exception("Invalid model")
40
+ # 初始化配置
41
+ if config is None:
42
+ processed_config = {"callbacks": []}
43
+ else:
44
+ processed_config = config.copy()
45
+ if "callbacks" not in processed_config:
46
+ processed_config["callbacks"] = []
47
+
48
+ # 添加回调(去重)
49
+ callbacks = processed_config["callbacks"]
50
+ # 添加LLMLogger(如果不存在)
51
+ if not any(isinstance(cb, LLMLogger) for cb in callbacks):
52
+ callbacks.append(LLMLogger())
53
+ # 添加Token处理器
54
+ callbacks.append(token_handler)
55
+
56
+ # 按类型去重
57
+ callback_types = {}
58
+ unique_callbacks = []
59
+ for cb in callbacks:
60
+ cb_type = type(cb)
61
+ if cb_type not in callback_types:
62
+ callback_types[cb_type] = cb
63
+ unique_callbacks.append(cb)
64
+
65
+ processed_config["callbacks"] = unique_callbacks
66
+
67
+ return processed_config, token_handler
68
+
69
+ # 同步调用
70
+ def invoke(self, input: Any, config: Optional[RunnableConfig] = None, ** kwargs) -> Dict[str, Any]:
71
+ try:
72
+ processed_config, token_handler = self._get_callback_config(
73
+ config)
74
+ adapted_input = self._adapt_input(input)
75
+
76
+ structured_result = self.retry_chain.invoke(
77
+ {"messages": adapted_input},
78
+ config=processed_config,
79
+ **kwargs
80
+ )
81
+
82
+ # 获取Token统计结果
83
+ token_usage = token_handler.usage_metadata
84
+ structured_result._token_usage_ = token_usage
85
+
86
+ return structured_result
87
+
88
+ except Exception as e:
89
+ SYLogger.error(f"同步LLM调用失败: {str(e)}", exc_info=True)
90
+ return None
91
+
92
+ # 异步调用
93
+ async def ainvoke(self, input: Any, config: Optional[RunnableConfig] = None, ** kwargs) -> Dict[str, Any]:
94
+ try:
95
+ processed_config, token_handler = self._get_callback_config(
96
+ config)
97
+ adapted_input = self._adapt_input(input)
98
+
99
+ structured_result = await self.retry_chain.ainvoke(
100
+ {"messages": adapted_input},
101
+ config=processed_config,
102
+ **kwargs
103
+ )
104
+
105
+ token_usage = token_handler.usage_metadata
106
+ structured_result._token_usage_ = token_usage
107
+
108
+ return structured_result
109
+
110
+ except Exception as e:
111
+ SYLogger.error(f"异步LLM调用失败: {str(e)}", exc_info=True)
112
+ return None
113
+
114
+
115
+ class LLMWithAutoTokenUsage(BaseChatModel):
116
+ """自动为结构化调用返回token_usage的LLM包装类"""
117
+ llm: BaseChatModel = Field(default=None)
118
+
119
+ def __init__(self, llm: BaseChatModel, **kwargs):
120
+ super().__init__(llm=llm, ** kwargs)
31
121
 
32
- # 为LLM动态添加with_structured_output方法,官方的with_structured_output方法有概率在qwen2.5中导致模型卡死不返回数据,2.5对functioncall支持不好
33
122
  def with_structured_output(
34
- self: BaseChatModel,
123
+ self,
35
124
  output_model: Type[BaseModel],
36
125
  max_retries: int = 3,
37
126
  is_extract: bool = False,
38
127
  override_prompt: ChatPromptTemplate = None,
39
- # 自定义处理函数列表(每个函数接收str,返回str)
40
128
  custom_processors: Optional[List[Callable[[str], str]]] = None,
41
- # 自定义解析函数(接收str,返回BaseModel)
42
129
  custom_parser: Optional[Callable[[str], BaseModel]] = None
43
- ) -> Runnable[List[BaseMessage], BaseModel]:
130
+ ) -> Runnable:
131
+ """返回支持自动统计Token的结构化Runnable"""
44
132
  parser = PydanticOutputParser(pydantic_object=output_model)
45
133
 
134
+ # 提示词模板
46
135
  accuracy_instructions = """
47
136
  字段值的抽取准确率(0~1之间),评分规则:
48
137
  1.0(完全准确):直接从原文提取,无需任何加工,且格式与原文完全一致
@@ -52,7 +141,6 @@ def get_llm(model: str = None, streaming: bool = False) -> BaseChatModel:
52
141
  """
53
142
 
54
143
  if is_extract:
55
- # 抽取模式下使用固定的抽取专用prompt
56
144
  prompt = ChatPromptTemplate.from_messages([
57
145
  MessagesPlaceholder(variable_name="messages"),
58
146
  HumanMessage(content=f"""
@@ -62,116 +150,97 @@ def get_llm(model: str = None, streaming: bool = False) -> BaseChatModel:
62
150
  """)
63
151
  ])
64
152
  else:
65
- if override_prompt:
66
- prompt = override_prompt
67
- else:
68
- prompt = ChatPromptTemplate.from_messages([
69
- MessagesPlaceholder(variable_name="messages"),
70
- HumanMessage(content=f"""
71
- 输出格式:{parser.get_format_instructions()}
72
- """)
73
- ])
74
-
75
- # ========== 基础处理函数 ==========
153
+ prompt = override_prompt or ChatPromptTemplate.from_messages([
154
+ MessagesPlaceholder(variable_name="messages"),
155
+ HumanMessage(content=f"""
156
+ 输出格式:{parser.get_format_instructions()}
157
+ """)
158
+ ])
159
+
160
+ # 文本处理函数
76
161
  def extract_response_content(response: BaseMessage) -> str:
77
- """提取响应中的文本内容"""
78
162
  try:
79
163
  return response.content
80
164
  except Exception as e:
81
165
  raise ValueError(f"提取响应内容失败:{str(e)}") from e
82
166
 
83
167
  def strip_code_block_markers(content: str) -> str:
84
- """移除JSON代码块标记(```json/```)"""
85
168
  try:
86
169
  return content.strip("```json").strip("```").strip()
87
170
  except Exception as e:
88
- raise ValueError(
89
- f"移除代码块标记失败(内容:{str(content)[:100]}):{str(e)}") from e
171
+ raise ValueError(f"移除代码块标记失败:{str(e)}") from e
90
172
 
91
173
  def normalize_in_json(content: str) -> str:
92
- """将None替换为null,确保JSON格式合法"""
93
174
  try:
94
- cleaned = content.replace("None", "null")
95
- cleaned = cleaned.replace("none", "null")
96
- cleaned = cleaned.replace("NONE", "null")
97
- cleaned = cleaned.replace("''", '""')
98
- return cleaned
175
+ return content.replace("None", "null").replace("none", "null").replace("NONE", "null").replace("''", '""')
99
176
  except Exception as e:
100
- raise ValueError(
101
- f"替换None为null失败(内容:{str(content)[:100]}):{str(e)}") from e
177
+ raise ValueError(f"JSON格式化失败:{str(e)}") from e
102
178
 
103
179
  def default_parse_to_pydantic(content: str) -> BaseModel:
104
- """默认解析函数:将处理后的文本解析为Pydantic模型"""
105
180
  try:
106
181
  return parser.parse(content)
107
182
  except (ValidationError, ValueError) as e:
108
- raise type(e)(f"解析失败(原始内容:{content[:200]}):{str(e)}") from e
109
-
110
- # ========== 构建处理链条 ==========
111
- # 基础链 prompt → LLM → 提取响应内容
112
- base_chain = (
113
- prompt
114
- | self
115
- | RunnableLambda(extract_response_content)
116
- )
183
+ raise ValueError(f"解析结构化结果失败:{str(e)}") from e
117
184
 
118
- # 处理函数链 优先使用自定义,否则用默认
119
- if custom_processors:
120
- # 自定义处理函数 → 转为RunnableLambda列表
121
- process_runnables = [RunnableLambda(
122
- func) for func in custom_processors]
123
- else:
124
- # 默认处理函数:移除代码块标记 → 标准化JSON空值
125
- process_runnables = [
126
- RunnableLambda(strip_code_block_markers),
127
- RunnableLambda(normalize_in_json)
128
- ]
185
+ # ========== 构建处理链 ==========
186
+ base_chain = prompt | self.llm | RunnableLambda(
187
+ extract_response_content)
129
188
 
130
- # 拼接处理链
189
+ # 文本处理链
190
+ process_runnables = custom_processors or [
191
+ RunnableLambda(strip_code_block_markers),
192
+ RunnableLambda(normalize_in_json)
193
+ ]
131
194
  process_chain = base_chain
132
195
  for runnable in process_runnables:
133
196
  process_chain = process_chain | runnable
134
197
 
135
- # 解析函数 优先使用自定义,否则用默认
136
- parse_func = custom_parser if custom_parser else default_parse_to_pydantic
137
- parse_chain = process_chain | RunnableLambda(parse_func)
198
+ # 解析链
199
+ parse_chain = process_chain | RunnableLambda(
200
+ custom_parser or default_parse_to_pydantic)
138
201
 
202
+ # 重试链
139
203
  retry_chain = parse_chain.with_retry(
140
204
  retry_if_exception_type=(ValidationError, ValueError),
141
205
  stop_after_attempt=max_retries,
142
206
  wait_exponential_jitter=True,
143
207
  exponential_jitter_params={
144
- "initial": 0.1, # 初始等待时间(秒)
145
- "max": 3.0, # 最大等待时间(秒)
146
- "exp_base": 2.0, # 指数基数(默认2)
147
- "jitter": 1.0 # 随机抖动值(默认1)
148
- }
208
+ "initial": 0.1, "max": 3.0, "exp_base": 2.0, "jitter": 1.0}
149
209
  )
150
210
 
151
- class StructuredRunnable(Runnable[Union[List[BaseMessage], BaseMessage, str, Dict[str, str]], BaseModel]):
152
- def _adapt_input(self, input: Union[List[BaseMessage], BaseMessage, str, Dict[str, str]]) -> List[BaseMessage]:
153
- """将多种输入格式统一转换为 List[BaseMessage]"""
154
- if isinstance(input, list) and all(isinstance(x, BaseMessage) for x in input):
155
- return input
156
- elif isinstance(input, BaseMessage):
157
- return [input]
158
- elif isinstance(input, str):
159
- return [HumanMessage(content=input)]
160
- elif isinstance(input, dict) and "input" in input:
161
- return [HumanMessage(content=str(input["input"]))]
162
- else:
163
- raise ValueError(
164
- "不支持的输入格式,请使用消息列表、单条消息、文本或 {'input': '文本'}")
165
-
166
- def invoke(self, input: Union[List[BaseMessage], BaseMessage, str, Dict[str, str]], config={"callbacks": [LLMLogger()]}):
167
- adapted_input = self._adapt_input(input)
168
- return retry_chain.invoke({"messages": adapted_input}, config=config)
169
-
170
- async def ainvoke(self, input: Union[List[BaseMessage], BaseMessage, str, Dict[str, str]], config={"callbacks": [LLMLogger()]}):
171
- adapted_input = self._adapt_input(input)
172
- return await retry_chain.ainvoke({"messages": adapted_input}, config=config)
173
-
174
- return StructuredRunnable()
175
-
176
- llm.__class__.with_structured_output = with_structured_output
177
- return llm
211
+ return StructuredRunnableWithToken(retry_chain)
212
+
213
+ # ========== 实现BaseChatModel抽象方法 ==========
214
+ def _generate(self, messages, stop=None, run_manager=None, ** kwargs):
215
+ return self.llm._generate(messages, stop=stop, run_manager=run_manager, ** kwargs)
216
+
217
+ @property
218
+ def _llm_type(self) -> str:
219
+ return self.llm._llm_type
220
+
221
+
222
+ def get_llm(
223
+ model: str = None,
224
+ streaming: bool = False
225
+ ) -> LLMWithAutoTokenUsage:
226
+ if not model:
227
+ model = "Qwen2.5-72B"
228
+
229
+ llmConfig = LLMConfig.from_config(model)
230
+ if not llmConfig:
231
+ raise Exception(f"无效的模型配置:{model}")
232
+
233
+ llm = init_chat_model(
234
+ model_provider=llmConfig.provider,
235
+ model=llmConfig.model,
236
+ base_url=llmConfig.baseUrl,
237
+ api_key="-",
238
+ temperature=0.1,
239
+ streaming=streaming,
240
+ callbacks=[LLMLogger()]
241
+ )
242
+
243
+ if llm is None:
244
+ raise Exception(f"初始化原始LLM实例失败:{model}")
245
+
246
+ return LLMWithAutoTokenUsage(llm)
@@ -0,0 +1,119 @@
1
+ from typing import Any
2
+ from langchain_core.callbacks import AsyncCallbackHandler
3
+ from langchain_core.outputs.llm_result import LLMResult
4
+ from sycommon.logging.kafka_log import SYLogger
5
+
6
+
7
+ class TokensCallbackHandler(AsyncCallbackHandler):
8
+ """
9
+ 继承AsyncCallbackHandler的Token统计处理器
10
+ """
11
+
12
+ def __init__(self):
13
+ super().__init__()
14
+ self.input_tokens = 0
15
+ self.output_tokens = 0
16
+ self.total_tokens = 0
17
+ self.usage_metadata = {}
18
+ self.reset()
19
+
20
+ def reset(self):
21
+ """重置Token统计数据"""
22
+ self.input_tokens = 0
23
+ self.output_tokens = 0
24
+ self.total_tokens = 0
25
+ self.usage_metadata = {
26
+ "input_tokens": 0,
27
+ "output_tokens": 0,
28
+ "total_tokens": 0
29
+ }
30
+
31
+ # ========== 同步回调方法(兼容签名) ==========
32
+ def on_llm_end(
33
+ self,
34
+ response: LLMResult,
35
+ **kwargs: Any,
36
+ ) -> None:
37
+ """同步LLM调用结束时的回调"""
38
+ self._parse_token_usage(response)
39
+
40
+ # ========== 异步回调方法(兼容签名) ==========
41
+ async def on_llm_end(
42
+ self,
43
+ response: LLMResult,
44
+ **kwargs: Any,
45
+ ) -> None:
46
+ """异步LLM调用结束时的回调"""
47
+ self._parse_token_usage(response)
48
+
49
+ def _parse_token_usage(self, response: LLMResult) -> None:
50
+ """
51
+ 通用Token解析逻辑,不依赖特定类结构
52
+ 兼容各种LLM响应格式
53
+ """
54
+ try:
55
+ # 情况1: 标准LangChain响应(有llm_output属性)
56
+ if response.llm_output:
57
+ llm_output = response.llm_output
58
+ self._parse_from_llm_output(llm_output)
59
+
60
+ # 情况2: 包含generations的响应
61
+ elif response.generations:
62
+ self._parse_from_generations(response.generations)
63
+
64
+ # 计算总Token
65
+ if self.total_tokens <= 0:
66
+ self.total_tokens = self.input_tokens + self.output_tokens
67
+
68
+ # 更新metadata
69
+ self.usage_metadata = {
70
+ "input_tokens": self.input_tokens,
71
+ "output_tokens": self.output_tokens,
72
+ "total_tokens": self.total_tokens
73
+ }
74
+
75
+ SYLogger.debug(
76
+ f"Token统计成功 - 输入: {self.input_tokens}, 输出: {self.output_tokens}")
77
+
78
+ except Exception as e:
79
+ SYLogger.warning(f"Token解析失败: {str(e)}", exc_info=True)
80
+ self.reset()
81
+
82
+ def _parse_from_llm_output(self, llm_output: dict) -> None:
83
+ """从llm_output字典解析Token信息"""
84
+ if not isinstance(llm_output, dict):
85
+ return
86
+
87
+ # OpenAI标准格式
88
+ if 'token_usage' in llm_output:
89
+ token_usage = llm_output['token_usage']
90
+ self.input_tokens = token_usage.get(
91
+ 'prompt_tokens', token_usage.get('input_tokens', 0))
92
+ self.output_tokens = token_usage.get(
93
+ 'completion_tokens', token_usage.get('output_tokens', 0))
94
+ self.total_tokens = token_usage.get('total_tokens', 0)
95
+
96
+ # 直接包含Token信息
97
+ else:
98
+ self.input_tokens = llm_output.get(
99
+ 'input_tokens', llm_output.get('prompt_tokens', 0))
100
+ self.output_tokens = llm_output.get(
101
+ 'output_tokens', llm_output.get('completion_tokens', 0))
102
+ self.total_tokens = token_usage.get('total_tokens', 0)
103
+
104
+ def _parse_from_generations(self, generations: list) -> None:
105
+ """从generations列表解析Token信息"""
106
+ if not isinstance(generations, list) or len(generations) == 0:
107
+ return
108
+
109
+ # 遍历generation信息
110
+ for gen_group in generations:
111
+ for generation in gen_group:
112
+ if hasattr(generation, 'generation_info') and generation.generation_info:
113
+ gen_info = generation.generation_info
114
+ self.input_tokens = gen_info.get(
115
+ 'input_tokens', gen_info.get('prompt_tokens', 0))
116
+ self.output_tokens = gen_info.get(
117
+ 'output_tokens', gen_info.get('completion_tokens', 0))
118
+ self.total_tokens = gen_info.get('total_tokens', 0)
119
+ return
@@ -817,6 +817,11 @@ class NacosService(metaclass=SingletonMeta):
817
817
  return []
818
818
 
819
819
  all_instances = instances.get('hosts', [])
820
+ # 筛选已上线实例
821
+ all_instances = [
822
+ instance for instance in all_instances
823
+ if instance.get('enabled', True) # 默认True担心阿里变更sdk
824
+ ]
820
825
  SYLogger.info(
821
826
  f"nacos:共发现 {len(all_instances)} 个 {service_name} 服务实例")
822
827
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.56b3
3
+ Version: 0.1.56b4
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -18,8 +18,9 @@ sycommon/health/metrics.py,sha256=fHqO73JuhoZkNPR-xIlxieXiTCvttq-kG-tvxag1s1s,26
18
18
  sycommon/health/ping.py,sha256=FTlnIKk5y1mPfS1ZGOeT5IM_2udF5aqVLubEtuBp18M,250
19
19
  sycommon/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  sycommon/llm/embedding.py,sha256=Wcm2W7JU3FyZXvOhMSdyhiZJhJS1MwW8bMqdrOzD2TY,5768
21
- sycommon/llm/get_llm.py,sha256=QnmNwah2wzO2P1o37lNTrpzg7ZYvaDHjIzgOD9n3Ais,8325
21
+ sycommon/llm/get_llm.py,sha256=wawJO_WSLSYPE8ImL421SYBPtAvWqwbRAcUN7d5i0W0,9434
22
22
  sycommon/llm/llm_logger.py,sha256=n4UeNy_-g4oHQOsw-VUzF4uo3JVRLtxaMp1FcI8FiEo,5437
23
+ sycommon/llm/llm_tokens.py,sha256=-udDyFcmyzx6UAwIi6_d_wwI5kMd5w0-WcS2soVPQxg,4309
23
24
  sycommon/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
25
  sycommon/logging/async_sql_logger.py,sha256=_OY36XkUm__U3NhMgiecy-qd-nptZ_0gpE3J8lGAr58,2619
25
26
  sycommon/logging/kafka_log.py,sha256=sVw-dFZKEgCosjSUqgTj7YrpK-ggXhleZFwMUVhl-K0,21416
@@ -56,15 +57,15 @@ sycommon/synacos/example.py,sha256=61XL03tU8WTNOo3FUduf93F2fAwah1S0lbH1ufhRhRk,5
56
57
  sycommon/synacos/example2.py,sha256=adUaru3Hy482KrOA17DfaC4nwvLj8etIDS_KrWLWmCU,4811
57
58
  sycommon/synacos/feign.py,sha256=frB3D5LeFDtT3pJLFOwFzEOrNAJKeQNGk-BzUg9T3WM,8295
58
59
  sycommon/synacos/feign_client.py,sha256=ExO7Pd5B3eFKDjXqBRc260K1jkI49IYguLwJJaD2R-o,16166
59
- sycommon/synacos/nacos_service.py,sha256=QPdCRFPXWUUoHL72VCjBAImdIo175q2plDO1T0xrPAg,35961
60
+ sycommon/synacos/nacos_service.py,sha256=9cW2tSjm0vuYFRMPR_cl_EOZPe4axQDCUBcVxoGdPKM,36180
60
61
  sycommon/synacos/param.py,sha256=KcfSkxnXOa0TGmCjY8hdzU9pzUsA8-4PeyBKWI2-568,1765
61
62
  sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
63
  sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
63
64
  sycommon/tools/merge_headers.py,sha256=HV_i52Q-9se3SP8qh7ZGYl8bP7Fxtal4CGVkyMwEdM8,4373
64
65
  sycommon/tools/snowflake.py,sha256=lVEe5mNCOgz5OqGQpf5_nXaGnRJlI2STX2s-ppTtanA,11947
65
66
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
66
- sycommon_python_lib-0.1.56b3.dist-info/METADATA,sha256=J7AWcuKaJxgpLHHVQs9qYPokB_-oge0eOD6q8tGIrXo,7226
67
- sycommon_python_lib-0.1.56b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
- sycommon_python_lib-0.1.56b3.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
69
- sycommon_python_lib-0.1.56b3.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
70
- sycommon_python_lib-0.1.56b3.dist-info/RECORD,,
67
+ sycommon_python_lib-0.1.56b4.dist-info/METADATA,sha256=c2XIy1w2EUZHGBPqJQvPTb6eYH2i5jTVNmnV-cPD-hs,7226
68
+ sycommon_python_lib-0.1.56b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
+ sycommon_python_lib-0.1.56b4.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
70
+ sycommon_python_lib-0.1.56b4.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
71
+ sycommon_python_lib-0.1.56b4.dist-info/RECORD,,