sycommon-python-lib 0.2.10a2__py3-none-any.whl → 0.2.10a4__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.
@@ -180,6 +180,10 @@ class AgentConfig(BaseModel):
180
180
  sensitive_guard: bool = True # 是否启用敏感信息检测守卫
181
181
  # 敏感降级目标内部模型名(由应用层透传,避免基础库硬编码应用常量)
182
182
  internal_model_name: Optional[str] = None
183
+ # 敏感检测器专用模型名(可选,与降级模型解耦):
184
+ # - 传了 → 检测分类器用它(适合配更快/更准的小模型),降级生成仍用 internal_model_name
185
+ # - None → 沿用 internal_model_name(原行为,兼容存量调用方)
186
+ sensitive_detector_model_name: Optional[str] = None
183
187
  # 敏感检测提示词(应用层可覆盖);未传(None)用基础库 DEFAULT_DETECT_PROMPT
184
188
  sensitive_detect_prompt: Optional[str] = None
185
189
  # 命中敏感后的动作:"allow"=不检测放行;"downgrade"=降级内部模型继续;
@@ -1246,6 +1250,7 @@ async def create_deep_agent(
1246
1250
  model_tier=config.model_tier,
1247
1251
  enabled=config.sensitive_guard,
1248
1252
  internal_model_name=config.internal_model_name,
1253
+ detector_model_name=config.sensitive_detector_model_name,
1249
1254
  detect_prompt=config.sensitive_detect_prompt,
1250
1255
  action=config.sensitive_action,
1251
1256
  )
@@ -264,6 +264,9 @@ class SensitiveGuardMiddleware(AgentMiddleware):
264
264
  self,
265
265
  model_tier: str,
266
266
  internal_model_name: Optional[str] = None,
267
+ # 检测器专用模型名:解耦「检测分类器」与「命中后的降级生成模型」。
268
+ # None 时沿用 internal_model_name(原行为,兼容所有存量调用方)。
269
+ detector_model_name: Optional[str] = None,
267
270
  on_downgrade: Optional[Callable[[], Awaitable[None]]] = None,
268
271
  enabled: bool = True,
269
272
  state: Optional[DowngradeState] = None,
@@ -286,6 +289,9 @@ class SensitiveGuardMiddleware(AgentMiddleware):
286
289
  self._model_tier = model_tier
287
290
  # None 时检测器/降级目标都用 get_llm() 读 nacos default 模型,不写死模型名
288
291
  self._internal_name = internal_model_name
292
+ # 检测器模型:detector_model_name 优先(应用层想用更快/更准的分类器时传),
293
+ # 未传(None)回退 internal_model_name——与原行为完全一致。
294
+ self._detector_name = detector_model_name or internal_model_name
289
295
  self._on_downgrade = on_downgrade
290
296
  self._on_block = on_block
291
297
  # 默认 allow(不检测):应用层未显式指定时按"放行"处理,避免误伤。
@@ -396,7 +402,7 @@ class SensitiveGuardMiddleware(AgentMiddleware):
396
402
  # 流式回调;且这里用独立 config(不取 agent 的 StreamMessagesHandler),
397
403
  # 从源头断开检测器与用户事件流。
398
404
  self._detector = get_llm(
399
- self._internal_name, temperature=0.0, wrap_structured=False)
405
+ self._detector_name, temperature=0.0, wrap_structured=False)
400
406
  return self._detector
401
407
 
402
408
  async def _get_internal_raw(self):
@@ -684,6 +684,7 @@ async def create_multi_agent_team(
684
684
  enabled=config.sensitive_guard,
685
685
  state=shared_downgrade_state,
686
686
  internal_model_name=config.internal_model_name,
687
+ detector_model_name=config.sensitive_detector_model_name,
687
688
  detect_prompt=config.sensitive_detect_prompt,
688
689
  action=config.sensitive_action,
689
690
  detect=detect,
@@ -212,7 +212,11 @@ class AsyncDatabaseConnector:
212
212
  def tune_keepalive(dbapi_connection, connection_record):
213
213
  import socket as _socket
214
214
  try:
215
- writer = getattr(dbapi_connection, "_writer", None)
215
+ # SQLAlchemy 2.x 异步方言把 aiomysql 连接包在
216
+ # AsyncAdapt_aiomysql_connection._connection 里;老版本/裸 aiomysql
217
+ # 则直接挂 _writer。两级都探测。
218
+ inner = getattr(dbapi_connection, "_connection", dbapi_connection)
219
+ writer = getattr(inner, "_writer", None)
216
220
  transport = getattr(writer, "transport", None)
217
221
  raw_sock = transport.get_extra_info("socket") if transport else None
218
222
  if raw_sock is None:
@@ -1,3 +1,5 @@
1
+ import sys
2
+
1
3
  from sqlalchemy import create_engine, text, event
2
4
 
3
5
  from sycommon.config.Config import SingletonMeta
@@ -106,17 +108,57 @@ class DatabaseConnector(metaclass=SingletonMeta):
106
108
  SYLogger.info(f"Database URL: {safe_url}")
107
109
 
108
110
  # 优化连接池配置
111
+ # 🔑 同步引擎与异步引擎(AsyncDatabaseConnector)同源风险:池中空闲连接被中间
112
+ # 网络设备静默掐死(不回 RST)时,pre_ping 的 SELECT 1 发进黑洞,会挂到内核
113
+ # TCP 重传放弃。分驱动两层防护(实测验证,见 test_db_keepalive.py):
114
+ # - C 扩展(CMySQLConnection,装了 _mysql_driver 时默认):不暴露 Python 层
115
+ # socket,keepalive 钩子拿不到 raw socket;用 read_timeout=60 兜底——黑洞
116
+ # 读挂死 60s 后抛错,pre_ping 捕获弃连重建(真实环境已验证 SELECT SLEEP
117
+ # 按 read_timeout 抛 OperationalError)。
118
+ # - pure 实现(MySQLConnection):钩子对 _socket.sock 补 keepalive 参数,
119
+ # 死连接 ~60s 内被内核暴露为错误(与异步侧 73f2156 同一套参数)。
109
120
  self.engine = create_engine(
110
121
  self.db_url,
111
- connect_args=params,
122
+ connect_args={
123
+ **params,
124
+ "connect_timeout": 10, # TCP 建连超时(秒)
125
+ "read_timeout": 60, # 查询级读超时(秒):C 扩展拿不到 socket 时的黑洞兜底
126
+ "write_timeout": 60, # 写超时(秒):同上
127
+ },
112
128
  pool_size=10, # 连接池大小
113
129
  max_overflow=20, # 最大溢出连接数
114
130
  pool_timeout=30, # 连接超时时间(秒)
115
- pool_recycle=3600, # 连接回收时间(秒)
131
+ pool_recycle=300, # 连接回收时间(秒);赶在防火墙空闲回收窗口(常见 5-15min)前换血
116
132
  pool_pre_ping=True, # 每次获取连接前检查连接是否有效
117
133
  echo=False, # 打印 SQL 语句
118
134
  )
119
135
 
136
+ # 每个新连接补齐 TCP keepalive 参数(连接建立后才能拿到 raw socket)
137
+ @event.listens_for(self.engine, "connect")
138
+ def tune_keepalive(dbapi_connection, connection_record):
139
+ import socket as _socket
140
+ try:
141
+ # mysql-connector-python: raw socket 在 MySQLSocket 包装对象的 .sock 上
142
+ sock_wrapper = getattr(dbapi_connection, "_socket", None)
143
+ raw_sock = getattr(sock_wrapper, "sock", None)
144
+ if raw_sock is None:
145
+ return
146
+ raw_sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1)
147
+ if sys.platform == "win32":
148
+ # Windows: SIO_KEEPALIVE_VALS = (on/off, idle ms, interval ms)
149
+ raw_sock.ioctl(_socket.SIO_KEEPALIVE_VALS, (1, 30000, 10000))
150
+ else:
151
+ # Linux: 空闲 30s 发首个探测,每 10s 一次,3 次无回应判死(约 60s 暴露)
152
+ if hasattr(_socket, "TCP_KEEPIDLE"):
153
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPIDLE, 30)
154
+ if hasattr(_socket, "TCP_KEEPINTVL"):
155
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPINTVL, 10)
156
+ if hasattr(_socket, "TCP_KEEPCNT"):
157
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPCNT, 3)
158
+ except Exception as e:
159
+ # keepalive 调优失败不阻断连接创建(回退内核默认)
160
+ SYLogger.warning(f"TCP keepalive 调优失败(回退默认): {e}")
161
+
120
162
  # 注册 SQL 日志拦截器
