sycommon-python-lib 0.2.10__py3-none-any.whl → 0.2.10a3__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.
@@ -65,6 +65,7 @@ from deepagents.middleware.summarization import create_summarization_tool_middle
65
65
  from sycommon.agent.summarization_utils import build_summarization_middleware
66
66
  from sycommon.agent.resume_compactor import maybe_compact_before_resume
67
67
  import sycommon.agent.reasoning_content_patch # noqa: F401 提取 reasoning_content/reasoning 字段,避免思考型模型被判空响应
68
+ import sycommon.agent.skills_retry_patch # noqa: F401 技能列表加载失败不永久缓存空结果(沙箱 ls 500 时整会话丢技能)
68
69
 
69
70
  # 流式逐 token 诊断日志开关(默认关)。开启后每个 chunk 都打印
70
71
  # chunk_type/content 等(仅供排障)。LLM 最终结果已由 LLMLogger.on_llm_end
@@ -179,6 +180,10 @@ class AgentConfig(BaseModel):
179
180
  sensitive_guard: bool = True # 是否启用敏感信息检测守卫
180
181
  # 敏感降级目标内部模型名(由应用层透传,避免基础库硬编码应用常量)
181
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
182
187
  # 敏感检测提示词(应用层可覆盖);未传(None)用基础库 DEFAULT_DETECT_PROMPT
183
188
  sensitive_detect_prompt: Optional[str] = None
184
189
  # 命中敏感后的动作:"allow"=不检测放行;"downgrade"=降级内部模型继续;
