sycommon-python-lib 0.1.57b3__py3-none-any.whl → 0.1.57b5__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/embedding.py CHANGED
@@ -30,18 +30,12 @@ class Embedding(metaclass=SingletonMeta):
30
30
  self.default_reranker_model: self.reranker_base_url
31
31
  }
32
32
 
33
- # [修复] 缓存模型的向量维度,用于生成兜底零向量
34
- self._model_dim_cache: Dict[str, int] = {}
35
-
36
33
  # 并发信号量
37
34
  self.semaphore = asyncio.Semaphore(self.max_concurrency)
38
35
  self.default_timeout = aiohttp.ClientTimeout(total=None)
39
36
 
40
37
  # 核心优化:创建全局可复用的ClientSession(连接池复用)
41
38
  self.session = None
42
- # 重试配置(可根据需要调整)
43
- self.max_retry_attempts = 3 # 最大重试次数
44
- self.retry_wait_base = 0.5 # 基础等待时间(秒)
45
39
 
46
40
  # [修复] 注册退出钩子,确保程序结束时关闭连接池
47
41
  atexit.register(self._sync_close_session)
@@ -51,7 +45,7 @@ class Embedding(metaclass=SingletonMeta):
51
45
  if self.session is None or self.session.closed:
52
46
  # 配置连接池参数,适配高并发
53
47
  connector = aiohttp.TCPConnector(
54
- limit=self.max_concurrency * 2, # 连接池最大连接数(建议是并发数的2倍)
48
+ limit=self.max_concurrency, # 连接池最大连接数
55
49
  limit_per_host=self.max_concurrency, # 每个域名的最大连接数
56
50
  ttl_dns_cache=300, # DNS缓存时间
57
51
  enable_cleanup_closed=True # 自动清理关闭的连接
@@ -68,52 +62,23 @@ class Embedding(metaclass=SingletonMeta):
68
62
 
69
63
  def _sync_close_session(self):
70
64
  """同步关闭Session的封装,供atexit调用"""
71
- # 注意:atexit在主线程运行,如果当前没有事件循环,这个操作可能会受限
72
- # 但它能捕获大多数正常退出的场景。对于asyncio程序,建议显式调用cleanup
73
65
  try:
74
66
  loop = asyncio.get_event_loop()
75
67
  if loop.is_running():
76
- # 如果loop还在跑,创建一个任务去关闭
77
- loop.create_task(self.close_session())
68
+ # [修复] 修正缩进,确保 create_task 的异常能被捕获
69
+ try:
70
+ loop.create_task(self.close_session())
71
+ except Exception:
72
+ pass
78
73
  else:
79
- # 如果loop已经停止,尝试运行一次
80
- loop.run_until_complete(self.close_session())
74
+ try:
75
+ loop.run_until_complete(self.close_session())
76
+ except Exception:
77
+ pass
81
78
  except Exception:
82
- # 静默处理清理失败,避免退出报错
79
+ # 捕获获取 loop 时的异常
83
80
  pass
84
81
 
85
- async def _retry_request(self, func, *args, **kwargs):
86
- """
87
- 原生异步重试封装函数
88
- Args:
89
- func: 待重试的异步函数
90
- *args: 函数参数
91
- **kwargs: 函数关键字参数
92
- Returns:
93
- 函数执行结果,重试失败返回None
94
- """
95
- attempt = 0
96
- while attempt < self.max_retry_attempts:
97
- try:
98
- return await func(*args, **kwargs)
99
- except (aiohttp.ClientConnectionResetError, asyncio.TimeoutError, aiohttp.ClientError) as e:
100
- attempt += 1
101
- if attempt >= self.max_retry_attempts:
102
- SYLogger.error(
103
- f"Request failed after {attempt} retries: {str(e)}")
104
- return None
105
- # 指数退避等待:0.5s → 1s → 2s(最大不超过5s)
106
- wait_time = min(self.retry_wait_base * (2 ** (attempt - 1)), 5)
107
- SYLogger.warning(
108
- f"Retry {func.__name__} (attempt {attempt}/{self.max_retry_attempts}): {str(e)}, wait {wait_time}s")
109
- await asyncio.sleep(wait_time)
110
- except Exception as e:
111
- # 非重试类异常直接返回None
112
- SYLogger.error(
113
- f"Non-retryable error in {func.__name__}: {str(e)}")
114
- return None
115
- return None
116
-
117
82
  def _get_embedding_url(self, model: str) -> str:
118
83
  """获取Embedding URL(带缓存)"""
119
84
  if model not in self._embedding_url_cache:
@@ -136,7 +101,7 @@ class Embedding(metaclass=SingletonMeta):
136
101
  timeout: aiohttp.ClientTimeout = None,
137
102
  **kwargs
138
103
  ):