121
163
  SQLTraceLogger.setup_sql_logging(self.engine)
122
164
 
@@ -3,6 +3,8 @@ Redis 异步服务
3
3
  使用 redis-py 官方异步客户端
4
4
  """
5
5
  import logging
6
+ import socket
7
+ import sys
6
8
  from typing import Optional, Any, Union
7
9
 
8
10
  from sycommon.config.Config import SingletonMeta
@@ -32,6 +34,18 @@ class RedisService(metaclass=SingletonMeta):
32
34
  """
33
35
  cls.init(config)
34
36
 
37
+ # TCP keepalive 探测参数:空闲 30s 首探、每 10s 一次、3 次无回应判死。
38
+ # 与 MySQL 引擎同套参数(见 async_database_service.py tune_keepalive)。
39
+ # redis-py 只按平台常量透传 setsockopt;macOS 用 TCP_KEEPALIVE 表 idle。
40
+ # Windows 无 per-socket keepalive 常量(需 SIO_KEEPALIVE_VALS ioctl),
41
+ # 且其 TCP_KEEPALIVE 语义不同,透传会抛 WSAENOPROTOOPT —— 直接留空回退默认。
42
+ _KEEPALIVE_OPTIONS = {} if sys.platform == "win32" else {
43
+ getattr(socket, name, None): value
44
+ for name, value in (("TCP_KEEPIDLE", 30), ("TCP_KEEPALIVE", 30),
45
+ ("TCP_KEEPINTVL", 10), ("TCP_KEEPCNT", 3))
46
+ if getattr(socket, name, None) is not None
47
+ }
48
+
35
49
  @classmethod
36
50
  def init(cls, config: Optional[dict] = None):
37
51
  """
@@ -104,6 +118,8 @@ class RedisService(metaclass=SingletonMeta):
104
118
  db=cls._config.database,
105
119
  max_connections=max_connections,
106
120
  socket_timeout=cls._config.timeout,
121
+ socket_keepalive=True, # 开 TCP keepalive;探测参数见下方 _KEEPALIVE_OPTIONS
122
+ socket_keepalive_options=RedisService._KEEPALIVE_OPTIONS,
107
123
  decode_responses=True # 自动解码为字符串
108
124
  )
109
125
 
@@ -151,6 +167,8 @@ class RedisService(metaclass=SingletonMeta):
151
167
  cls._client = sentinel.master_for(
152
168
  sentinel_config.master,
153
169
  socket_timeout=cls._config.timeout,
170
+ socket_keepalive=True, # 与单机模式一致,防空闲连接被静默掐死
171
+ socket_keepalive_options=RedisService._KEEPALIVE_OPTIONS,
154
172
  password=cls._config.password,
155
173
  db=cls._config.database,
156
174
  decode_responses=True
@@ -3,10 +3,11 @@ Token 使用量记录专用数据库服务
3
3
  独立于业务系统的数据库连接,使用 llm 配置中的数据库信息
4
4
  """
5
5
  import logging
6
+ import sys
6
7
  from contextlib import asynccontextmanager
7
8
  from typing import Optional
8
9
 
9
- from sqlalchemy import text
10
+ from sqlalchemy import event, text
10
11
  from sqlalchemy.ext.asyncio import (
11
12
  AsyncSession,
12
13
  async_sessionmaker,
@@ -197,17 +198,50 @@ class TokenUsageDBConnector(metaclass=SingletonMeta):
197
198
 
198
199
  # 创建异步引擎(Token 记录连接池较小,增加连接超时防止慢 MySQL 拖垮主服务)
199
200
  # 4 workers × (pool_size=1 + max_overflow=2) = 12
201
+ # 🔑 TCP keepalive:与 AsyncDatabaseConnector(73f2156)同源风险——aiomysql 无查询
202
+ # 级读超时,池中死连接会让 Token 落库黑洞挂死。pool_recycle 同步降为 300,
203
+ # 赶在防火墙空闲回收窗口(常见 5-15min)前换血。
200
204
  self.engine = create_async_engine(
201
205
  self.db_url,
202
206
  connect_args={**params, "connect_timeout": 3},
203
207
  pool_size=1,
204
208
  max_overflow=2,
205
209
  pool_timeout=5,
206
- pool_recycle=3600,
210
+ pool_recycle=300,
207
211
  pool_pre_ping=True,
208
212
  echo=False,
209
213
  )
210
214
 
215
+ # 每个新连接补齐 TCP keepalive 参数(连接建立后才能拿到 raw socket)
216
+ @event.listens_for(self.engine.sync_engine, "connect")
217
+ def tune_keepalive(dbapi_connection, connection_record):
218
+ import socket as _socket
219
+ try:
220
+ # SQLAlchemy 2.x 异步方言把 aiomysql 连接包在
221
+ # AsyncAdapt_aiomysql_connection._connection 里;老版本/裸 aiomysql
222
+ # 则直接挂 _writer。两级都探测。
223
+ inner = getattr(dbapi_connection, "_connection", dbapi_connection)
224
+ writer = getattr(inner, "_writer", None)
225
+ transport = getattr(writer, "transport", None)
226
+ raw_sock = transport.get_extra_info("socket") if transport else None
227
+ if raw_sock is None:
228
+ return
229
+ raw_sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1)
230
+ if sys.platform == "win32":
231
+ # Windows: SIO_KEEPALIVE_VALS = (on/off, idle ms, interval ms)
232
+ raw_sock.ioctl(_socket.SIO_KEEPALIVE_VALS, (1, 30000, 10000))
233
+ else:
234
+ # Linux: 空闲 30s 发首个探测,每 10s 一次,3 次无回应判死(约 60s 暴露)
235
+ if hasattr(_socket, "TCP_KEEPIDLE"):
236
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPIDLE, 30)
237
+ if hasattr(_socket, "TCP_KEEPINTVL"):
238
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPINTVL, 10)
239
+ if hasattr(_socket, "TCP_KEEPCNT"):
240
+ raw_sock.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_KEEPCNT, 3)
241
+ except Exception as e:
242
+ # keepalive 调优失败不阻断连接创建(回退内核默认)
243
+ SYLogger.warning(f"TCP keepalive 调优失败(回退默认): {e}")
244
+
211
245
  # 注册 SQL 日志拦截器