@@ -1245,6 +1250,7 @@ async def create_deep_agent(
1245
1250
  model_tier=config.model_tier,
1246
1251
  enabled=config.sensitive_guard,
1247
1252
  internal_model_name=config.internal_model_name,
1253
+ detector_model_name=config.sensitive_detector_model_name,
1248
1254
  detect_prompt=config.sensitive_detect_prompt,
1249
1255
  action=config.sensitive_action,
1250
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,
@@ -0,0 +1,93 @@
1
+ # -*- coding: utf-8 -*-
2
+ """deepagents SkillsMiddleware 的重试补丁。
3
+
4
+ 修复「会话首轮沙箱 ls 失败 → skills_metadata=[] 被当已加载缓存进
5
+ checkpoint → 整个会话从此看不到技能」的问题。
6
+
7
+ 背景(2026-09-01 线上案例):
8
+ - deepagents SkillsMiddleware.abefore_agent 每会话只加载一次技能:
9
+ `if "skills_metadata" in state: return None` —— 空列表也算"已加载"。
10
+ - 会话首条消息若撞上沙箱 /v1/sandbox/ls 500(发版恢复期/沙箱故障转移),
11
+ middleware 写入 skills_metadata=[] + skills_load_errors,并随 checkpoint
12
+ 持久化。后续每一轮命中"in state"直接跳过,永不重试。
13
+ - 用户视角:该会话从此没有技能,直到新建会话。
14
+
15
+ 修复语义:skills_metadata 为空**且**存在加载错误(或本应有 system 技能源)
16
+ → 视为未加载成功,清除旧值后重新加载。真正的零技能(所有源都正常列空)
17
+ 不重试,避免每轮白打一次 ls。
18
+
19
+ 打补丁方式:替换 deepagents.graph 模块里的 SkillsMiddleware 符号(graph
20
+ 在 create_deep_agent 内部 `SkillsMiddleware(backend=..., sources=...)`
21
+ 引用的是模块全局名),子类化原类、只重载 abefore_agent,其余行为不动。
22
+ 在 sycommon.agent.deep_agent 导入本模块即生效(幂等,可重复导入)。
23
+ """
24
+ import logging
25
+
26
+ from deepagents.middleware import skills as _skills_module
27
+
28
+ logger = logging.getLogger("sycommon")
29
+
30
+ _ORIG_MIDDLEWARE = _skills_module.SkillsMiddleware
31
+ _PATCHED = getattr(_ORIG_MIDDLEWARE, "_sycommon_retry_patch", False)
32
+
33
+
34
+ class RetrySkillsMiddleware(_ORIG_MIDDLEWARE):
35
+ """加载失败(空列表+错误)不缓存的 SkillsMiddleware。
36
+
37
+ 上游缓存条件 `if "skills_metadata" in state: return None` 的漏洞:
38
+ 空列表也是合法 dict key 存在,首轮 ls 失败写进去后永不再加载。
39
+ 这里放宽为「空列表且带加载错误 → 重试」,其余行为与上游一致。
40
+ """
41
+
42
+ async def abefore_agent(self, state, runtime, config):
43
+ # 未加载过 → 走上游逻辑(会 fall through 到正常加载)
44
+ if "skills_metadata" not in state:
45
+ return await super().abefore_agent(state, runtime, config)
46
+
47
+ cached = state.get("skills_metadata") or []
48
+ cached_errors = state.get("skills_load_errors") or []
49
+
50
+ if cached:
51
+ # 已有技能列表(哪怕部分源失败) → 尊重上游缓存语义,不重试
52
+ return None
53
+
54
+ # 空列表:两种可能——
55
+ # a) 曾加载失败(ls 500 等)带 errors → 需要重试
56
+ # b) 所有源正常但确实零技能(无 errors) → 不重试
57
+ if not cached_errors:
58
+ return None
59
+
60
+ logger.warning(
61
+ "[SkillsRetry] 检测到缓存为空的技能列表(加载曾失败),"
62
+ "本会话轮次重新加载 skills: %s", self.sources)
63
+ # 清掉旧值让上游 super() 不再命中 "skills_metadata" in state
64
+ # (dict 可变,删 key 即可;state 是 langgraph 传入的 dict 视图)
65
+ try:
66
+ state.pop("skills_metadata", None)
67
+ state.pop("skills_load_errors", None)
68
+ except Exception:
69
+ # state 不可变视图等极端情况:构造副本调 super 不可行,
70
+ # 退化为直接调用内部加载逻辑(与上游 for-source 循环等价)
71
+ pass
72
+ return await super().abefore_agent(state, runtime, config)
73
+
74
+
75
+ def _apply() -> None:
76
+ """把 deepagents.graph 引用的 SkillsMiddleware 换成重试版(幂等)。"""
77
+ if _PATCHED:
78
+ return
79
+ import deepagents.graph as _graph
80
+
81
+ if getattr(_graph.SkillsMiddleware, "_sycommon_retry_patch", False):
82
+ # 已打过(例如热重载场景)——只补标记
83
+ _graph.SkillsMiddleware = RetrySkillsMiddleware
84
+ return
85
+
86
+ RetrySkillsMiddleware._sycommon_retry_patch = True
87
+ _graph.SkillsMiddleware = RetrySkillsMiddleware
88
+ logger.info(
89
+ "[SkillsRetry] SkillsMiddleware 已启用空列表重试补丁"
90
+ "(加载失败不再永久缓存空技能列表)")
91
+
92
+
93
+ _apply()
@@ -41,6 +41,21 @@ def _filtered_headers(raw_headers) -> list:
41
41
  return []
42
42
 
43
43
 
44
+ def _omit_base64_content(obj: Any) -> None:
45
+ """把结构化响应体里超长的 content 值(PDF/图片 base64)替换为省略占位。
46
+
47
+ 就地修改,dict/list 逐层递归。sanitize_deep_str 已把 JSON 字符串还原成
48
+ 结构化 dict/list——不能再 json.loads(传容器进去会 TypeError)。
49
+ """
50
+ if isinstance(obj, dict):
51
+ c = obj.get("content")
52
+ if c and len(str(c)) > 1000:
53
+ obj["content"] = f"... (base64/binary content omitted, {len(str(c))} chars)"
54
+ elif isinstance(obj, list):
55
+ for item in obj:
56
+ _omit_base64_content(item)
57
+
58
+
44
59
  def setup_trace_id_handler(app):
45
60
  @app.middleware("http")
46
61
  async def trace_id_and_log_middleware(request: Request, call_next):
@@ -324,11 +339,11 @@ def setup_trace_id_handler(app):
324
339
  response_log_body = _log_masker.sanitize_deep_str(
325
340
  response_body.decode('utf-8', errors='ignore'))
326
341
  # 检测是否包含 base64 二进制内容(如 PDF、图片等),如有则省略。
327
- # sanitize_deep_str 已把 JSON 字符串还原成结构化 dict——直接在结构上
328
- # 改(不要再 json.loads 一个 dict,会 TypeError);仅字符串回退路径走解析。
329
- if isinstance(response_log_body, dict):
330
- if response_log_body.get("content") and len(str(response_log_body["content"])) > 1000:
331
- response_log_body["content"] = f"... (base64/binary content omitted, {len(str(response_log_body['content']))} chars)"
342
+ # sanitize_deep_str 已把 JSON 字符串还原成结构化 dict/list——直接在
343
+ # 结构上改(不要再 json.loads 一个 dict/list,会 TypeError);仅字符串
344
+ # 回退路径(非 JSON 响应)走解析。
345
+ if isinstance(response_log_body, (dict, list)):
346
+ _omit_base64_content(response_log_body)
332
347
  else:
333
348
  try:
334
349
  resp_json = json.loads(response_log_body)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.10
3
+ Version: 0.2.10a3
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -137,10 +137,11 @@ sycommon/services.py,sha256=d1HAavs5HWmiSKVjlObu8ApcV-ag9yYCQr87qY10EcE,27919
137
137
  sycommon/agent/__init__.py,sha256=P5ZQ6R9dpqCjl0HeviWuQo2KDbXGOd9xPXXZHWDmMnQ,4554
138
138
  sycommon/agent/agent_manager.py,sha256=-5SzPHYeo8v3Tt6yDA53W1UZ6HqTSc157-rmJjHlrMk,14065
139
139
  sycommon/agent/chat_events.py,sha256=V5W21cLwcSm3X2lejUxOUQY_e4h_-30_mkDagxA4JD4,14709
140
- sycommon/agent/deep_agent.py,sha256=NKDgVahoKfxrbCo_tLFG_urWnjRUGUz5gouC0zp3WD0,89058
141
- sycommon/agent/multi_agent_team.py,sha256=r_CZW-yQNqLQwKiHadPI0-p6eCRw1JB-RPn33oU-WzM,35565
140
+ sycommon/agent/deep_agent.py,sha256=IVMCeSsW36FEhxG4NdorMFMvqJJ7U7axgMFGWhRfz84,89613
141
+ sycommon/agent/multi_agent_team.py,sha256=ga6iUb3cQxPNLsLyczldHZ8uthmHSYN8Bie9HgfV6kE,35636
142
142
  sycommon/agent/reasoning_content_patch.py,sha256=AQ3lKjV7zLrKlWCCIyH1-VehUmcpVpzXdwXBVR9idzs,4033
143
143
  sycommon/agent/resume_compactor.py,sha256=4L27VcVpE8UrCrmR-RRJ75ixKT0U62TG62hQuaC5EMw,21249
144
+ sycommon/agent/skills_retry_patch.py,sha256=LwlNIQdl0Hz9tywT5foPipULReMlWJyRK5nGibgp948,4049
144
145
  sycommon/agent/summarization_utils.py,sha256=-jQJNKZrb0np_L66qiaXWdXJC98Y4CUn9q8nzz8srIE,19960
145
146
  sycommon/agent/acp/__init__.py,sha256=_5QYNAgO_6b0xQXzK8ICRegODEPDQ_AhHC6XCderljg,1908
146
147
  sycommon/agent/acp/client.py,sha256=XEOoUsvVI67k0Lw0ZF6T-0Qut-vh5myVfCYQd8RJBwg,27787
@@ -153,7 +154,7 @@ sycommon/agent/mcp/models.py,sha256=ta9unJD0fPM3Ql10VMusAHchJLumd-0GmyVG5JEsuj0,
153
154
  sycommon/agent/mcp/tool_loader.py,sha256=0gmpAoSgk6KI9NAsIHxta4DtThpFLUi6G4PHepy1w1M,18263
154
155
  sycommon/agent/middleware/model_request_prep.py,sha256=jK6A5Isfxb9Yxf1XFmjDvoYswHiygbLjEBicDPYmplY,7772
155
156
  sycommon/agent/middleware/sandbox_path_guard.py,sha256=238q_pI4ZScpuWQ6XOjx-nzEe40TrUy9Lp_VmETJ3xQ,8268
156
- sycommon/agent/middleware/sensitive_guard.py,sha256=R1IE9FIDBRA8i04iwBDmlX9qRaPSnMO-51Di3wQRH7Y,32425
157
+ sycommon/agent/middleware/sensitive_guard.py,sha256=TzQN1keNvlxfvsUPKyQiftPBdV2B-22M5UeX6w45IkY,32947
157
158
  sycommon/agent/middleware/skill_write_guard.py,sha256=6LX6lpCa2ZVEzJ2AjfRXR3juJL2A9oQfE7shfUPqsQE,3172
158
159
  sycommon/agent/sandbox/__init__.py,sha256=N6Mcg6Lyvk9dzZYD7Vh2yahStg3hYgtgCBb3ja9HCng,4724
159
160
  sycommon/agent/sandbox/consistent_hash.py,sha256=ou6cS0f6Z79VGFE5Bk9PaiW2YsAKDVnG1P9pk9UmE4c,7452
@@ -242,7 +243,7 @@ sycommon/middleware/sandbox_gateway.py,sha256=K4Z0Mw2x76-xfD3pI1085ik-IbrOKHH2BZ
242
243
  sycommon/middleware/timeout.py,sha256=lgbyCkrk_fMjl76LvKktBm4g90SOLQcBKCaMCMoS3pE,1477
243
244
  sycommon/middleware/token_tracking.py,sha256=mQqfthsL2iyI4skO5S3TPl4oHPF6c80UYjKJz_2cqCE,6782
244
245
  sycommon/middleware/tool_result_truncation.py,sha256=JHEmdkYfaA1WvWFxijCPYr-bKmYhwtlLAHSrPNG_ijc,13994
245
- sycommon/middleware/traceid.py,sha256=4vJztLFGhy-WPIVuCmh2nKD1aCLhMVrom3mmt0sSq_g,19788
246
+ sycommon/middleware/traceid.py,sha256=Cm3qpyupIyP40DLMSPqQf_cEH4rUsXBuUoj5EO-XlCw,20286
246
247
  sycommon/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
247
248
  sycommon/models/base_http.py,sha256=FjJCRU6Po2OYu-8WVXKPbi9OM2vLFSrMKCk5DfV5hP0,4941
248
249
  sycommon/models/log.py,sha256=NqBHue6-_dn97_8d5cKYzYzbI2G2RaMUE4ru-3HAd2s,530
@@ -334,8 +335,8 @@ sycommon/tools/timing.py,sha256=IVJVqQdCFcm2timqSvKu9Ht372i8pF3K7b2JbtkcBWg,2137
334
335
  sycommon/tools/user_id.py,sha256=CfQ9bVRbtCYRHkizo-efx42DE600vNGAblXZJkJT7Nk,4867
335
336
  sycommon/xxljob/__init__.py,sha256=1VrCsCpzEcU9JLEjpyrbzfL0g0Wu8pIFgkJZJMQjmuw,89
336
337
  sycommon/xxljob/xxljob_service.py,sha256=cWLa-mrm7rJHigHrpj-xrSudDwBCtEfssv2pEntBxqA,7865
337
- sycommon_python_lib-0.2.10.dist-info/METADATA,sha256=Ov7z0kGghbJWncuTz3PZ9ri2b-H3HoBzsjltmnOs3lE,8187
338
- sycommon_python_lib-0.2.10.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
339
- sycommon_python_lib-0.2.10.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
340
- sycommon_python_lib-0.2.10.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
341
- sycommon_python_lib-0.2.10.dist-info/RECORD,,
338
+ sycommon_python_lib-0.2.10a3.dist-info/METADATA,sha256=jZ7yKV5_Lsfr8zwAese9wSPZiUHiv535bPGH3RGm5TI,8189
339
+ sycommon_python_lib-0.2.10a3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
340
+ sycommon_python_lib-0.2.10a3.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
341
+ sycommon_python_lib-0.2.10a3.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
342
+ sycommon_python_lib-0.2.10a3.dist-info/RECORD,,