139
- """embedding请求核心逻辑(剥离重试,供重试封装调用)"""
104
+ """embedding请求核心逻辑"""
140
105
  await self.init_session() # 确保Session已初始化
141
106
  async with self.semaphore:
142
107
  request_timeout = timeout or self.default_timeout
@@ -154,17 +119,33 @@ class Embedding(metaclass=SingletonMeta):
154
119
  request_body.update(kwargs)
155
120
 
156
121
  # 复用全局Session
157
- async with self.session.post(
158
- url,
159
- json=request_body,
160
- timeout=request_timeout
161
- ) as response:
162
- if response.status != 200:
163
- error_detail = await response.text()
164
- SYLogger.error(
165
- f"Embedding request failed (model: {target_model}): {error_detail}")
166
- return None
167
- return await response.json()
122
+ try:
123
+ async with self.session.post(
124
+ url,
125
+ json=request_body,
126
+ timeout=request_timeout
127
+ ) as response:
128
+ if response.status != 200:
129
+ error_detail = await response.text()
130
+ # [日志] 记录详细的HTTP错误响应
131
+ SYLogger.error(
132
+ f"Embedding request HTTP Error. Status: {response.status}, "
133
+ f"Model: {target_model}, URL: {url}. Detail: {error_detail}"
134
+ )
135
+ return None
136
+ return await response.json()
137
+ except (aiohttp.ClientConnectionResetError, asyncio.TimeoutError, aiohttp.ClientError) as e:
138
+ # [日志] 记录网络错误
139
+ SYLogger.error(
140
+ f"Embedding request Network Error. Model: {target_model}, URL: {url}. "
141
+ f"Error: {e.__class__.__name__} - {str(e)}"
142
+ )
143
+ return None
144
+ except Exception as e:
145
+ # 记录其他未预期的异常
146
+ SYLogger.error(
147
+ f"Unexpected error in _get_embeddings_http_core: {str(e)}", exc_info=True)
148
+ return None
168
149
 
169
150
  async def _get_embeddings_http_async(
170
151
  self,
@@ -173,9 +154,8 @@ class Embedding(metaclass=SingletonMeta):
173
154
  model: str = None,
174
155
  timeout: aiohttp.ClientTimeout = None, ** kwargs
175
156
  ):
176
- """对外暴露的embedding请求方法(包含重试)"""
177
- return await self._retry_request(
178
- self._get_embeddings_http_core,
157
+ """对外暴露的embedding请求方法"""
158
+ return await self._get_embeddings_http_core(
179
159
  input, encoding_format, model, timeout, ** kwargs
180
160
  )
181
161
 
@@ -190,7 +170,7 @@ class Embedding(metaclass=SingletonMeta):
190
170
  return_len: Optional[bool] = True,
191
171
  timeout: aiohttp.ClientTimeout = None, ** kwargs
192
172
  ):
193
- """reranker请求核心逻辑(剥离重试,供重试封装调用)"""
173
+ """reranker请求核心逻辑"""
194
174
  await self.init_session() # 确保Session已初始化
195
175
  async with self.semaphore:
196
176
  request_timeout = timeout or self.default_timeout
@@ -212,17 +192,33 @@ class Embedding(metaclass=SingletonMeta):
212
192
  request_body.update(kwargs)
213
193
 
214
194
  # 复用全局Session