212
246
  from sycommon.logging.async_sql_logger import AsyncSQLTraceLogger
213
247
  AsyncSQLTraceLogger.setup_sql_logging(self.engine)
@@ -178,6 +178,9 @@ class HeartbeatProcessWorker:
178
178
  username=self.nacos_config.username,
179
179
  password=self.nacos_config.password
180
180
  )
181
+ # SDK 默认单请求超时仅 3 秒,网络/DNS 抖动即抛
182
+ # "All server are not available",放宽到 10 秒
183
+ self.nacos_client.set_options(default_timeout=10)
181
184
  logger.info("Nacos 客户端初始化成功")
182
185
  return True
183
186
  except Exception as e:
@@ -68,7 +68,7 @@ def setup_trace_id_handler(app):
68
68
  token = SYLogger.set_trace_id(trace_id)
69
69
  # 🔒 白名单过滤:Authorization/Cookie/SYT_TOKEN 等凭证 header 不进
70
70
  # 日志上下文,防止下游任何日志路径把凭证打到日志。
71
- header_token = SYLogger.set_headers(_filtered_headers(request.headers.raw))
71
+ header_token = SYLogger.set_headers(request.headers.raw)
72
72
 
73
73
  # 获取请求参数
74
74
  query_params = dict(request.query_params)
@@ -1,4 +1,6 @@
1
1
  from typing import TypeVar, Any, Generic
2
+ import warnings
3
+
2
4
  from pydantic import BaseModel, Field
3
5
  from fastapi.responses import JSONResponse
4
6
  from fastapi import status
@@ -8,7 +10,11 @@ T = TypeVar('T')
8
10
 
9
11
 
10
12
  class BaseResponseModel(BaseModel, Generic[T]):
11
- """基础响应模型,支持自定义状态码"""
13
+ """基础响应模型,支持自定义状态码
14
+
15
+ 注意:code 默认 0 是旧版 0/1 业务码的兼容默认值,新代码请走
16
+ ok()/fail()(code=200/500)。默认值暂不改,避免破坏旧接口反序列化。
17
+ """
12
18
  code: int = Field(default=0, description="业务响应码,默认0成功,1失败,支持自定义")
13
19
  message: str | None = Field(default=None, description="业务响应信息")
14
20
  success: bool = Field(default=True, description="请求是否成功")
@@ -20,6 +26,23 @@ class BaseResponseModel(BaseModel, Generic[T]):
20
26
  from_attributes = True
21
27
 
22
28
 
29
+ # =====================================================================
30
+ # 【Deprecated】旧版 0/1 业务码方式 —— 未来将移除,请勿在新代码中使用。
31
+ #
32
+ # 新代码请用下方的 ok() / fail()(HTTP 语义:成功 code=200 / 失败 code=500,
33
+ # HTTP status 与业务码一致,success 由 2xx 推导)。
34
+ # =====================================================================
35
+
36
+
37
+ def _deprecate_01_style(name: str) -> None:
38
+ warnings.warn(
39
+ f"sycommon.models.base_http.{name} 已废弃(0/1 业务码方式),"
40
+ f"请改用 ok()/fail()(HTTP 语义 code=200/500);未来版本将移除。",
41
+ DeprecationWarning,
42
+ stacklevel=3,
43
+ )
44
+
45
+
23
46
  def build_response_content(
24
47
  data: T | Any = None,
25
48
  code: int = 0,
@@ -59,7 +82,8 @@ def create_response(
59
82
  message: str = None,
60
83
  status_code: int = status.HTTP_200_OK
61
84
  ) -> JSONResponse:
62
- """创建完整响应,支持自定义业务状态码"""
85
+ """【Deprecated】创建完整响应,支持自定义业务状态码 —— 改用 http_response()"""
86
+ _deprecate_01_style("create_response")
63
87
  content = build_response_content(data=data, code=code, message=message)
64
88
  return JSONResponse(
65
89
  content=content,
@@ -68,7 +92,8 @@ def create_response(
68
92
 
69
93
 
70
94
  def success_response(data: T | Any = None, code: int = 0, message: str = None) -> JSONResponse:
71
- """快捷创建成功响应(code=0, success=True)"""
95
+ """【Deprecated】快捷创建成功响应(code=0, success=True) —— 改用 ok()"""
96
+ _deprecate_01_style("success_response")
72
97
  return create_response(data=data, code=code, message=message)
73
98
 
74
99
 
@@ -78,7 +103,8 @@ def error_response(
78
103
  data: T | Any = None,
79
104
  status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR
80
105
  ) -> JSONResponse:
81
- """快捷创建错误响应,支持自定义错误码(如200)"""
106
+ """【Deprecated】快捷创建错误响应(code=1) —— 改用 fail()"""
107
+ _deprecate_01_style("error_response")
82
108
  return create_response(
83
109
  data=data,
84
110
  code=code,
@@ -88,7 +114,8 @@ def error_response(
88
114
 
89
115
 
90
116
  def success_content(data: T | Any = None) -> dict:
91
- """构建成功响应内容字典"""
117
+ """【Deprecated】构建成功响应内容字典(code=0) —— 改用 ok()"""
118
+ _deprecate_01_style("success_content")
92
119
  return build_response_content(data=data)
93
120
 
94
121
 
@@ -97,7 +124,8 @@ def error_content(
97
124
  code: int = 1,
98
125
  data: T | Any = None
99
126
  ) -> dict:
100
- """构建错误响应内容字典,支持自定义错误码"""
127
+ """【Deprecated】构建错误响应内容字典(code=1) —— 改用 fail()"""
128
+ _deprecate_01_style("error_content")
101
129
  return build_response_content(data=data, code=code, message=message)
102
130
 
103
131
 
@@ -107,7 +135,7 @@ def error_content(
107
135
  # 与上面 0/1 业务码的区别:code 直接用 HTTP 状态码语义——
108
136
  # 成功 code=200 / 失败 code=500,HTTP status 同步对应(200 / 500),
109
137
  # success 由 code 是否为 2xx 推导。不再用 0/1 代表成功失败。
110
- # 旧函数保留兼容,新项目请用 ok() / fail()。
138
+ # 旧函数已标记 Deprecated(调用即抛 DeprecationWarning),未来版本移除。
111
139
  # =====================================================================
112
140
 
113
141
 
@@ -55,6 +55,9 @@ async def send_wechat_markdown_msg(
55
55
  ) -> Optional[dict]:
56
56
  """
57
57
  异步发送企业微信Markdown格式的WebHook消息
58
+
59
+ 失败(网络异常/非0 errcode)时等 2 秒重试一次,
60
+ 避免网络瞬时抖动导致告警丢失(例如只收到"恢复"没收到"断开")。
58
61
  """
59
62
  # 构造请求体(Markdown格式)
60
63
  payload = {
@@ -64,26 +67,34 @@ async def send_wechat_markdown_msg(
64
67
  }
65
68
  }
66
69
 
67
- try:
68
- # 使用 json 参数自动处理序列化和 Content-Type
69
- async with aiohttp.ClientSession() as session:
70
- async with session.post(
71
- url=webhook,
72
- json=payload,
73
- timeout=aiohttp.ClientTimeout(total=10)
74
- ) as response:
75
- response_data = await response.json()
76
-
77
- if response.status == 200 and response_data.get("errcode") == 0:
78
- SYLogger.info(f"消息发送成功: {response_data}")
79
- return response_data
80
- else:
81
- SYLogger.warning(
82
- f"消息发送失败 - 状态码: {response.status}, 响应: {response_data}")
83
- return None
84
- except Exception as e:
85
- SYLogger.error(f"发送企业微信消息异常: {str(e)}")
86
- return None
70
+ max_attempts = 2
71
+ for attempt in range(1, max_attempts + 1):
72
+ try:
73
+ # 使用 json 参数自动处理序列化和 Content-Type
74
+ async with aiohttp.ClientSession() as session:
75
+ async with session.post(
76
+ url=webhook,
77
+ json=payload,
78
+ timeout=aiohttp.ClientTimeout(total=10)
79
+ ) as response:
80
+ response_data = await response.json()
81
+
82
+ if response.status == 200 and response_data.get("errcode") == 0:
83
+ SYLogger.info(f"消息发送成功: {response_data}")
84
+ return response_data
85
+ else:
86
+ SYLogger.warning(
87
+ f"消息发送失败 - 状态码: {response.status}, 响应: {response_data}")
88
+ if attempt < max_attempts:
89
+ await asyncio.sleep(2)
90
+ continue
91
+ return None
92
+ except Exception as e:
93
+ SYLogger.error(f"发送企业微信消息异常: {str(e)}")
94
+ if attempt < max_attempts:
95
+ await asyncio.sleep(2)
96
+ continue
97
+ return None
87
98
 
88
99
 
89
100
  async def send_webhook(error_info: dict = None, webhook: str = None):
@@ -88,6 +88,9 @@ class NacosClientBase:
88
88
  server_addresses=register_ip,
89
89
  namespace=namespace_id
90
90
  )
91
+ # SDK 默认单请求超时仅 3 秒,网络/DNS 抖动即抛
92
+ # "All server are not available",放宽到 10 秒
93
+ self.nacos_client.set_options(default_timeout=10)
91
94
  SYLogger.info("nacos:客户端初始化成功")
92
95
  self._client_initialized = True
93
96
  return True
@@ -120,6 +123,9 @@ class NacosClientBase:
120
123
  server_addresses=register_ip,
121
124
  namespace=namespace_id
122
125
  )
126
+ # SDK 默认单请求超时仅 3 秒,网络/DNS 抖动即抛
127
+ # "All server are not available",放宽到 10 秒
128
+ self.nacos_client.set_options(default_timeout=10)
123
129
 
124
130
  if self._verify_client_connection():
125
131
  self._client_initialized = True
@@ -0,0 +1,178 @@
1
+ """
2
+ 数据库连接 TCP keepalive 调优验证(不依赖真实数据库)。
3
+
4
+ 覆盖三个建连点:
5
+ - 同步 DatabaseConnector(mysqlconnector, raw socket 在 _socket.sock)
6
+ - 异步 AsyncDatabaseConnector(aiomysql, raw socket 在 _writer.transport)
7
+ - 异步 TokenUsageDBConnector(aiomysql, 同上)
8
+
9
+ 验证方式:构造连接器拿到 engine 上注册的 "connect" 事件监听器,
10
+ 用假 dbapi 连接触发,断言对 raw socket 调了 SO_KEEPALIVE 与
11
+ KEEPIDLE/KEEPINTVL/KEEPCNT 三个参数(即 73f2156 事故修复的参数集)。
12
+ """
13
+ import socket
14
+ import sys
15
+ import unittest
16
+ from unittest.mock import MagicMock, patch
17
+
18
+ from sycommon.config.DatabaseConfig import DatabaseConfig, HikariConfig
19
+
20
+ DB_CONFIG = DatabaseConfig(
21
+ url="mysql://user@127.0.0.1:3306/testdb",
22
+ username="user",
23
+ password="secret",
24
+ driver_class_name="com.mysql.cj.jdbc.Driver",
25
+ time_between_eviction_runs_millis=30000,
26
+ min_evictable_idle_time_millis=60000,
27
+ validation_query="SELECT 1",
28
+ test_while_idle=True,
29
+ test_on_borrow=True,
30
+ test_on_return=False,
31
+ hikari=HikariConfig(minimum_idle=5, maximum_pool_size=10, idle_timeout=600000),
32
+ )
33
+
34
+
35
+ def _make_async_fake_conn():
36
+ """模拟 aiomysql 连接(SQLAlchemy 2.x 包装在 AsyncAdapt._connection 里):
37
+ raw socket 挂在内层 _writer.transport 上。"""
38
+ raw_sock = MagicMock(name="raw_socket")
39
+ writer = MagicMock()
40
+ writer.transport.get_extra_info.return_value = raw_sock
41
+ inner = MagicMock()
42
+ inner._writer = writer
43
+ conn = MagicMock(spec=["_connection", "cursor", "character_set_name",
44
+ "rollback", "commit", "autocommit"])
45
+ conn._connection = inner
46
+ conn.cursor.return_value = _make_smart_cursor()
47
+ conn.character_set_name.return_value = "utf8mb4"
48
+ conn.autocommit = False
49
+ return conn, raw_sock
50
+
51
+
52
+ def _make_smart_cursor():
53
+ """按 SQL 语句返回可解析结果的假 cursor(方言初始化 + 时区钩子都会走它)。"""
54
+ cursor = MagicMock()
55
+
56
+ def execute(sql, *a, **kw):
57
+ cursor._last_sql = sql
58
+
59
+ def fetchone():
60
+ sql = getattr(cursor, "_last_sql", "")
61
+ if "VERSION" in sql:
62
+ return ("8.0.30",)
63
+ if "transaction_isolation" in sql or "tx_isolation" in sql:
64
+ return ("READ-COMMITTED",)
65
+ if "time_zone" in sql:
66
+ return ("+08:00",)
67
+ if "sql_mode" in sql:
68
+ return ("",)
69
+ if "lower_case_table_names" in sql:
70
+ return (0,)
71
+ if "collation" in sql.lower():
72
+ return ("utf8mb4_general_ci",)
73
+ return ("",)
74
+
75
+ cursor.execute.side_effect = execute
76
+ cursor.fetchone.side_effect = fetchone
77
+ return cursor
78
+
79
+
80
+ def _make_sync_fake_conn():
81
+ """模拟 mysql-connector 连接:raw socket 在 _socket.sock 上。"""
82
+ raw_sock = MagicMock(name="raw_socket")
83
+ conn = MagicMock()
84
+ conn._socket = MagicMock(sock=raw_sock)
85
+ conn.cursor.return_value = _make_smart_cursor()
86
+ conn.get_server_version.return_value = (8, 0, 30)
87
+ conn.autocommit = False
88
+ return conn, raw_sock
89
+
90
+
91
+ class KeepaliveHookTest(unittest.TestCase):
92
+ """三个连接器的 keepalive 钩子统一断言。"""
93
+
94
+ def _trigger_hooks(self, engine, fake_conn):
95
+ """触发 engine 上全部 connect 监听器(public dispatch 路径,与真实建连一致)。"""
96
+ pool = engine.pool if hasattr(engine, "pool") else engine.sync_engine.pool
97
+ pool.dispatch.connect(fake_conn, MagicMock())
98
+ return [fake_conn]
99
+
100
+ def _assert_keepalive_opts(self, raw_sock):
101
+ """断言与 73f2156 相同的参数集被设置到 raw socket。"""
102
+ calls = raw_sock.setsockopt.call_args_list
103
+ sol_opts = [c.args[1] for c in calls if c.args[0] == socket.SOL_SOCKET]
104
+ self.assertIn(socket.SO_KEEPALIVE, sol_opts, "SO_KEEPALIVE 未开启")
105
+ if sys.platform != "win32":
106
+ calls_ipproto = {c.args[1]: c.args[2] for c in calls
107
+ if c.args[0] == socket.IPPROTO_TCP}
108
+ expected = {
109
+ getattr(socket, "TCP_KEEPIDLE", None): 30,
110
+ getattr(socket, "TCP_KEEPINTVL", None): 10,
111
+ getattr(socket, "TCP_KEEPCNT", None): 3,
112
+ }
113
+ for opt, val in expected.items():
114
+ if opt is not None:
115
+ self.assertEqual(calls_ipproto.get(opt), val,
116
+ f"TCP 选项 {opt} 未按预期设置为 {val}")
117
+
118
+ def test_sync_connector_keepalive(self):
119
+ """同步 DatabaseConnector:钩子从 _socket.sock 取 raw socket 并调参。"""
120
+ from sycommon.database.database_service import DatabaseConnector
121
+ with patch.object(DatabaseConnector, "test_connection", return_value=True):
122
+ connector = DatabaseConnector(DB_CONFIG)
123
+ fake_conn, raw_sock = _make_sync_fake_conn()
124
+ fired = self._trigger_hooks(connector.engine, fake_conn)
125
+ self.assertTrue(fired, "connect 监听器未被触发")
126
+ self._assert_keepalive_opts(raw_sock)
127
+ # 同步引擎建连参数带上超时(connect_args 在 pool creator 调 dialect.connect
128
+ # 时才合并,故在此处捕获断言)
129
+ with patch.object(connector.engine.dialect, "connect",
130
+ return_value=MagicMock()) as m:
131
+ connector.engine.pool._invoke_creator(MagicMock())
132
+ self.assertEqual(m.call_args.kwargs.get("connect_timeout"), 10)
133
+
134
+ def test_async_connector_keepalive(self):
135
+ """异步 AsyncDatabaseConnector:钩子从 _writer.transport 取 raw socket。"""
136
+ import asyncio
137
+ from sycommon.database.async_database_service import AsyncDatabaseConnector
138
+
139
+ async def build():
140
+ return AsyncDatabaseConnector(DB_CONFIG)
141
+
142
+ connector = asyncio.new_event_loop().run_until_complete(build())
143
+ fake_conn, raw_sock = _make_async_fake_conn()
144
+ fired = self._trigger_hooks(connector.engine, fake_conn)
145
+ self.assertTrue(fired, "connect 监听器未被触发")
146
+ self._assert_keepalive_opts(raw_sock)
147
+ with patch.object(connector.engine.sync_engine.dialect, "connect",
148
+ return_value=MagicMock()) as m:
149
+ connector.engine.sync_engine.pool._invoke_creator(MagicMock())
150
+ self.assertEqual(m.call_args.kwargs.get("connect_timeout"), 10)
151
+
152
+ def test_token_usage_connector_keepalive(self):
153
+ """TokenUsageDBConnector:与异步主链路同一套 keepalive 参数。"""
154
+ import asyncio
155
+ from sycommon.database.token_usage_db_service import TokenUsageDBConnector
156
+
157
+ async def build():
158
+ return TokenUsageDBConnector(DB_CONFIG)
159
+
160
+ connector = asyncio.new_event_loop().run_until_complete(build())
161
+ fake_conn, raw_sock = _make_async_fake_conn()
162
+ fired = self._trigger_hooks(connector.engine, fake_conn)
163
+ self.assertTrue(fired, "connect 监听器未被触发")
164
+ self._assert_keepalive_opts(raw_sock)
165
+
166
+ def test_keepalive_failure_does_not_block(self):
167
+ """setsockopt 抛异常不阻断建连(钩子内部吞掉)。"""
168
+ from sycommon.database.database_service import DatabaseConnector
169
+ with patch.object(DatabaseConnector, "test_connection", return_value=True):
170
+ connector = DatabaseConnector(DB_CONFIG)
171
+ fake_conn, raw_sock = _make_sync_fake_conn()
172
+ raw_sock.setsockopt.side_effect = OSError("perm")
173
+ # 不应抛异常
174
+ self._trigger_hooks(connector.engine, fake_conn)
175
+
176
+
177
+ if __name__ == "__main__":
178
+ unittest.main()
@@ -1,7 +1,7 @@
1
1
  """验证本次依赖升级 + 补丁适配的正确性(纯离线,不依赖 RabbitMQ/真实 LLM)。
2
2
 
3
3
  覆盖本次改动:
4
- A. 依赖版本落地:langchain 1.3.15 / langchain_openai 1.5.1 / langchain_core 1.5.5。
4
+ A. 依赖版本落地:langchain 1.3.18 / langchain_openai 1.6.0 / langchain_core 1.6.1。
5
5
  B. summarization_utils.py 的 ContextOverflow 补丁:在当前版本上成功替换
6
6
  _handle_openai_bad_request / _handle_openai_api_error,且:
7
7
  - MiniMax/vLLM 句式 "maximum context length is ..." 仍被正确转成
@@ -28,12 +28,12 @@ def test_installed_versions():
28
28
  import langchain_openai
29
29
  import langchain_core
30
30
 
31
- assert langchain.__version__ == "1.3.15", (
32
- f"langchain 应为 1.3.15, 实际 {langchain.__version__}")
33
- assert langchain_openai.__version__ == "1.5.1", (
34
- f"langchain_openai 应为 1.5.1, 实际 {langchain_openai.__version__}")
35
- assert langchain_core.__version__ == "1.5.5", (
36
- f"langchain_core 应为 1.5.5, 实际 {langchain_core.__version__}")
31
+ assert langchain.__version__ == "1.3.18", (
32
+ f"langchain 应为 1.3.18, 实际 {langchain.__version__}")
33
+ assert langchain_openai.__version__ == "1.6.0", (
34
+ f"langchain_openai 应为 1.6.0, 实际 {langchain_openai.__version__}")
35
+ assert langchain_core.__version__ == "1.6.1", (
36
+ f"langchain_core 应为 1.6.1, 实际 {langchain_core.__version__}")
37
37
  print(f" [OK] langchain={langchain.__version__} "
38
38
  f"langchain_openai={langchain_openai.__version__} "
39
39
  f"langchain_core={langchain_core.__version__}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.10a2
3
+ Version: 0.2.10a4
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -13,36 +13,36 @@ Requires-Dist: deepagents==0.7.6
13
13
  Requires-Dist: elasticsearch==9.5.0
14
14
  Requires-Dist: fastapi==0.141.1
15
15
  Requires-Dist: jinja2==3.1.6
16
- Requires-Dist: kafka-python==3.0.10
17
- Requires-Dist: langchain==1.3.15
18
- Requires-Dist: langchain-core==1.5.5
19
- Requires-Dist: langchain-openai==1.5.1
20
- Requires-Dist: langfuse==4.14.4
16
+ Requires-Dist: kafka-python==3.0.11
17
+ Requires-Dist: langchain==1.3.18
18
+ Requires-Dist: langchain-core==1.6.1
19
+ Requires-Dist: langchain-openai==1.6.0
20
+ Requires-Dist: langfuse==4.15.1
21
21
  Requires-Dist: langgraph==1.2.11
22
22
  Requires-Dist: langgraph-checkpoint-postgres==3.1.2
23
- Requires-Dist: langgraph-checkpoint-redis==0.5.1
23
+ Requires-Dist: langgraph-checkpoint-redis==0.5.2
24
24
  Requires-Dist: ldap3==2.9.1
25
25
  Requires-Dist: loguru==0.7.3
26
26
  Requires-Dist: mysql-connector-python==26.7.0
27
27
  Requires-Dist: nacos-sdk-python<3.0,==2.0.11
28
28
  Requires-Dist: psutil==7.2.2
29
29
  Requires-Dist: pyxxl==0.4.8
30
- Requires-Dist: pydantic==2.13.4
31
- Requires-Dist: python-dotenv==1.2.2
30
+ Requires-Dist: pydantic==2.13.5
31
+ Requires-Dist: python-dotenv==1.2.3
32
32
  Requires-Dist: python-multipart==0.0.32
33
33
  Requires-Dist: pyyaml==6.0.3
34
34
  Requires-Dist: redis==7.4.0
35
- Requires-Dist: sentry-sdk[fastapi]==2.68.0
35
+ Requires-Dist: sentry-sdk[fastapi]==2.68.1
36
36
  Requires-Dist: sqlalchemy[asyncio]==2.0.52
37
37
  Requires-Dist: starlette[full]==1.6.0
38
- Requires-Dist: tiktoken==0.13.0
39
- Requires-Dist: uvicorn==0.52.3
38
+ Requires-Dist: tiktoken==0.14.0
39
+ Requires-Dist: uvicorn==0.52.4
40
40
  Requires-Dist: wecom-aibot-python-sdk==1.0.2
41
41
  Requires-Dist: twine==7.0.0
42
42
  Requires-Dist: minio==7.2.20
43
43
  Requires-Dist: langchain-mcp-adapters==0.3.2
44
44
  Requires-Dist: fastapi-mcp==0.4.0
45
- Requires-Dist: psycopg[binary,pool]==3.3.4
45
+ Requires-Dist: psycopg[binary,pool]==3.3.5
46
46
  Requires-Dist: agent-client-protocol==0.11.0
47
47
  Requires-Dist: langchain-acp==1.7.0
48
48
  Requires-Dist: pypinyin==0.55.0
@@ -137,8 +137,8 @@ sycommon/services.py,sha256=BalCSLqx0aictqEsyUOUXimjqdMGbZgZyd0MqViaBI0,27315
137
137
  sycommon/agent/__init__.py,sha256=mxceAeUifQ-DKvWp7ZEJIFlmOCb5wpYHPGQw3rwEN8I,4378
138
138
  sycommon/agent/agent_manager.py,sha256=UhhaekEumT7g4v_Z1UB4jTp13X0n8M8erYaQdkGGWkA,13620
139
139
  sycommon/agent/chat_events.py,sha256=t7qWa6OrIWLfqtd1AnqaVP67QWkn1JxpCBTZYQpoLuM,14226
140
- sycommon/agent/deep_agent.py,sha256=Bkur9ybj7cKSqI6ZBPbtVXXNN_hmvOIHlEh5fOHjEVs,87488
141
- sycommon/agent/multi_agent_team.py,sha256=227UgO0XrrPxvQE4xXTNCCYq-5WiUi13r_AUolTpLS4,34719
140
+ sycommon/agent/deep_agent.py,sha256=gBQ2rzQIS53PZhaU50zIsJ3cWMEg_IaBbH_IzycmHGU,87895
141
+ sycommon/agent/multi_agent_team.py,sha256=GRXjjrPblsMnLjY2aB5CeFJ6-F7wvPhUc_JxNjfBXes,34789
142
142
  sycommon/agent/reasoning_content_patch.py,sha256=HZ_If0qVDHlz_b1mDT3tuTqbExhzRVhP4VgsoDt0JLc,3945
143
143
  sycommon/agent/resume_compactor.py,sha256=CJp3AUqeStr8U2JeWP31DyulomXZdrkLL4kVtLxnwn8,20849
144
144
  sycommon/agent/skills_retry_patch.py,sha256=LwlNIQdl0Hz9tywT5foPipULReMlWJyRK5nGibgp948,4049
@@ -154,7 +154,7 @@ sycommon/agent/mcp/models.py,sha256=zda4ho-UTYOJ5oPAZPZ-vxhfDRLghUCsMroHnA2A2QQ,
154
154
  sycommon/agent/mcp/tool_loader.py,sha256=pwhZoSoZuQVYo6BgfdMoRHlGnRPDRxvOvxEgvUM5q7Q,17818
155
155
  sycommon/agent/middleware/model_request_prep.py,sha256=W0dTgdFo6RI0Tg9jdN7jSB9QOroQR5XHIrm2hw5lslk,7618
156
156
  sycommon/agent/middleware/sandbox_path_guard.py,sha256=K1SnxA8pDxkCSucOkranwUvC9_8rd3l_-TIR8gMMTK8,8102
157
- sycommon/agent/middleware/sensitive_guard.py,sha256=Ql4BUwF0rNOcpqB7OOHTaO0-65MTEJ9Qk-sC6p7SIQ4,31834
157
+ sycommon/agent/middleware/sensitive_guard.py,sha256=TN1SEFzUborxx7l24-XvR6TZY0s5G61kTbJnyaB11tE,32350
158
158
  sycommon/agent/middleware/skill_write_guard.py,sha256=ZLW-OIG56z5LvLRMaMnryhPz_O5PbrdTLuHTfC3PveA,3101
159
159
  sycommon/agent/sandbox/__init__.py,sha256=jR7LlkD4J4Y6QYyRXQClkwmqDBCCPmycV_hQV9p9YHw,4621
160
160
  sycommon/agent/sandbox/consistent_hash.py,sha256=8Jgk-W4NAD2-u5_vKRVlPmql_e0Vy4aNORiXOuzvIrs,7277
@@ -187,14 +187,14 @@ sycommon/config/XxlJobConfig.py,sha256=VSG6dn9ysfUVunOs7PqugyZUGJWmX_cEePz2ZCfqH
187
187
  sycommon/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
188
188
  sycommon/config/config_change_notifier.py,sha256=7qQe7vdBS1zohEfVq0d5IaDqYsLdgd2ckF4mKKgh0gE,6848
189
189
  sycommon/database/async_base_db_service.py,sha256=8mPZ2yI1hIYvuHYxcpeU0gus_1WGjrpRqv2bm9TIBCk,6186
190
- sycommon/database/async_database_service.py,sha256=eS7Vje0eqB6xLNXHoHBan-44w9XrSKCjcQCrHSgLtm8,12692
190
+ sycommon/database/async_database_service.py,sha256=Ghop6On0H2H1yZBhP3xt8EKvYjAZ1kjyjDpDP9dhE_g,12985
191
191
  sycommon/database/base_db_service.py,sha256=urdAibaCUt3hxwwKzEMBceWkIwsw3ja_WDJ1QKc8nLU,4116
192
- sycommon/database/database_service.py,sha256=svkg6MBxcar6nWSepBv40mSfpZi5tVecloTDu1O1Tns,6099
192
+ sycommon/database/database_service.py,sha256=mspi98qQnzl7gvaEjGM40lvM_LfjGdQZR1jp5RaFDkc,8974
193
193
  sycommon/database/elasticsearch_service.py,sha256=BJnBcoeSDtK1a1IcCyLbzQaOMb5hCfIh0wgBRQ9XmpU,4257
194
194
  sycommon/database/pg_checkpoint_service.py,sha256=lR73iZTDJDZc7tfx4_D6vjdlJAUTDe49zAOa3QIkBsg,7342
195
- sycommon/database/redis_service.py,sha256=_Sr9RRSsiMh_xPSyxKxjfDkECVf5Lii2yGBzKZRi_hE,21473
195
+ sycommon/database/redis_service.py,sha256=Gz9ovGIeHZdcvY7_DTG_pU1t1OqfV29cS9CvN6A0q5E,22598
196
196
  sycommon/database/resilient_pg_saver.py,sha256=S5hOoF7gGTa0b4-P3dM_iLy1GFesG_jEnIxv-WRwd3s,7148
197
- sycommon/database/token_usage_db_service.py,sha256=wU9jSUyOafLSI_45gbfv7_kOU0nG2bmwOmEQnN2mLpU,7913
197
+ sycommon/database/token_usage_db_service.py,sha256=iYK51ZtGNpBA4nj2IhflltbftE5Fsc3C0UksSJr-phY,10152
198
198
  sycommon/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
199
199
  sycommon/health/health_check.py,sha256=EhfbhspRpQiKJaxdtE-PzpKQO_ucaFKtQxIm16F5Mpk,391
200
200
  sycommon/health/metrics.py,sha256=fHqO73JuhoZkNPR-xIlxieXiTCvttq-kG-tvxag1s1s,268
@@ -202,7 +202,7 @@ sycommon/health/ping.py,sha256=FTlnIKk5y1mPfS1ZGOeT5IM_2udF5aqVLubEtuBp18M,250
202
202
  sycommon/heartbeat_process/__init__.py,sha256=8HPwckgg7UW6dhf_h71BidiZy6Uj5Q2la8OnmqVcPSU,1899
203
203
  sycommon/heartbeat_process/heartbeat_config.py,sha256=qbxxsYYhYNS5jEQ644aYmJB1wJCnhwFn04m92EQ0iSg,5486
204
204
  sycommon/heartbeat_process/heartbeat_process_manager.py,sha256=24qUKs8qegdWHqcoxL0gTaypjNHoSCFDwlbzi5SgkYw,9252
205
- sycommon/heartbeat_process/heartbeat_process_worker.py,sha256=duuAEFwda43Y6pZE8tOOspitlyxecaFsg1n1iH9jQDQ,16863
205
+ sycommon/heartbeat_process/heartbeat_process_worker.py,sha256=18XtfMTYrbOeC9EsZF5oKTUb2vNIoV8JzqiGqemwiv4,17064
206
206
  sycommon/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
207
  sycommon/llm/embedding.py,sha256=n2PtKlfJ4E1-V8DIHTilR-y9y1TYB5iaATlWOKjid28,19878
208
208
  sycommon/llm/get_llm.py,sha256=DK-kZSz6NtQ8lpbEdleLyYXm7qnAJwAItDKUqF6Xxkw,10079
@@ -243,9 +243,9 @@ sycommon/middleware/sandbox_gateway.py,sha256=ZyXV9iwvaLNNTk0oM-RdjGRSgkBPaPp67A
243
243
  sycommon/middleware/timeout.py,sha256=KlxOPa8xl2dg6yuRi_EzkVJG8bX4stb5ueYxctzzGM8,1433
244
244
  sycommon/middleware/token_tracking.py,sha256=rEbgV1bgWMdzAERx4aq5XAvOIT6jTY_tK1P0xHJnL3o,6609
245
245
  sycommon/middleware/tool_result_truncation.py,sha256=xtwBf8q11Z8EPzmo3Ju4OfUbULOklSsWo_oystxCW0o,13640
246
- sycommon/middleware/traceid.py,sha256=LMZB7JykO6dX250rrorowFBpJBL7JljQbZ2Sq3VDHh0,19884
246
+ sycommon/middleware/traceid.py,sha256=NZ2zLtqnd3MfNSGMal2pfnKlc57t-CLIi5j1L1NAaFU,19865
247
247
  sycommon/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
- sycommon/models/base_http.py,sha256=efxtsxmQmInlsRVqQU9P17MSVXx3TsNmgimMlz0hNV0,4788
248
+ sycommon/models/base_http.py,sha256=pLa6IBWZtVy92bKiZ8_Sdo5jUjlojAqhJ1TH4C-nxvQ,6042
249
249
  sycommon/models/log.py,sha256=rZpj6VkDRxK3B6H7XSeWdYZshU8F0Sks8bq1p6pPlDw,500
250
250
  sycommon/models/mcp_server_config.py,sha256=cm_oDEDXVJR4nCKgWPli5zFI0DUy6xtvRs-___EWAW8,1768
251
251
  sycommon/models/mqlistener_config.py,sha256=ZFGg_vc9HcIV22L5Jgrynkjj5B6Ci8ETQNUbNUiXrVw,1830
@@ -257,7 +257,7 @@ sycommon/models/token_usage.py,sha256=w3Aash7jUVSUIgKxBUzI6SqJ9VV5Pk7rLBbMUlLv6n
257
257
  sycommon/models/token_usage_mysql.py,sha256=eWnCvI2FDwyTA_fr4wWQtAmUnFJgLwrt0WXMXHLAESQ,2633
258
258
  sycommon/models/xxljob_handler_config.py,sha256=XiSv9AlJ8UsLRai4hvTMeiSZ6VsO5Ijx8HcDwLxJL64,376
259
259
  sycommon/notice/__init__.py,sha256=cVYbKlLLMFHckyDpx21CTZCMTufqZk_uHuEIxUNxwx4,104
260
- sycommon/notice/uvicorn_monitor.py,sha256=_SlLB91N7g-Jt_xP_AX1IWFgA7_1lCqsQFwm_Y6QjFk,11756
260
+ sycommon/notice/uvicorn_monitor.py,sha256=68VC2d6JvlVsH-Ev87JS-TIbCBGhIjneg3Uxx9LMkOg,12308
261
261
  sycommon/notice/wecom_message.py,sha256=_oA6hg0Ask7NkFbvUpL78WOmyUb6uW7KcY71GhNKiJY,12363
262
262
  sycommon/rabbitmq/process_pool_consumer.py,sha256=EXdNypgathjTfPqkccoAW0HiVB9HuZLGhPbWUcYh0_Q,29750
263
263
  sycommon/rabbitmq/rabbit_management_client.py,sha256=cf7GSbvdzOA6vFV4jC3IL6wh8yu8IEDgTPHUqRV2_uE,4021
@@ -279,7 +279,7 @@ sycommon/synacos/example.py,sha256=FOnBkvodR8WF_jf-RovM3ngVmvZQX6wKwMLscUTGn2M,8
279
279
  sycommon/synacos/example2.py,sha256=yYuQscfHUIl1HLZ8kSRBuZpHUcNWZMi5H3Mb-LjYnvk,8136
280
280
  sycommon/synacos/feign.py,sha256=RU6p2gRP3LZoHYBBEUUY9z5KKzGiUmwj4qdo7aF9UNA,12610
281
281
  sycommon/synacos/feign_client.py,sha256=i6O20JWl1g2fjD-et7olFaO-0z2yEbKT8-pKluAHCgs,19917
282
- sycommon/synacos/nacos_client_base.py,sha256=iP5kLkBD2VOrx6X8v6_RnC9NWiBWmL6-Bgf493QOtxc,6899
282
+ sycommon/synacos/nacos_client_base.py,sha256=dBKtGG1g-in8cAILHUVxPmfZZ3fNEuYMDFAHBgTYfJM,7337
283
283
  sycommon/synacos/nacos_config_manager.py,sha256=LdmMwyQcc09K0fNWCuSqobyV-wdSW_qVZXb-TEASgxY,8330
284
284
  sycommon/synacos/nacos_heartbeat_manager.py,sha256=LfimUKpG4KaqsVQl150sg3MLK8psanuUwQ07tjL3uBE,10963
285
285
  sycommon/synacos/nacos_service.py,sha256=nmL007c2SBMHaVJXlzBHV2_R8whfl_X_rmgCpjRDqZw,12319
@@ -295,6 +295,7 @@ sycommon/tests/test_consumer_loss_rootcause.py,sha256=rP_XdbGbzOM9RB36vk0gU4kqip
295
295
  sycommon/tests/test_consumer_recovery.py,sha256=pMMtOwHIijNTZxg3IVBYSGFYgezMX0PUeylks8xzMM8,10364
296
296
  sycommon/tests/test_consumer_recovery_fix.py,sha256=q2OW0-KEsTqHpelNIDr15G8p-ZOC49rXGxq2wqBmFZM,12857
297
297
  sycommon/tests/test_consumer_tags_sync.py,sha256=E9BZs9WGUIE5ygHsGzLlaohG2qlAVnkpOcjaYW9WHkg,5702
298
+ sycommon/tests/test_db_keepalive.py,sha256=xp2MSwMpVFjq4gdT0UCFptUKau95xiwJHUXzXGs3YBM,7576
298
299
  sycommon/tests/test_deep_agent.py,sha256=YDbDdMbTjUjLjgGjT1kPwyeMRQ4fqjliJd71dJ0rOPA,5918
299
300
  sycommon/tests/test_dynamic_max_tokens.py,sha256=mk2QXZIxpyClEBvS9g88NXr9xmXoNdVrIjFAsN5t6CQ,17934
300
301
  sycommon/tests/test_email.py,sha256=-oPtYVGQzJ3Cv-op3ZNRMeyYOF-UNidGJC35CHRgjGQ,5442
@@ -322,7 +323,7 @@ sycommon/tests/test_summarization_real.py,sha256=iTwwA_xQd5Zgkn849lu2M0zIHPnMp-3
322
323
  sycommon/tests/test_summary_leak_e2e.py,sha256=vE5r9QCcZRmUy3YQASwdwCBsVrRcl8vU95sLUyB6u9s,34739
323
324
  sycommon/tests/test_upgrade_aio_pika.py,sha256=HIvy0Gs-aNNGX6Q3Y2fwSq2nDODbsWt7kl7-bk3gTk0,6115
324
325
  sycommon/tests/test_upgrade_langgraph_stream.py,sha256=1FEVuv8FPNeLOB6yht6NoQifHoiCtkrtBI4pB_PQYTE,5443
325
- sycommon/tests/test_upgrade_verification.py,sha256=mLBKM7bVSp-Vgdhx1VqzSFHnjYN1ao_oOpUugwTK8r0,7589
326
+ sycommon/tests/test_upgrade_verification.py,sha256=HS0KQ1q-Djolf1Km2xMFNKtoUHKLNLjacTHv_Wh5O8Y,7589
326
327
  sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
327
328
  sycommon/tools/async_utils.py,sha256=e2Bp8v0LDrnViBIZ3zcCWtm_01w67vG34VhnAyGu9ZM,1015
328
329
  sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
@@ -335,8 +336,8 @@ sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
335
336
  sycommon/tools/user_id.py,sha256=o-zsubGSR5rSSZf8Pk8B7AhE21ahDTTIW5JZmJO6Flw,4761
336
337
  sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
337
338
  sycommon/xxljob/xxljob_service.py,sha256=1yifwIBNGsCIxLnQjHKiBlbsigc_zvPH-dMTZcNxe-Q,7649
338
- sycommon_python_lib-0.2.10a2.dist-info/METADATA,sha256=gM2vnr9a4QSKvGHKPwTF8h4Of6bbnOhO-f34BC91N5Q,8033
339
- sycommon_python_lib-0.2.10a2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
340
- sycommon_python_lib-0.2.10a2.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
341
- sycommon_python_lib-0.2.10a2.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
342
- sycommon_python_lib-0.2.10a2.dist-info/RECORD,,
339
+ sycommon_python_lib-0.2.10a4.dist-info/METADATA,sha256=LEDBGdzj5bGZDBhJBAJ0w7v0IeSMsw9MQJsoxrXYCKM,8033
340
+ sycommon_python_lib-0.2.10a4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
341
+ sycommon_python_lib-0.2.10a4.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
342
+ sycommon_python_lib-0.2.10a4.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
343
+ sycommon_python_lib-0.2.10a4.dist-info/RECORD,,