215
- async with self.session.post(
216
- url,
217
- json=request_body,
218
- timeout=request_timeout
219
- ) as response:
220
- if response.status != 200:
221
- error_detail = await response.text()
222
- SYLogger.error(
223
- f"Rerank request failed (model: {target_model}): {error_detail}")
224
- return None
225
- return await response.json()
195
+ try:
196
+ async with self.session.post(
197
+ url,
198
+ json=request_body,
199
+ timeout=request_timeout
200
+ ) as response:
201
+ if response.status != 200:
202
+ error_detail = await response.text()
203
+ # [日志] 记录详细的HTTP错误响应
204
+ SYLogger.error(
205
+ f"Reranker request HTTP Error. Status: {response.status}, "
206
+ f"Model: {target_model}, URL: {url}. Detail: {error_detail}"
207
+ )
208
+ return None
209
+ return await response.json()
210
+ except (aiohttp.ClientConnectionResetError, asyncio.TimeoutError, aiohttp.ClientError) as e:
211
+ # [日志] 记录网络错误
212
+ SYLogger.error(
213
+ f"Reranker request Network Error. Model: {target_model}, URL: {url}. "
214
+ f"Error: {e.__class__.__name__} - {str(e)}"
215
+ )
216
+ return None
217
+ except Exception as e:
218
+ # 记录其他未预期的异常
219
+ SYLogger.error(
220
+ f"Unexpected error in _get_reranker_http_core: {str(e)}", exc_info=True)
221
+ return None
226
222
 
227
223
  async def _get_reranker_http_async(
228
224
  self,
@@ -235,13 +231,23 @@ class Embedding(metaclass=SingletonMeta):
235
231
  return_len: Optional[bool] = True,
236
232
  timeout: aiohttp.ClientTimeout = None, ** kwargs
237
233
  ):
238
- """对外暴露的reranker请求方法(包含重试)"""
239
- return await self._retry_request(
240
- self._get_reranker_http_core,
234
+ """对外暴露的reranker请求方法"""
235
+ return await self._get_reranker_http_core(
241
236
  documents, query, top_n, model, max_chunks_per_doc,
242
237
  return_documents, return_len, timeout, **kwargs
243
238
  )
244
239
 
240
+ def _get_dimension(self, model: str) -> int:
241
+ """获取模型维度,用于生成兜底零向量"""
242
+ try:
243
+ config = EmbeddingConfig.from_config(model)
244
+ if hasattr(config, 'dimension'):
245
+ return int(config.dimension)
246
+ except Exception:
247
+ pass
248
+ # 默认兜底 1024
249
+ return 1024
250
+
245
251
  async def get_embeddings(
246
252
  self,
247
253
  corpus: List[str],
@@ -287,44 +293,23 @@ class Embedding(metaclass=SingletonMeta):
287
293
 
288
294
  for result in results:
289
295
  if result is None:
290
- # [修复] 尝试获取真实维度或使用配置兜底,不再硬编码 1024
291
- dim = self._model_dim_cache.get(actual_model)
292
-
293
- # 如果缓存中没有维度,尝试从配置对象获取(假设Config类有dimension属性)
294
- if dim is None:
295
- try:
296
- config = EmbeddingConfig.from_config(actual_model)
297
- if hasattr(config, 'dimension'):
298
- dim = config.dimension
299
- else:
300
- # 最后的兜底:如果配置也没有,必须有一个默认值防止崩溃
301
- # bge-large 通常是 1024
302
- dim = 1024
303
- SYLogger.warning(
304
- f"Cannot get dimension from config for {actual_model}, use default 1024")
305
- except Exception:
306
- dim = 1024
296
+ dim = self._get_dimension(actual_model)
307
297
 
308
298
  zero_vector = [0.0] * dim
309
299
  all_vectors.append(zero_vector)
300
+ # [日志] 补充日志,明确是补零操作
310
301
  SYLogger.warning(
311
- f"Embedding request failed, append zero vector ({dim}D) for model {actual_model}")
302
+ f"Embedding request failed (returned None), appending zero vector ({dim}D) for model {actual_model}")
312
303
  continue
313
304
 
314
- # 从返回结果中提取向量并更新维度缓存
315
- # 正常情况下 result["data"] 是一个列表
305
+ # 从返回结果中提取向量
316
306
  try:
317
307
  for item in result["data"]:
318
308
  embedding = item["embedding"]
319
- # [修复] 动态学习并缓存维度
320
- if actual_model not in self._model_dim_cache:
321
- self._model_dim_cache[actual_model] = len(
322
- embedding)
323
309
  all_vectors.append(embedding)
324
310
  except (KeyError, TypeError) as e:
325
311
  SYLogger.error(f"Failed to parse embedding result: {e}")
326
- # 解析失败也补零
327
- dim = self._model_dim_cache.get(actual_model, 1024)
312
+ dim = self._get_dimension(actual_model)
328
313
  all_vectors.append([0.0] * dim)
329
314
 
330
315
  SYLogger.info(
@@ -256,55 +256,66 @@ class RabbitMQClient:
256
256
  self._message_handler = handler
257
257
 
258
258
  async def _process_message_callback(self, message: AbstractIncomingMessage):
259
+ # 记录消息的原始追踪ID
260
+ original_trace_id = message.headers.get(
261
+ "trace-id") if message.headers else None
262
+ current_retry = 0
263
+
259
264
  try:
260
265
  msg_obj: MQMsgModel
266
+
267
+ # 1. 解析消息
261
268
  if self.auto_parse_json:
262
269
  try:
263
270
  body_dict = json.loads(message.body.decode("utf-8"))
264
271
  msg_obj = MQMsgModel(**body_dict)
265
272
  except json.JSONDecodeError as e:
266
273
  logger.error(f"JSON解析失败: {e}")
267
- await message.nack(requeue=False)
274
+ await message.reject(requeue=False)
268
275
  return
269
276
  else:
270
277
  msg_obj = MQMsgModel(
271
278
  body=message.body.decode("utf-8"),
272
279
  routing_key=message.routing_key,
273
280
  delivery_tag=message.delivery_tag,
274
- traceId=message.headers.get("trace-id"),
281
+ traceId=original_trace_id,
275
282
  )
276
283
 
284
+ # 2. 设置日志上下文
285
+ # 注意:如果 header 中有 x-last-retry-ts,说明之前重试过
286
+ current_retry = int(message.headers.get("x-retry-count", 0))
277
287
  SYLogger.set_trace_id(msg_obj.traceId)
278
288
 
289
+ # 3. 执行业务逻辑
279
290
  if self._message_handler:
280
291
  await self._message_handler(msg_obj, message)
281
292
 
293
+ # 4. 业务成功,Ack (移除 finally 中的 ack,成功即确认)
282
294
  await message.ack()
283
295
 
284
296
  except Exception as e:
285
- logger.error(f"消息处理异常: {e}", exc_info=True)
286
- headers = dict(message.headers) if message.headers else {}
287
- current_retry = int(headers.get("x-retry-count", 0))
297
+ logger.error(f"消息处理异常 (第 {current_retry} 次尝试): {e}", exc_info=True)
288
298
 
299
+ # 【核心修复】使用原生 Nack + Requeue
289
300
  if current_retry >= 3:
290
- logger.warning(f"重试次数超限,丢弃消息: {message.delivery_tag}")
301
+ # 超过重试次数,丢弃消息(或进入死信队列)
302
+ logger.warning(f"重试次数超限 (3次),丢弃消息: {message.delivery_tag}")
291
303
  await message.reject(requeue=False)
292
304
  else:
293
- headers["x-retry-count"] = current_retry + 1
294
- try:
295
- new_msg = Message(
296
- body=message.body,
297
- headers=headers,
298
- content_type=message.content_type,
299
- delivery_mode=message.delivery_mode
300
- )
301
- # 这里的 publish 如果失败,会触发重连机制
302
- # 但注意,当前是在回调线程中,建议做好异常捕获
303
- await self._exchange.publish(new_msg, routing_key=message.routing_key)
304
- await message.ack()
305
- except Exception as pub_err:
306
- logger.error(f"重试发布失败: {pub_err}")
307
- await message.reject(requeue=False)
305
+ # 还没到重试上限,重新入队
306
+ # 为了防止立即重试导致的死循环,我们需要人为增加一点延迟
307
+ # Nack 本身不支持延迟,所以这里只能快速 Nack 让它尽快回来,
308
+ # 并在业务层(或外层)做好限流保护。
309
+
310
+ # 如果你有延迟队列插件,可以 publish 到延迟交换机。
311
+ # 如果没有,直接 requeue 是最安全的不丢包方案。
312
+ logger.info(f"消息处理失败,重新入队等待重试... (当前重试: {current_retry})")
313
+
314
+ # 技巧:如果你不想立即重试,可以 Nack(False) 然后手动 Publish 延迟消息
315
+ # 但为了解决你当前的“死循环”问题,直接 Nack(True) 是最有效的
316
+ # 延迟5秒
317
+ await asyncio.sleep(5)
318
+ await message.nack(requeue=True)
308
319
 
309
320
  async def start_consuming(self) -> Optional[ConsumerTag]:
310
321
  if self._closed:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.57b3
3
+ Version: 0.1.57b5
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -19,7 +19,7 @@ sycommon/health/health_check.py,sha256=EhfbhspRpQiKJaxdtE-PzpKQO_ucaFKtQxIm16F5M
19
19
  sycommon/health/metrics.py,sha256=fHqO73JuhoZkNPR-xIlxieXiTCvttq-kG-tvxag1s1s,268
20
20
  sycommon/health/ping.py,sha256=FTlnIKk5y1mPfS1ZGOeT5IM_2udF5aqVLubEtuBp18M,250
21
21
  sycommon/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- sycommon/llm/embedding.py,sha256=rasx8xBOq-mQdWZ5RSzpwjbkAKi7Da-FDWiTm-Ga2Bs,15606
22
+ sycommon/llm/embedding.py,sha256=qf7snCJQNemiB6Yz3nHEVB70sMjGXQx59Fz5RpT82sE,14372
23
23
  sycommon/llm/get_llm.py,sha256=C48gt9GCwEpR26M-cUjM74_t-el18ZvlwpGhcQfR3gs,1054
24
24
  sycommon/llm/llm_logger.py,sha256=n4UeNy_-g4oHQOsw-VUzF4uo3JVRLtxaMp1FcI8FiEo,5437
25
25
  sycommon/llm/llm_tokens.py,sha256=-udDyFcmyzx6UAwIi6_d_wwI5kMd5w0-WcS2soVPQxg,4309
@@ -51,7 +51,7 @@ sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6
51
51
  sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
52
52
  sycommon/notice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  sycommon/notice/uvicorn_monitor.py,sha256=VryQYcAtjijJuGDBimbVurgwxlsLaLtkNnABPDY5Tao,7332
54
- sycommon/rabbitmq/rabbitmq_client.py,sha256=hAbLOioU_clucJ9xq88Oo-waZOuU0ii4yBVGIjz1nBE,17992
54
+ sycommon/rabbitmq/rabbitmq_client.py,sha256=DTGJ0fkzg55l_XPbassfaN3j7A2grnakm2yflFiLqTk,18668
55
55
  sycommon/rabbitmq/rabbitmq_pool.py,sha256=BiFQgZPzSAFR-n5XhyIafoeWQXETF_31nFRDhMbe6aU,15577
56
56
  sycommon/rabbitmq/rabbitmq_service.py,sha256=XSHo9HuIJ_lq-vizRh4xJVdZr_2zLqeLhot09qb0euA,2025
57
57
  sycommon/rabbitmq/rabbitmq_service_client_manager.py,sha256=IP9TMFeG5LSrwFPEmOy1ce4baPxBUZnWJZR3nN_-XR4,8009
@@ -82,8 +82,8 @@ sycommon/tools/env.py,sha256=Ah-tBwG2C0_hwLGFebVQgKdWWXCjTzBuF23gCkLHYy4,2437
82
82
  sycommon/tools/merge_headers.py,sha256=u9u8_1ZIuGIminWsw45YJ5qnsx9MB-Fot0VPge7itPw,4941
83
83
  sycommon/tools/snowflake.py,sha256=xQlYXwYnI85kSJ1rZ89gMVBhzemP03xrMPVX9vVa3MY,9228
84
84
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
85
- sycommon_python_lib-0.1.57b3.dist-info/METADATA,sha256=PWfBGcqM7sbERAKlz3I9wM0zuIQCw4rTQDw5f2Nn56c,7301
86
- sycommon_python_lib-0.1.57b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
- sycommon_python_lib-0.1.57b3.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
88
- sycommon_python_lib-0.1.57b3.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
89
- sycommon_python_lib-0.1.57b3.dist-info/RECORD,,
85
+ sycommon_python_lib-0.1.57b5.dist-info/METADATA,sha256=IDLVpTPF4RAD8J-mIjE0ihJ5oPXX0AoM6sh0cIrOHZc,7301
86
+ sycommon_python_lib-0.1.57b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
+ sycommon_python_lib-0.1.57b5.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
88
+ sycommon_python_lib-0.1.57b5.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
89
+ sycommon_python_lib-0.1.57b5.dist-info/RECORD,,