sycommon-python-lib 0.2.3a11__py3-none-any.whl → 0.2.4__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/agent/__init__.py +26 -0
- sycommon/agent/deep_agent.py +21 -2
- sycommon/agent/mcp/__init__.py +30 -0
- sycommon/agent/mcp/models.py +56 -0
- sycommon/agent/mcp/tool_loader.py +174 -0
- sycommon/agent/multi_agent_team.py +6 -2
- sycommon/agent/sandbox/http_sandbox_backend.py +27 -3
- sycommon/agent/sandbox/minio_sync.py +26 -13
- sycommon/agent/sandbox/sandbox_recovery.py +3 -2
- sycommon/agent/summarization_utils.py +72 -29
- sycommon/config/PgConfig.py +54 -0
- sycommon/database/async_database_service.py +15 -0
- sycommon/database/pg_checkpoint_service.py +132 -0
- sycommon/llm/get_llm.py +4 -0
- sycommon/services.py +18 -2
- sycommon/synacos/feign.py +5 -2
- sycommon/synacos/feign_client.py +7 -4
- {sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/METADATA +9 -6
- {sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/RECORD +22 -17
- {sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/top_level.txt +0 -0
sycommon/agent/__init__.py
CHANGED
|
@@ -105,6 +105,18 @@ from sycommon.agent.chat_events import (
|
|
|
105
105
|
error_event,
|
|
106
106
|
cancelled_event,
|
|
107
107
|
)
|
|
108
|
+
from sycommon.agent.mcp import (
|
|
109
|
+
MCPToolStatus,
|
|
110
|
+
MCPServerConfig,
|
|
111
|
+
MCPServerCreateRequest,
|
|
112
|
+
MCPServerUpdateRequest,
|
|
113
|
+
MCPServerTestRequest,
|
|
114
|
+
MCPServerTestResult,
|
|
115
|
+
load_mcp_tools,
|
|
116
|
+
test_mcp_connection,
|
|
117
|
+
sanitize_name,
|
|
118
|
+
)
|
|
119
|
+
from sycommon.database.pg_checkpoint_service import PgCheckpointService
|
|
108
120
|
|
|
109
121
|
__all__ = [
|
|
110
122
|
# 沙箱
|
|
@@ -147,4 +159,18 @@ __all__ = [
|
|
|
147
159
|
"done_event",
|
|
148
160
|
"error_event",
|
|
149
161
|
"cancelled_event",
|
|
162
|
+
|
|
163
|
+
# MCP 工具集成
|
|
164
|
+
"MCPToolStatus",
|
|
165
|
+
"MCPServerConfig",
|
|
166
|
+
"MCPServerCreateRequest",
|
|
167
|
+
"MCPServerUpdateRequest",
|
|
168
|
+
"MCPServerTestRequest",
|
|
169
|
+
"MCPServerTestResult",
|
|
170
|
+
"load_mcp_tools",
|
|
171
|
+
"test_mcp_connection",
|
|
172
|
+
"sanitize_name",
|
|
173
|
+
|
|
174
|
+
# PG Checkpoint 服务
|
|
175
|
+
"PgCheckpointService",
|
|
150
176
|
]
|
sycommon/agent/deep_agent.py
CHANGED
|
@@ -168,6 +168,7 @@ class DeepAgent:
|
|
|
168
168
|
"""
|
|
169
169
|
current_tool_calls = []
|
|
170
170
|
ai_chunk_buffer = ""
|
|
171
|
+
ai_text_content = ""
|
|
171
172
|
seen_tool_call_ids = set()
|
|
172
173
|
stream_step = 0
|
|
173
174
|
# 兜底:累积流式 chunk 中的 usage_metadata(middleware 在流式场景可能拿不到)
|
|
@@ -211,6 +212,12 @@ class DeepAgent:
|
|
|
211
212
|
if usage_meta:
|
|
212
213
|
total_input_tokens += usage_meta.get("input_tokens", 0)
|
|
213
214
|
total_output_tokens += usage_meta.get("output_tokens", 0)
|
|
215
|
+
if usage_meta.get("input_tokens", 0) > 0:
|
|
216
|
+
SYLogger.debug(
|
|
217
|
+
f"[DeepAgent] usage_metadata | input={usage_meta.get('input_tokens', 0)} "
|
|
218
|
+
f"output={usage_meta.get('output_tokens', 0)} "
|
|
219
|
+
f"total={usage_meta.get('total_tokens', 0)} "
|
|
220
|
+
f"cumulative_input={total_input_tokens} step={stream_step}")
|
|
214
221
|
|
|
215
222
|
if msg_type == "AIMessageChunk":
|
|
216
223
|
tool_calls_log = getattr(msg, "tool_calls", [])
|
|
@@ -397,6 +404,7 @@ class DeepAgent:
|
|
|
397
404
|
|
|
398
405
|
if content:
|
|
399
406
|
ai_chunk_buffer += content
|
|
407
|
+
ai_text_content += content
|
|
400
408
|
event = ChatEventBuilder.ai_chunk(
|
|
401
409
|
content, id=getattr(msg, "id", None),
|
|
402
410
|
agent=DEFAULT_AGENT_NAME)
|
|
@@ -453,6 +461,13 @@ class DeepAgent:
|
|
|
453
461
|
print(
|
|
454
462
|
f"[DeepAgent] AI chunk done | {repr(ai_chunk_buffer[:100])}...")
|
|
455
463
|
|
|
464
|
+
# 空响应检测:模型被调用但没有产出任何文本
|
|
465
|
+
if not ai_text_content and not ai_chunk_buffer:
|
|
466
|
+
SYLogger.warning(
|
|
467
|
+
f"[DeepAgent] 空响应警告:模型未返回任何文本内容。"
|
|
468
|
+
f"stream_step={stream_step}, tool_calls={len(current_tool_calls)}, "
|
|
469
|
+
f"input_tokens={total_input_tokens}, output_tokens={total_output_tokens}")
|
|
470
|
+
|
|
456
471
|
# 兜底:如果 middleware 没有成功记录(流式场景),在这里补充记录
|
|
457
472
|
if total_input_tokens > 0 or total_output_tokens > 0:
|
|
458
473
|
try:
|
|
@@ -598,8 +613,12 @@ async def create_deep_agent(
|
|
|
598
613
|
|
|
599
614
|
# 创建 checkpointer
|
|
600
615
|
if checkpointer is None:
|
|
601
|
-
from
|
|
602
|
-
|
|
616
|
+
from sycommon.database.pg_checkpoint_service import PgCheckpointService
|
|
617
|
+
if PgCheckpointService.is_initialized():
|
|
618
|
+
checkpointer = await PgCheckpointService.get_checkpointer()
|
|
619
|
+
else:
|
|
620
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
621
|
+
checkpointer = MemorySaver()
|
|
603
622
|
|
|
604
623
|
tid = thread_id or user_id
|
|
605
624
|
agent_config = {"configurable": {"thread_id": tid}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""MCP 工具集成模块
|
|
2
|
+
|
|
3
|
+
提供 MCP 服务器连接、工具发现和加载功能。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from sycommon.agent.mcp.models import (
|
|
7
|
+
MCPToolStatus,
|
|
8
|
+
MCPServerConfig,
|
|
9
|
+
MCPServerCreateRequest,
|
|
10
|
+
MCPServerUpdateRequest,
|
|
11
|
+
MCPServerTestRequest,
|
|
12
|
+
MCPServerTestResult,
|
|
13
|
+
)
|
|
14
|
+
from sycommon.agent.mcp.tool_loader import (
|
|
15
|
+
load_mcp_tools,
|
|
16
|
+
test_mcp_connection,
|
|
17
|
+
sanitize_name,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"MCPToolStatus",
|
|
22
|
+
"MCPServerConfig",
|
|
23
|
+
"MCPServerCreateRequest",
|
|
24
|
+
"MCPServerUpdateRequest",
|
|
25
|
+
"MCPServerTestRequest",
|
|
26
|
+
"MCPServerTestResult",
|
|
27
|
+
"load_mcp_tools",
|
|
28
|
+
"test_mcp_connection",
|
|
29
|
+
"sanitize_name",
|
|
30
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""MCP 服务器配置数据模型"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MCPToolStatus(BaseModel):
|
|
8
|
+
"""MCP 工具可用状态"""
|
|
9
|
+
tool_name: str
|
|
10
|
+
description: Optional[str] = None
|
|
11
|
+
available: bool = True
|
|
12
|
+
last_check_at: Optional[str] = None
|
|
13
|
+
last_error: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MCPServerConfig(BaseModel):
|
|
17
|
+
id: str
|
|
18
|
+
user_id: Optional[str] = None
|
|
19
|
+
name: str
|
|
20
|
+
server_url: str
|
|
21
|
+
description: Optional[str] = None
|
|
22
|
+
headers: Optional[dict] = None
|
|
23
|
+
enabled: bool = True
|
|
24
|
+
sanitized_name: str = ""
|
|
25
|
+
created_at: str
|
|
26
|
+
updated_at: str
|
|
27
|
+
tools: Optional[List[MCPToolStatus]] = None
|
|
28
|
+
server_status: Optional[str] = None
|
|
29
|
+
server_error: Optional[str] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MCPServerCreateRequest(BaseModel):
|
|
33
|
+
name: str
|
|
34
|
+
server_url: str
|
|
35
|
+
description: Optional[str] = None
|
|
36
|
+
headers: Optional[dict] = None
|
|
37
|
+
enabled: bool = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MCPServerUpdateRequest(BaseModel):
|
|
41
|
+
name: Optional[str] = None
|
|
42
|
+
server_url: Optional[str] = None
|
|
43
|
+
description: Optional[str] = None
|
|
44
|
+
headers: Optional[dict] = None
|
|
45
|
+
enabled: Optional[bool] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MCPServerTestRequest(BaseModel):
|
|
49
|
+
server_url: str
|
|
50
|
+
headers: Optional[dict] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MCPServerTestResult(BaseModel):
|
|
54
|
+
success: bool
|
|
55
|
+
tools: Optional[list] = None
|
|
56
|
+
error: Optional[str] = None
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""MCP 工具加载器
|
|
2
|
+
|
|
3
|
+
使用 langchain-mcp-adapters 官方库连接远程 MCP 服务器,
|
|
4
|
+
发现工具并作为 LangChain BaseTool 注入 Agent。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import hashlib
|
|
9
|
+
from typing import List, Optional, Callable, Awaitable
|
|
10
|
+
|
|
11
|
+
from langchain_core.tools import BaseTool
|
|
12
|
+
|
|
13
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
14
|
+
from sycommon.agent.mcp.models import MCPServerConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sanitize_name(name: str) -> str:
|
|
18
|
+
"""将名称转为合法的标识符,中文等非ASCII字符做 transliterate"""
|
|
19
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', name).strip('_')
|
|
20
|
+
if not sanitized:
|
|
21
|
+
h = hashlib.md5(name.encode()).hexdigest()[:8]
|
|
22
|
+
sanitized = f"mcp_{h}"
|
|
23
|
+
return sanitized
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def wrap_tool_with_error_handler(
|
|
27
|
+
tool: BaseTool,
|
|
28
|
+
server_config_id: str,
|
|
29
|
+
server_name: str,
|
|
30
|
+
original_tool_name: str,
|
|
31
|
+
on_tool_success: Optional[Callable[[str, str], Awaitable[None]]] = None,
|
|
32
|
+
on_tool_error: Optional[Callable[[str, str, str], Awaitable[None]]] = None,
|
|
33
|
+
) -> BaseTool:
|
|
34
|
+
"""包装 MCP 工具的 coroutine,捕获连接/超时等异常,返回友好的错误信息而非抛出异常。
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
on_tool_success: 异步回调 (server_config_id, tool_name) -> None
|
|
38
|
+
on_tool_error: 异步回调 (server_config_id, tool_name, error_msg) -> None
|
|
39
|
+
"""
|
|
40
|
+
original_coroutine = tool.coroutine
|
|
41
|
+
tool_name = tool.name
|
|
42
|
+
|
|
43
|
+
async def _safe_coroutine(*args, **kwargs):
|
|
44
|
+
try:
|
|
45
|
+
result = await original_coroutine(*args, **kwargs)
|
|
46
|
+
if on_tool_success:
|
|
47
|
+
try:
|
|
48
|
+
await on_tool_success(server_config_id, original_tool_name)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return result
|
|
52
|
+
except Exception as e:
|
|
53
|
+
err_type = type(e).__name__
|
|
54
|
+
err_msg = str(e)[:500]
|
|
55
|
+
SYLogger.warning(f"[MCP] 工具 '{tool_name}' 调用失败 ({err_type}): {err_msg}")
|
|
56
|
+
|
|
57
|
+
if on_tool_error:
|
|
58
|
+
try:
|
|
59
|
+
await on_tool_error(server_config_id, original_tool_name, f"{err_type}: {err_msg}")
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
friendly_msg = (
|
|
64
|
+
f"MCP 工具调用失败:工具 '{tool_name}' (服务器: {server_name}) 当前不可用。\n"
|
|
65
|
+
f"错误类型: {err_type}\n"
|
|
66
|
+
f"可能原因: MCP 服务器未启动、网络不可达或连接超时。\n"
|
|
67
|
+
f"请尝试不使用该工具继续完成任务,或联系管理员检查 MCP 服务 '{server_name}'。"
|
|
68
|
+
)
|
|
69
|
+
return [friendly_msg]
|
|
70
|
+
|
|
71
|
+
tool.coroutine = _safe_coroutine
|
|
72
|
+
return tool
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def load_mcp_tools(
|
|
76
|
+
configs: List[MCPServerConfig],
|
|
77
|
+
on_tool_success: Optional[Callable[[str, str], Awaitable[None]]] = None,
|
|
78
|
+
on_tool_error: Optional[Callable[[str, str, str], Awaitable[None]]] = None,
|
|
79
|
+
on_batch_available: Optional[Callable[[str, list], Awaitable[None]]] = None,
|
|
80
|
+
on_server_failure: Optional[Callable[[str, str], Awaitable[None]]] = None,
|
|
81
|
+
) -> List[BaseTool]:
|
|
82
|
+
"""加载 MCP 工具列表
|
|
83
|
+
|
|
84
|
+
接受 MCPServerConfig 列表,逐个服务器连接并加载工具。
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
configs: MCP 服务器配置列表(仅 enabled 的会被处理)
|
|
88
|
+
on_tool_success: 单个工具调用成功回调
|
|
89
|
+
on_tool_error: 单个工具调用失败回调
|
|
90
|
+
on_batch_available: 服务器连接成功后批量标记工具可用回调 (config_id, tool_names)
|
|
91
|
+
on_server_failure: 服务器连接失败回调 (config_id, error_msg)
|
|
92
|
+
"""
|
|
93
|
+
enabled_configs = [c for c in configs if c.enabled]
|
|
94
|
+
if not enabled_configs:
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
all_tools = []
|
|
98
|
+
for config in enabled_configs:
|
|
99
|
+
try:
|
|
100
|
+
from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
101
|
+
|
|
102
|
+
key = sanitize_name(config.name)
|
|
103
|
+
client_config = {
|
|
104
|
+
key: {
|
|
105
|
+
"url": config.server_url,
|
|
106
|
+
"transport": "streamable_http",
|
|
107
|
+
**({"headers": config.headers} if config.headers else {}),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
client = MultiServerMCPClient(client_config)
|
|
112
|
+
tools = await client.get_tools()
|
|
113
|
+
|
|
114
|
+
original_names = []
|
|
115
|
+
for tool in tools:
|
|
116
|
+
original_name = tool.name
|
|
117
|
+
original_names.append(original_name)
|
|
118
|
+
tool.name = f"mcp__{key}__{original_name}"
|
|
119
|
+
if tool.description and not tool.description.startswith("[MCP"):
|
|
120
|
+
tool.description = f"[MCP:{config.name}] {tool.description}"
|
|
121
|
+
wrap_tool_with_error_handler(
|
|
122
|
+
tool, config.id, config.name, original_name,
|
|
123
|
+
on_tool_success=on_tool_success,
|
|
124
|
+
on_tool_error=on_tool_error,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
all_tools.extend(tools)
|
|
128
|
+
|
|
129
|
+
if on_batch_available:
|
|
130
|
+
try:
|
|
131
|
+
await on_batch_available(config.id, original_names)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
SYLogger.warning(f"[MCP] 写入工具状态失败: {e}")
|
|
134
|
+
|
|
135
|
+
SYLogger.info(f"[MCP] 服务器 '{config.name}' 加载了 {len(tools)} 个工具")
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
SYLogger.warning(f"[MCP] 服务器 '{config.name}' ({config.server_url}) 连接失败,跳过: {e}")
|
|
139
|
+
if on_server_failure:
|
|
140
|
+
try:
|
|
141
|
+
await on_server_failure(config.id, str(e)[:300])
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
if all_tools:
|
|
146
|
+
SYLogger.info(f"[MCP] 共加载 {len(all_tools)} 个 MCP 工具")
|
|
147
|
+
return all_tools
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def test_mcp_connection(server_url: str, headers: dict = None) -> dict:
|
|
151
|
+
"""测试 MCP 服务器连接,返回发现的工具列表"""
|
|
152
|
+
try:
|
|
153
|
+
from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
154
|
+
|
|
155
|
+
client_config = {
|
|
156
|
+
"test": {
|
|
157
|
+
"url": server_url,
|
|
158
|
+
"transport": "streamable_http",
|
|
159
|
+
**({"headers": headers} if headers else {}),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
client = MultiServerMCPClient(client_config)
|
|
164
|
+
tools = await client.get_tools()
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"success": True,
|
|
168
|
+
"tools": [{"name": t.name, "description": t.description or ""} for t in tools],
|
|
169
|
+
}
|
|
170
|
+
except Exception as e:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"error": str(e),
|
|
174
|
+
}
|
|
@@ -574,8 +574,12 @@ async def create_multi_agent_team(
|
|
|
574
574
|
|
|
575
575
|
# 创建 checkpointer
|
|
576
576
|
if checkpointer is None:
|
|
577
|
-
from
|
|
578
|
-
|
|
577
|
+
from sycommon.database.pg_checkpoint_service import PgCheckpointService
|
|
578
|
+
if PgCheckpointService.is_initialized():
|
|
579
|
+
checkpointer = await PgCheckpointService.get_checkpointer()
|
|
580
|
+
else:
|
|
581
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
582
|
+
checkpointer = MemorySaver()
|
|
579
583
|
|
|
580
584
|
tid = thread_id or user_id
|
|
581
585
|
agent_config = {"configurable": {"thread_id": tid}}
|
|
@@ -37,6 +37,11 @@ except ImportError:
|
|
|
37
37
|
_SSL_CONTEXT = ssl.create_default_context()
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def _read_file_safe(path: str) -> bytes:
|
|
41
|
+
with open(path, "rb") as f:
|
|
42
|
+
return f.read()
|
|
43
|
+
|
|
44
|
+
|
|
40
45
|
class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
41
46
|
"""
|
|
42
47
|
通过 HTTP API 连接远程沙箱容器
|
|
@@ -210,6 +215,24 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
210
215
|
nacos_group=group,
|
|
211
216
|
)
|
|
212
217
|
|
|
218
|
+
@classmethod
|
|
219
|
+
async def afrom_nacos(
|
|
220
|
+
cls,
|
|
221
|
+
service_name: str,
|
|
222
|
+
user_id: str,
|
|
223
|
+
group: str = "DEFAULT_GROUP",
|
|
224
|
+
version: str = None,
|
|
225
|
+
timeout: int = 180,
|
|
226
|
+
sync_dirs: List[tuple[str, str]] = None,
|
|
227
|
+
auto_sync: bool = False,
|
|
228
|
+
load_balance: bool = True,
|
|
229
|
+
) -> "HTTPSandboxBackend":
|
|
230
|
+
"""异步从 Nacos 服务发现创建后端实例"""
|
|
231
|
+
return await asyncio.to_thread(
|
|
232
|
+
cls.from_nacos,
|
|
233
|
+
service_name, user_id, group, version, timeout, sync_dirs, auto_sync, load_balance,
|
|
234
|
+
)
|
|
235
|
+
|
|
213
236
|
# ============== 内部方法 - 同步版本 ==============
|
|
214
237
|
|
|
215
238
|
def _refresh_from_nacos_and_switch_sync(self) -> bool:
|
|
@@ -565,7 +588,7 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
565
588
|
else:
|
|
566
589
|
# 单文件
|
|
567
590
|
SYLogger.info(f"[Sandbox] 上传单文件: {local_path}")
|
|
568
|
-
content = await asyncio.to_thread(lambda:
|
|
591
|
+
content = await asyncio.to_thread(lambda p: _read_file_safe(p), local_dir)
|
|
569
592
|
upload_results = await self.aupload_files([(remote_path, content)], timeout=timeout)
|
|
570
593
|
if upload_results[0].error:
|
|
571
594
|
results[local_path] = {"success": 0, "failed": 1, "errors": [{"path": remote_path, "error": upload_results[0].error}]}
|
|
@@ -669,7 +692,8 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
669
692
|
def _read_batch():
|
|
670
693
|
items = []
|
|
671
694
|
for sandbox_path, local_file in batch_files:
|
|
672
|
-
|
|
695
|
+
with open(local_file, "rb") as f:
|
|
696
|
+
content = f.read()
|
|
673
697
|
items.append((sandbox_path, content))
|
|
674
698
|
return items
|
|
675
699
|
batch_items = await asyncio.to_thread(_read_batch)
|
|
@@ -1037,7 +1061,7 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
1037
1061
|
SYLogger.info(f"[Sandbox] 异步目录上传完成: {local_path}, 成功={result['success']}, 失败={result['failed']}")
|
|
1038
1062
|
else:
|
|
1039
1063
|
SYLogger.info(f"[Sandbox] 异步上传单文件: {local_path}")
|
|
1040
|
-
content = await asyncio.to_thread(lambda:
|
|
1064
|
+
content = await asyncio.to_thread(lambda p: _read_file_safe(p), local_dir)
|
|
1041
1065
|
upload_results = await self.aupload_files([(remote_path, content)], timeout=timeout)
|
|
1042
1066
|
if upload_results[0].error:
|
|
1043
1067
|
results[local_path] = {"success": 0, "failed": 1, "errors": [{"path": remote_path, "error": upload_results[0].error}]}
|
|
@@ -162,7 +162,7 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
162
162
|
return None
|
|
163
163
|
|
|
164
164
|
def get_presigned_url(self, object_key: str, expires_days: int = 7) -> Optional[str]:
|
|
165
|
-
"""生成预签名下载 URL"""
|
|
165
|
+
"""生成预签名下载 URL(同步)"""
|
|
166
166
|
if not self._client:
|
|
167
167
|
return None
|
|
168
168
|
try:
|
|
@@ -177,8 +177,12 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
177
177
|
f"[MinIO] Presigned URL failed: {object_key}, error={e}")
|
|
178
178
|
return None
|
|
179
179
|
|
|
180
|
+
async def aget_presigned_url(self, object_key: str, expires_days: int = 7) -> Optional[str]:
|
|
181
|
+
"""异步生成预签名下载 URL"""
|
|
182
|
+
return await asyncio.to_thread(self.get_presigned_url, object_key, expires_days)
|
|
183
|
+
|
|
180
184
|
def remove_object(self, object_key: str) -> bool:
|
|
181
|
-
"""从 MinIO
|
|
185
|
+
"""从 MinIO 删除文件(同步)"""
|
|
182
186
|
if not self._client:
|
|
183
187
|
return False
|
|
184
188
|
try:
|
|
@@ -189,12 +193,12 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
189
193
|
SYLogger.warning(f"[MinIO] Delete failed: {object_key}, error={e}")
|
|
190
194
|
return False
|
|
191
195
|
|
|
192
|
-
def
|
|
193
|
-
"""
|
|
196
|
+
async def aremove_object(self, object_key: str) -> bool:
|
|
197
|
+
"""异步从 MinIO 删除文件"""
|
|
198
|
+
return await asyncio.to_thread(self.remove_object, object_key)
|
|
194
199
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"""
|
|
200
|
+
def remove_prefix(self, prefix: str) -> int:
|
|
201
|
+
"""删除 MinIO 中指定前缀下的所有对象(同步,用于目录删除)"""
|
|
198
202
|
if not self._client:
|
|
199
203
|
return 0
|
|
200
204
|
try:
|
|
@@ -210,6 +214,10 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
210
214
|
SYLogger.warning(f"[MinIO] Delete prefix failed: {prefix}, error={e}")
|
|
211
215
|
return 0
|
|
212
216
|
|
|
217
|
+
async def aremove_prefix(self, prefix: str) -> int:
|
|
218
|
+
"""异步删除 MinIO 中指定前缀下的所有对象"""
|
|
219
|
+
return await asyncio.to_thread(self.remove_prefix, prefix)
|
|
220
|
+
|
|
213
221
|
# ============== 查找最近副本 ==============
|
|
214
222
|
|
|
215
223
|
def find_latest_object_key(self, user_id: str, file_path: str) -> Optional[str]:
|
|
@@ -252,7 +260,7 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
252
260
|
SYLogger.warning(f"[MinIO] find_latest failed: {e}")
|
|
253
261
|
return None
|
|
254
262
|
|
|
255
|
-
async def
|
|
263
|
+
async def afind_latest_object_key(self, user_id: str, file_path: str) -> Optional[str]:
|
|
256
264
|
"""异步查找某个文件最近一天的 object key"""
|
|
257
265
|
return await asyncio.to_thread(self.find_latest_object_key, user_id, file_path)
|
|
258
266
|
|
|
@@ -473,12 +481,17 @@ class MinioSyncService(metaclass=SingletonMeta):
|
|
|
473
481
|
files_to_upload = []
|
|
474
482
|
for rel_path in batch:
|
|
475
483
|
try:
|
|
476
|
-
|
|
477
|
-
self._client.get_object,
|
|
484
|
+
def _download_object(bucket, key):
|
|
485
|
+
resp = self._client.get_object(bucket, key)
|
|
486
|
+
try:
|
|
487
|
+
return resp.read()
|
|
488
|
+
finally:
|
|
489
|
+
resp.close()
|
|
490
|
+
resp.release_conn()
|
|
491
|
+
|
|
492
|
+
content = await asyncio.to_thread(
|
|
493
|
+
_download_object, self._bucket, f"{prefix}{rel_path}"
|
|
478
494
|
)
|
|
479
|
-
content = response.read()
|
|
480
|
-
response.close()
|
|
481
|
-
response.release_conn()
|
|
482
495
|
files_to_upload.append((f"/{rel_path}", content))
|
|
483
496
|
except Exception as e:
|
|
484
497
|
SYLogger.warning(f"[MinIO] 下载文件失败: {rel_path}, error={e}")
|
|
@@ -123,7 +123,7 @@ class SandboxRecoveryManager:
|
|
|
123
123
|
bool: 切换成功返回 True,否则返回 False
|
|
124
124
|
"""
|
|
125
125
|
# 优先使用底层已有的切换逻辑(会尝试迁移工作空间文件)
|
|
126
|
-
switched = await self.backend.
|
|
126
|
+
switched = await asyncio.to_thread(self.backend._refresh_from_nacos_and_switch_sync)
|
|
127
127
|
|
|
128
128
|
if switched:
|
|
129
129
|
self._consecutive_failures = 0
|
|
@@ -149,7 +149,8 @@ class SandboxRecoveryManager:
|
|
|
149
149
|
SYLogger.error("[Sandbox] NacosService 未初始化")
|
|
150
150
|
return False
|
|
151
151
|
|
|
152
|
-
instances =
|
|
152
|
+
instances = await asyncio.to_thread(
|
|
153
|
+
nacos_manager.get_service_instances,
|
|
153
154
|
self.backend._nacos_service_name,
|
|
154
155
|
group=self.backend._nacos_group
|
|
155
156
|
)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""上下文压缩 middleware 构建工具。
|
|
2
2
|
|
|
3
3
|
根据 nacos 中配置的模型 maxTokens,用绝对 token 数设置压缩阈值。
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
优先使用模型 API 返回的 usage_metadata.total_tokens(真实 token 数),
|
|
5
|
+
无 metadata 时回退到 chars_per_token=2.0 的估算值。
|
|
6
|
+
同时增加基于消息数的安全阈值,防止估算偏低导致压缩不触发。
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from __future__ import annotations
|
|
@@ -24,17 +25,28 @@ if TYPE_CHECKING:
|
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
def _extract_last_usage_total_tokens(messages) -> int:
|
|
30
|
+
"""从消息历史中提取最后一条 AIMessage 的 usage_metadata.total_tokens。
|
|
31
|
+
|
|
32
|
+
返回 0 表示无数据(需回退到估算)。
|
|
33
|
+
"""
|
|
34
|
+
from langchain_core.messages import AIMessage
|
|
35
|
+
for msg in reversed(messages):
|
|
36
|
+
if isinstance(msg, AIMessage):
|
|
37
|
+
meta = getattr(msg, 'usage_metadata', None)
|
|
38
|
+
if meta and isinstance(meta, dict):
|
|
39
|
+
total = meta.get('total_tokens', 0)
|
|
40
|
+
if isinstance(total, int) and total > 0:
|
|
41
|
+
return total
|
|
42
|
+
return 0
|
|
30
43
|
|
|
31
44
|
|
|
32
45
|
def _patched_compute_summarization_defaults(model):
|
|
33
46
|
"""覆盖 deepagents 默认值,返回中文场景修正后的绝对 token 阈值。
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
直接返回修正后的绝对 token 数。
|
|
48
|
+
同时增加基于消息数的安全阈值:即使 token 估算偏低,
|
|
49
|
+
消息数超过 200 条时也会触发压缩(覆盖工具 schema 等未计入的开销)。
|
|
38
50
|
"""
|
|
39
51
|
try:
|
|
40
52
|
from sycommon.config.Config import Config
|
|
@@ -48,10 +60,12 @@ def _patched_compute_summarization_defaults(model):
|
|
|
48
60
|
except Exception:
|
|
49
61
|
max_tokens = 72000
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
# 60% 触发(120K/200K):实测模型在 input≈137K 时开始退化,
|
|
64
|
+
# 在 120K 触发压缩留 ~17K 安全余量给工具 schema 等未计入开销
|
|
65
|
+
trigger = int(max_tokens * 0.60)
|
|
66
|
+
keep = int(max_tokens * 0.10)
|
|
53
67
|
return {
|
|
54
|
-
"trigger": ("tokens", trigger),
|
|
68
|
+
"trigger": [("tokens", trigger), ("messages", 200)],
|
|
55
69
|
"keep": ("tokens", keep),
|
|
56
70
|
"truncate_args_settings": {
|
|
57
71
|
"trigger": ("tokens", trigger),
|
|
@@ -63,10 +77,14 @@ def _patched_compute_summarization_defaults(model):
|
|
|
63
77
|
# monkey-patch:替换 deepagents 的默认计算函数
|
|
64
78
|
_summ_mod.compute_summarization_defaults = _patched_compute_summarization_defaults
|
|
65
79
|
|
|
66
|
-
# monkey-patch:在内置 middleware 的 awrap_model_call
|
|
80
|
+
# monkey-patch:在内置 middleware 的 awrap_model_call 中注入真实 token + 日志
|
|
67
81
|
_OrigDeepAgentsSumm = _summ_mod._DeepAgentsSummarizationMiddleware
|
|
68
82
|
_orig_awrap_model_call = _OrigDeepAgentsSumm.awrap_model_call
|
|
69
83
|
|
|
84
|
+
# 基础估算函数,用于日志对比
|
|
85
|
+
_approx_counter = functools.partial(
|
|
86
|
+
count_tokens_approximately, chars_per_token=2.0)
|
|
87
|
+
|
|
70
88
|
|
|
71
89
|
async def _patched_awrap_model_call(self, request, handler):
|
|
72
90
|
effective_messages = self._get_effective_messages(request)
|
|
@@ -75,15 +93,43 @@ async def _patched_awrap_model_call(self, request, handler):
|
|
|
75
93
|
)
|
|
76
94
|
counted_messages = [request.system_message, *
|
|
77
95
|
truncated_messages] if request.system_message is not None else truncated_messages
|
|
96
|
+
|
|
97
|
+
# 从截断前的 effective_messages 提取真实 token(截断会丢失 usage_metadata)
|
|
98
|
+
real_tokens = _extract_last_usage_total_tokens(effective_messages)
|
|
99
|
+
|
|
100
|
+
# 估算值(用于日志对比)
|
|
78
101
|
try:
|
|
79
|
-
|
|
80
|
-
counted_messages, tools=request.tools)
|
|
102
|
+
estimated = _approx_counter(counted_messages, tools=request.tools)
|
|
81
103
|
except TypeError:
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
estimated = _approx_counter(counted_messages)
|
|
105
|
+
|
|
106
|
+
# 如果有真实 token,临时替换 token_counter 使 _orig 内部判断也用真实值
|
|
107
|
+
# 这样 _should_summarize 和 _determine_cutoff_index 都能拿到正确的 token 数
|
|
108
|
+
if real_tokens > 0:
|
|
109
|
+
original_counter = self.token_counter
|
|
110
|
+
|
|
111
|
+
def _real_counter(msgs, **kwargs):
|
|
112
|
+
# 优先从当前消息中提取真实值(压缩后的消息可能有新的 metadata)
|
|
113
|
+
r = _extract_last_usage_total_tokens(msgs)
|
|
114
|
+
return r if r > 0 else real_tokens
|
|
115
|
+
|
|
116
|
+
self._lc_helper.token_counter = _real_counter
|
|
117
|
+
try:
|
|
118
|
+
result = await _orig_awrap_model_call(self, request, handler)
|
|
119
|
+
finally:
|
|
120
|
+
self._lc_helper.token_counter = original_counter
|
|
121
|
+
else:
|
|
122
|
+
result = await _orig_awrap_model_call(self, request, handler)
|
|
123
|
+
|
|
124
|
+
# 日志
|
|
125
|
+
source = 'real' if real_tokens > 0 else 'estimated'
|
|
126
|
+
should = self._should_summarize(truncated_messages, real_tokens if real_tokens > 0 else estimated)
|
|
84
127
|
print(
|
|
85
|
-
f"[TokenCount]
|
|
86
|
-
|
|
128
|
+
f"[TokenCount] real={real_tokens} estimated={estimated} "
|
|
129
|
+
f"source={source} msgs={len(counted_messages)} "
|
|
130
|
+
f"should_summarize={should} "
|
|
131
|
+
f"trigger={getattr(self._lc_helper, 'trigger', '?')}")
|
|
132
|
+
return result
|
|
87
133
|
|
|
88
134
|
|
|
89
135
|
_OrigDeepAgentsSumm.awrap_model_call = _patched_awrap_model_call
|
|
@@ -94,25 +140,21 @@ def build_summarization_middleware(
|
|
|
94
140
|
model_name: str,
|
|
95
141
|
backend: "BACKEND_TYPES",
|
|
96
142
|
*,
|
|
97
|
-
trigger_fraction: float = 0.
|
|
98
|
-
keep_fraction: float = 0.
|
|
143
|
+
trigger_fraction: float = 0.60,
|
|
144
|
+
keep_fraction: float = 0.10,
|
|
99
145
|
default_max_tokens: int = 200000,
|
|
100
146
|
) -> SummarizationToolMiddleware:
|
|
101
147
|
"""根据模型上下文窗口大小构建 compact_conversation 工具 middleware。
|
|
102
148
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
计算压缩阈值。
|
|
106
|
-
|
|
107
|
-
此处创建的 SummarizationToolMiddleware 仅提供 compact_conversation 手动压缩工具,
|
|
108
|
-
使用 nacos 配置的 maxTokens 作为其内部 SummarizationMiddleware 的阈值参数。
|
|
149
|
+
优先使用模型返回的 usage_metadata 真实 token 数进行压缩判断,
|
|
150
|
+
无 usage_metadata 时回退到 chars_per_token=2.0 估算。
|
|
109
151
|
|
|
110
152
|
Args:
|
|
111
153
|
model: LLM 实例。
|
|
112
154
|
model_name: 模型名称(用于从 nacos 读取配置)。
|
|
113
155
|
backend: 后端实例。
|
|
114
|
-
trigger_fraction: 触发压缩占有效输入的比例,默认
|
|
115
|
-
keep_fraction: 压缩后保留占有效输入的比例,默认
|
|
156
|
+
trigger_fraction: 触发压缩占有效输入的比例,默认 60%。
|
|
157
|
+
keep_fraction: 压缩后保留占有效输入的比例,默认 10%。
|
|
116
158
|
default_max_tokens: 无法从配置读取时的默认上下文窗口大小。
|
|
117
159
|
|
|
118
160
|
Returns:
|
|
@@ -134,7 +176,8 @@ def build_summarization_middleware(
|
|
|
134
176
|
backend=backend,
|
|
135
177
|
trigger=("tokens", trigger_tokens),
|
|
136
178
|
keep=("tokens", keep_tokens),
|
|
137
|
-
token_counter=
|
|
179
|
+
token_counter=functools.partial(
|
|
180
|
+
count_tokens_approximately, chars_per_token=2.0),
|
|
138
181
|
trim_tokens_to_summarize=None,
|
|
139
182
|
truncate_args_settings={
|
|
140
183
|
"trigger": ("tokens", trigger_tokens),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PgConfig(BaseModel):
|
|
5
|
+
"""PostgreSQL 配置"""
|
|
6
|
+
host: str = Field(default="localhost", description="PG 主机地址")
|
|
7
|
+
port: int = Field(default=5432, description="PG 端口")
|
|
8
|
+
dbname: str = Field(default="postgres", description="数据库名")
|
|
9
|
+
user: str = Field(default="postgres", description="用户名")
|
|
10
|
+
password: str = Field(default="", description="密码")
|
|
11
|
+
min_pool_size: int = Field(default=2, description="连接池最小连接数")
|
|
12
|
+
max_pool_size: int = Field(default=10, description="连接池最大连接数")
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_dict(cls, config: dict) -> "PgConfig":
|
|
16
|
+
"""从字典解析配置
|
|
17
|
+
|
|
18
|
+
支持两种格式:
|
|
19
|
+
1. 扁平格式: {"host": "...", "port": 5432, ...}
|
|
20
|
+
2. Spring 格式: {"spring": {"datasource": {"url": "jdbc:postgresql://...", ...}}}
|
|
21
|
+
"""
|
|
22
|
+
spring = config.get("spring", {})
|
|
23
|
+
if spring:
|
|
24
|
+
ds = spring.get("datasource", {})
|
|
25
|
+
url = ds.get("url", "")
|
|
26
|
+
return cls._from_jdbc_url(url, ds.get("username", ""), ds.get("password", ""))
|
|
27
|
+
|
|
28
|
+
return cls(
|
|
29
|
+
host=config.get("host", "localhost"),
|
|
30
|
+
port=config.get("port", 5432),
|
|
31
|
+
dbname=config.get("dbname", config.get("database", "postgres")),
|
|
32
|
+
user=config.get("user", config.get("username", "postgres")),
|
|
33
|
+
password=config.get("password", ""),
|
|
34
|
+
min_pool_size=config.get("min_pool_size", 2),
|
|
35
|
+
max_pool_size=config.get("max_pool_size", 10),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _from_jdbc_url(cls, url: str, username: str, password: str) -> "PgConfig":
|
|
40
|
+
"""从 JDBC URL 解析配置"""
|
|
41
|
+
# jdbc:postgresql://host:port/dbname
|
|
42
|
+
if "://" in url:
|
|
43
|
+
url = url.split("://", 1)[1]
|
|
44
|
+
parts = url.split("/")
|
|
45
|
+
dbname = parts[1] if len(parts) > 1 else "postgres"
|
|
46
|
+
host_port = parts[0].split(":")
|
|
47
|
+
host = host_port[0]
|
|
48
|
+
port = int(host_port[1]) if len(host_port) > 1 else 5432
|
|
49
|
+
return cls(host=host, port=port, dbname=dbname, user=username, password=password)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def dsn(self) -> str:
|
|
53
|
+
"""生成 psycopg 连接字符串"""
|
|
54
|
+
return f"host={self.host} port={self.port} dbname={self.dbname} user={self.user} password={self.password}"
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
2
2
|
from sqlalchemy import text
|
|
3
3
|
|
|
4
|
+
# Fix: aiomysql's AsyncAdapt ping() requires 'reconnect' positional arg,
|
|
5
|
+
# but SQLAlchemy's pymysql dialect calls ping() without it.
|
|
6
|
+
# aiomysql asserts reconnect must be False (async reconnection is handled by pool).
|
|
7
|
+
from sqlalchemy.dialects.mysql.pymysql import MySQLDialect_pymysql
|
|
8
|
+
|
|
9
|
+
_original_do_ping = MySQLDialect_pymysql.do_ping
|
|
10
|
+
|
|
11
|
+
def _patched_do_ping(self, dbapi_connection):
|
|
12
|
+
try:
|
|
13
|
+
return _original_do_ping(self, dbapi_connection)
|
|
14
|
+
except TypeError:
|
|
15
|
+
dbapi_connection.ping(reconnect=False)
|
|
16
|
+
|
|
17
|
+
MySQLDialect_pymysql.do_ping = _patched_do_ping
|
|
18
|
+
|
|
4
19
|
from sycommon.config.Config import SingletonMeta
|
|
5
20
|
from sycommon.config.DatabaseConfig import DatabaseConfig, convert_dict_keys
|
|
6
21
|
from sycommon.logging.kafka_log import SYLogger
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PostgreSQL Checkpoint 服务
|
|
3
|
+
|
|
4
|
+
为 LangGraph Agent 提供 PostgreSQL 持久化 checkpoint 存储。
|
|
5
|
+
使用 AsyncPostgresSaver,适配 FastAPI 的异步环境。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
# 通过 Services.plugins 自动初始化
|
|
9
|
+
Services.plugins(app, pg_checkpoint_service=True)
|
|
10
|
+
|
|
11
|
+
# 或手动初始化
|
|
12
|
+
await PgCheckpointService.setup({"host": "10.10.6.203", "port": 5432, ...})
|
|
13
|
+
|
|
14
|
+
# 获取 checkpointer
|
|
15
|
+
checkpointer = await PgCheckpointService.get_checkpointer()
|
|
16
|
+
"""
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Optional, Any
|
|
19
|
+
|
|
20
|
+
from sycommon.config.Config import SingletonMeta
|
|
21
|
+
from sycommon.config.PgConfig import PgConfig
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PgCheckpointService(metaclass=SingletonMeta):
|
|
25
|
+
"""PostgreSQL Checkpoint 服务(单例)"""
|
|
26
|
+
_checkpointer: Optional[Any] = None
|
|
27
|
+
_initialized: bool = False
|
|
28
|
+
_config: Optional[PgConfig] = None
|
|
29
|
+
_pool: Optional[Any] = None
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
async def setup(cls, config: Optional[dict] = None):
|
|
36
|
+
"""初始化 PG Checkpoint 服务
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config: 配置字典。不传则尝试从 Nacos 读取 pg.yml。
|
|
40
|
+
"""
|
|
41
|
+
if cls._initialized:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
if config is None:
|
|
46
|
+
try:
|
|
47
|
+
from sycommon.config.Config import Config
|
|
48
|
+
config = Config().config.get('llm', {}).get('PostgreSQL')
|
|
49
|
+
if not config:
|
|
50
|
+
logging.info("未从 Nacos 获取到 PostgreSQL 配置,PG Checkpoint 服务将禁用")
|
|
51
|
+
return
|
|
52
|
+
logging.info("从 Nacos 获取 PostgreSQL 配置成功")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.info(f"从 Nacos 读取 PostgreSQL 配置失败: {e},PG Checkpoint 服务将禁用")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
cls._config = PgConfig.from_dict(config)
|
|
58
|
+
|
|
59
|
+
from psycopg_pool import AsyncConnectionPool
|
|
60
|
+
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
|
|
61
|
+
|
|
62
|
+
conn_string = cls._config.dsn
|
|
63
|
+
|
|
64
|
+
cls._pool = AsyncConnectionPool(
|
|
65
|
+
conninfo=conn_string,
|
|
66
|
+
min_size=cls._config.min_pool_size,
|
|
67
|
+
max_size=cls._config.max_pool_size,
|
|
68
|
+
open=False,
|
|
69
|
+
kwargs={
|
|
70
|
+
"autocommit": True,
|
|
71
|
+
"prepare_threshold": 0,
|
|
72
|
+
"keepalives": 1,
|
|
73
|
+
"keepalives_idle": 30,
|
|
74
|
+
"keepalives_interval": 10,
|
|
75
|
+
"keepalives_count": 5,
|
|
76
|
+
},
|
|
77
|
+
check=AsyncConnectionPool.check_connection,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
await cls._pool.open()
|
|
81
|
+
|
|
82
|
+
cls._checkpointer = AsyncPostgresSaver(cls._pool)
|
|
83
|
+
await cls._checkpointer.setup()
|
|
84
|
+
|
|
85
|
+
cls._initialized = True
|
|
86
|
+
logging.info(
|
|
87
|
+
f"PgCheckpointService 初始化成功,地址: {cls._config.host}:{cls._config.port}/{cls._config.dbname}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logging.error(f"PgCheckpointService 初始化失败: {e}", exc_info=True)
|
|
92
|
+
# 清理可能已创建的连接池
|
|
93
|
+
if cls._pool:
|
|
94
|
+
try:
|
|
95
|
+
await cls._pool.close()
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
cls._pool = None
|
|
99
|
+
cls._checkpointer = None
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
async def get_checkpointer(cls) -> Any:
|
|
103
|
+
"""获取 AsyncPostgresSaver 实例
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
AsyncPostgresSaver 实例
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
RuntimeError: 如果服务未初始化
|
|
110
|
+
"""
|
|
111
|
+
if not cls._initialized or not cls._checkpointer:
|
|
112
|
+
raise RuntimeError("PgCheckpointService 未初始化,请先调用 PgCheckpointService.setup()")
|
|
113
|
+
return cls._checkpointer
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def is_initialized(cls) -> bool:
|
|
117
|
+
"""检查是否已初始化"""
|
|
118
|
+
return cls._initialized
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
async def close(cls):
|
|
122
|
+
"""关闭 PG 连接池"""
|
|
123
|
+
if cls._pool:
|
|
124
|
+
try:
|
|
125
|
+
await cls._pool.close()
|
|
126
|
+
logging.info("PgCheckpointService 连接池已关闭")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logging.error(f"关闭 PgCheckpointService 连接池失败: {e}")
|
|
129
|
+
finally:
|
|
130
|
+
cls._pool = None
|
|
131
|
+
cls._checkpointer = None
|
|
132
|
+
cls._initialized = False
|
sycommon/llm/get_llm.py
CHANGED
|
@@ -168,6 +168,10 @@ def get_llm(
|
|
|
168
168
|
"stream_chunk_timeout": 180,
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
# 传入 maxOutputTokens(max_completion_tokens)确保模型有足够的输出 token 空间
|
|
172
|
+
if llmConfig.maxOutputTokens:
|
|
173
|
+
init_params["max_tokens"] = llmConfig.maxOutputTokens
|
|
174
|
+
|
|
171
175
|
# 合并其他透传参数(包括 presence_penalty, extra_body, top_p 等)
|
|
172
176
|
init_params.update(kwargs)
|
|
173
177
|
|
sycommon/services.py
CHANGED
|
@@ -20,6 +20,7 @@ from sycommon.synacos.feign import close_all_feign_sessions
|
|
|
20
20
|
from sycommon.synacos.feign_client import close_all_feign_client_sessions
|
|
21
21
|
from sycommon.database.elasticsearch_service import ElasticsearchService
|
|
22
22
|
from sycommon.database.redis_service import RedisService
|
|
23
|
+
from sycommon.database.pg_checkpoint_service import PgCheckpointService
|
|
23
24
|
from sycommon.database.database_service import DatabaseService
|
|
24
25
|
from sycommon.xxljob.xxljob_service import XxlJobService
|
|
25
26
|
|
|
@@ -109,6 +110,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
109
110
|
# 可插拔服务开关(从类变量中继承 plugins() 阶段的设置)
|
|
110
111
|
self._enable_es: bool = Services.__dict__.get('_enable_es', False)
|
|
111
112
|
self._enable_redis: bool = Services.__dict__.get('_enable_redis', False)
|
|
113
|
+
self._enable_pg: bool = Services.__dict__.get('_enable_pg', False)
|
|
112
114
|
self._enable_sandbox: bool = Services.__dict__.get('_enable_sandbox', False)
|
|
113
115
|
self._enable_xxljob: bool = Services.__dict__.get('_enable_xxljob', False)
|
|
114
116
|
|
|
@@ -148,6 +150,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
148
150
|
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None,
|
|
149
151
|
elasticsearch_service: Optional[Callable[[dict], None]] = None,
|
|
150
152
|
redis_service: Optional[Callable[[dict], None]] = None,
|
|
153
|
+
pg_checkpoint_service: Optional[Union[bool, dict]] = None,
|
|
151
154
|
sandbox_service: bool = False,
|
|
152
155
|
deep_agent_service: bool = False,
|
|
153
156
|
multi_agent_service: bool = False,
|
|
@@ -193,6 +196,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
193
196
|
# 保存可插拔服务开关状态到类变量(实例创建时会被拷贝到实例变量)
|
|
194
197
|
Services._enable_es = elasticsearch_service is not None
|
|
195
198
|
Services._enable_redis = redis_service is not None
|
|
199
|
+
Services._enable_pg = pg_checkpoint_service is not None
|
|
196
200
|
Services._enable_sandbox = sandbox_service or deep_agent_service or multi_agent_service
|
|
197
201
|
Services._enable_xxljob = xxljob_service is not None
|
|
198
202
|
|
|
@@ -297,14 +301,19 @@ class Services(metaclass=SingletonMeta):
|
|
|
297
301
|
|
|
298
302
|
app_instance.state.services = instance
|
|
299
303
|
|
|
300
|
-
# 4.
|
|
304
|
+
# 4. 初始化 PG Checkpoint 服务(如果启用)
|
|
305
|
+
if instance._enable_pg:
|
|
306
|
+
pg_config = pg_checkpoint_service if isinstance(pg_checkpoint_service, dict) else None
|
|
307
|
+
await PgCheckpointService.setup(pg_config)
|
|
308
|
+
|
|
309
|
+
# 5. 启动沙箱后台清理任务(如果沙箱服务启用)
|
|
301
310
|
if instance._enable_sandbox:
|
|
302
311
|
from sycommon.middleware.sandbox import _cleanup_finished_processes
|
|
303
312
|
sandbox_cleanup_task = asyncio.create_task(
|
|
304
313
|
_cleanup_finished_processes())
|
|
305
314
|
logging.info("沙箱后台清理任务已启动")
|
|
306
315
|
|
|
307
|
-
#
|
|
316
|
+
# 6. 执行用户定义的生命周期
|
|
308
317
|
if cls._user_lifespan:
|
|
309
318
|
async with cls._user_lifespan(app_instance):
|
|
310
319
|
yield
|
|
@@ -472,6 +481,13 @@ class Services(metaclass=SingletonMeta):
|
|
|
472
481
|
except Exception as e:
|
|
473
482
|
logging.debug(f"关闭 RedisService 时发生异常: {e}")
|
|
474
483
|
|
|
484
|
+
# 关闭 PG Checkpoint 服务(仅当启用时)
|
|
485
|
+
if cls._instance and cls._instance._enable_pg:
|
|
486
|
+
try:
|
|
487
|
+
await PgCheckpointService.close()
|
|
488
|
+
except Exception as e:
|
|
489
|
+
logging.debug(f"关闭 PgCheckpointService 时发生异常: {e}")
|
|
490
|
+
|
|
475
491
|
# 关闭 XXL-JOB 执行器(仅当启用时)
|
|
476
492
|
if cls._instance and cls._instance._enable_xxljob:
|
|
477
493
|
try:
|
sycommon/synacos/feign.py
CHANGED
|
@@ -203,8 +203,11 @@ async def _feign_internal(service_name, api_path, method='GET', params=None, hea
|
|
|
203
203
|
raise TypeError(f"files 参数必须是字典或列表,实际为 {type(files)}")
|
|
204
204
|
if file_path:
|
|
205
205
|
filename = os.path.basename(file_path)
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
def _read_file():
|
|
207
|
+
with open(file_path, 'rb') as f:
|
|
208
|
+
return f.read()
|
|
209
|
+
content = await asyncio.to_thread(_read_file)
|
|
210
|
+
data.add_field('file', content, filename=filename)
|
|
208
211
|
# 移除Content-Type,让aiohttp自动处理
|
|
209
212
|
headers.pop('Content-Type', None)
|
|
210
213
|
async with session.request(
|
sycommon/synacos/feign_client.py
CHANGED
|
@@ -303,10 +303,13 @@ def feign_client(
|
|
|
303
303
|
for path in file_paths:
|
|
304
304
|
if not os.path.exists(path):
|
|
305
305
|
raise FileNotFoundError(f"文件不存在: {path}")
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
def _read_file(p: str) -> bytes:
|
|
307
|
+
with open(p, "rb") as f:
|
|
308
|
+
return f.read()
|
|
309
|
+
content = await asyncio.to_thread(_read_file, path)
|
|
310
|
+
form_data.add_field(
|
|
311
|
+
meta.field_name, content, filename=os.path.basename(path)
|
|
312
|
+
)
|
|
310
313
|
# 处理表单字段(支持 Pydantic 模型)
|
|
311
314
|
form_params = {
|
|
312
315
|
n: m for n, m in param_meta.items() if isinstance(m, Form)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sycommon-python-lib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,17 +8,18 @@ Requires-Dist: aio-pika>=9.6.2
|
|
|
8
8
|
Requires-Dist: aiohttp>=3.13.5
|
|
9
9
|
Requires-Dist: aiomysql>=0.3.2
|
|
10
10
|
Requires-Dist: anyio>=4.12.1
|
|
11
|
-
Requires-Dist: decorator>=5.
|
|
11
|
+
Requires-Dist: decorator>=5.3.0
|
|
12
12
|
Requires-Dist: deepagents>=0.6.1
|
|
13
13
|
Requires-Dist: elasticsearch>=9.4.0
|
|
14
14
|
Requires-Dist: fastapi>=0.136.1
|
|
15
15
|
Requires-Dist: jinja2>=3.1.6
|
|
16
16
|
Requires-Dist: kafka-python>=2.3.1
|
|
17
|
-
Requires-Dist: langchain>=1.3.
|
|
17
|
+
Requires-Dist: langchain>=1.3.1
|
|
18
18
|
Requires-Dist: langchain-core>=1.4.0
|
|
19
19
|
Requires-Dist: langchain-openai>=1.2.1
|
|
20
20
|
Requires-Dist: langfuse>=4.6.1
|
|
21
21
|
Requires-Dist: langgraph>=1.2.0
|
|
22
|
+
Requires-Dist: langgraph-checkpoint-postgres>=3.1.0
|
|
22
23
|
Requires-Dist: langgraph-checkpoint-redis>=0.4.1
|
|
23
24
|
Requires-Dist: ldap3>=2.9.1
|
|
24
25
|
Requires-Dist: loguru>=0.7.3
|
|
@@ -28,17 +29,19 @@ Requires-Dist: psutil>=7.2.2
|
|
|
28
29
|
Requires-Dist: pyxxl>=0.4.6
|
|
29
30
|
Requires-Dist: pydantic>=2.13.4
|
|
30
31
|
Requires-Dist: python-dotenv>=1.2.2
|
|
31
|
-
Requires-Dist: python-multipart>=0.0.
|
|
32
|
+
Requires-Dist: python-multipart>=0.0.29
|
|
32
33
|
Requires-Dist: pyyaml>=6.0.3
|
|
33
34
|
Requires-Dist: redis>=7.3.0
|
|
34
35
|
Requires-Dist: sentry-sdk[fastapi]>=2.60.0
|
|
35
36
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.48
|
|
36
37
|
Requires-Dist: starlette[full]>=1.0.0
|
|
37
|
-
Requires-Dist: tiktoken>=0.
|
|
38
|
-
Requires-Dist: uvicorn>=0.
|
|
38
|
+
Requires-Dist: tiktoken>=0.13.0
|
|
39
|
+
Requires-Dist: uvicorn>=0.47.0
|
|
39
40
|
Requires-Dist: wecom-aibot-python-sdk>=1.0.2
|
|
40
41
|
Requires-Dist: twine>=6.2.0
|
|
41
42
|
Requires-Dist: minio>=7.2.20
|
|
43
|
+
Requires-Dist: langchain-mcp-adapters>=0.2.2
|
|
44
|
+
Requires-Dist: psycopg[binary,pool]>=3.3.4
|
|
42
45
|
|
|
43
46
|
# sycommon-python-lib
|
|
44
47
|
|
|
@@ -125,19 +125,22 @@ sycli/rl/strategy_generator.py,sha256=RzYkaj4jQ_5aNOuj1WA-SXIHfO5h1nTSSHmLZpO-gi
|
|
|
125
125
|
sycli/rl/strategy_prompts.py,sha256=dwx291OnyywP6z7uKmoDPZNCC4MNrIyDANdAEO0evJs,2873
|
|
126
126
|
sycli/skills/__init__.py,sha256=0o9HuaDSvN-z0JZwlDSwU7WxtchQn7f0OtluIMgtC0Q,212
|
|
127
127
|
sycommon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
128
|
-
sycommon/services.py,sha256=
|
|
129
|
-
sycommon/agent/__init__.py,sha256=
|
|
128
|
+
sycommon/services.py,sha256=UtWn36dPmB9g1CTKkNgzbU8M28iOkZd2BtdhvDZLfbk,22214
|
|
129
|
+
sycommon/agent/__init__.py,sha256=mxceAeUifQ-DKvWp7ZEJIFlmOCb5wpYHPGQw3rwEN8I,4378
|
|
130
130
|
sycommon/agent/agent_manager.py,sha256=UhhaekEumT7g4v_Z1UB4jTp13X0n8M8erYaQdkGGWkA,13620
|
|
131
131
|
sycommon/agent/chat_events.py,sha256=bWAMWYIZ2L_yqUcn5jq9ius_lQxLHEv4zQLEqX6UaeM,13190
|
|
132
|
-
sycommon/agent/deep_agent.py,sha256=
|
|
133
|
-
sycommon/agent/multi_agent_team.py,sha256=
|
|
134
|
-
sycommon/agent/summarization_utils.py,sha256=
|
|
132
|
+
sycommon/agent/deep_agent.py,sha256=WTGKWx0MxmiIbbrKPTbQl6on2w4LQRWtH0hdiViA-dE,31709
|
|
133
|
+
sycommon/agent/multi_agent_team.py,sha256=NHmsUNwe3huguUUzbeoiOjgo9wh4-eXH0AjPDvm1dP4,26781
|
|
134
|
+
sycommon/agent/summarization_utils.py,sha256=PRCIFtYBrH0bbSxsIc-qpC4iEXJzk72UuR7u5mQTt2w,7360
|
|
135
|
+
sycommon/agent/mcp/__init__.py,sha256=iKrdDhIrFsNIkqG_kgcwNe-nOiM6uVfolKv44LfQ-FQ,636
|
|
136
|
+
sycommon/agent/mcp/models.py,sha256=RBAIbGETNXkqD3wQZT7eKS4ozkgE9DQEneF1WKZf1C0,1355
|
|
137
|
+
sycommon/agent/mcp/tool_loader.py,sha256=SEny14f7Bm9I17pT-9PJWMbhi9Ki77wvCR0KRNEJmyM,6428
|
|
135
138
|
sycommon/agent/sandbox/__init__.py,sha256=jR7LlkD4J4Y6QYyRXQClkwmqDBCCPmycV_hQV9p9YHw,4621
|
|
136
139
|
sycommon/agent/sandbox/file_ops.py,sha256=6ymRMM0WchM7G_YmF1ckrLjf5s_JCh1wrAp2g_-sg8k,23162
|
|
137
|
-
sycommon/agent/sandbox/http_sandbox_backend.py,sha256=
|
|
138
|
-
sycommon/agent/sandbox/minio_sync.py,sha256=
|
|
140
|
+
sycommon/agent/sandbox/http_sandbox_backend.py,sha256=kwuPEmrOMyxfrRu20AEGqWD9t38L-DrtKSFp6CWt44o,56877
|
|
141
|
+
sycommon/agent/sandbox/minio_sync.py,sha256=d1kuWllvyAvAMsFZCP0OdHEQtXN9BEIgHbupC31BjSk,20000
|
|
139
142
|
sycommon/agent/sandbox/sandbox_pool.py,sha256=eMn8sLakCWf90l6ni2-333QM8oBdX1CflV-WzneFp_k,9133
|
|
140
|
-
sycommon/agent/sandbox/sandbox_recovery.py,sha256=
|
|
143
|
+
sycommon/agent/sandbox/sandbox_recovery.py,sha256=X-eDODx1tmGMh_iTngV6e1ppfDBHpTdkPreJusN5MHY,7358
|
|
141
144
|
sycommon/agent/sandbox/session.py,sha256=TjzC3yFC-VaJ75UwCyL26QX4PRTGNNfQae1FKFuOsYI,2365
|
|
142
145
|
sycommon/auth/__init__.py,sha256=W814cfHlLXFymmxeTi3pIreFb4nhKnQ7NY1H38x1Gic,974
|
|
143
146
|
sycommon/auth/ldap_service.py,sha256=fOcpVov5LWJkBk62qbTaltks1c4la7JsbD104KfdBOI,10102
|
|
@@ -149,16 +152,18 @@ sycommon/config/EmbeddingConfig.py,sha256=gPKwiDYbeu1GpdIZXMmgqM7JqBIzCXi0yYuGRL
|
|
|
149
152
|
sycommon/config/LLMConfig.py,sha256=pjMiUgsUaKHw6WNi3weL2ilE9nFua5MkXg96kgwtuzY,510
|
|
150
153
|
sycommon/config/LangfuseConfig.py,sha256=t2LulAtnMUvIINOKHXNWlT5PtgNb7IuaHURjWlbma38,370
|
|
151
154
|
sycommon/config/MQConfig.py,sha256=_RDcmIdyWKjmgM5ZnriOoI-DpaxgXs7CD0awdAD6z88,252
|
|
155
|
+
sycommon/config/PgConfig.py,sha256=Hs9LwgIxSBxcFP16oq18N6Gq9hU2qVl4-7bPfd-ON_s,2333
|
|
152
156
|
sycommon/config/RedisConfig.py,sha256=gIa4BS8L_HdmBg9Dkv3cuIK6CU9zt9RodZOJUuUlh5Y,5235
|
|
153
157
|
sycommon/config/RerankerConfig.py,sha256=35sVwzus2IscvTHnCG63Orl2pC-pMsrVi6wAGDmOH3U,341
|
|
154
158
|
sycommon/config/SentryConfig.py,sha256=OsLb3G9lTsCSZ7tWkcXWJHmvfILQopBxje5pjnkFJfo,320
|
|
155
159
|
sycommon/config/XxlJobConfig.py,sha256=VSG6dn9ysfUVunOs7PqugyZUGJWmX_cEePz2ZCfqHtU,392
|
|
156
160
|
sycommon/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
157
161
|
sycommon/database/async_base_db_service.py,sha256=w6ONUiTtF4-bXRnkBt9QpL9BAy0XUDbQG7F9Hf2rfjw,1337
|
|
158
|
-
sycommon/database/async_database_service.py,sha256=
|
|
162
|
+
sycommon/database/async_database_service.py,sha256=Cf3RaO3skP6IAJrkta-CRE-Q1NtjWLPLUe9hazB8LRM,4873
|
|
159
163
|
sycommon/database/base_db_service.py,sha256=J5ELHMNeGfzA6zVcASPSPZ0XNKrRY3_gdGmVkZw3Mto,946
|
|
160
164
|
sycommon/database/database_service.py,sha256=IMoJ9554dYkr6QfRofvNa0VR24U1WQDz_ATrg0-6EQ0,3857
|
|
161
165
|
sycommon/database/elasticsearch_service.py,sha256=qm490GRlxZlYsQgyfyclSbARRP1-Tc4Lwav3lbPINvQ,3092
|
|
166
|
+
sycommon/database/pg_checkpoint_service.py,sha256=LCwJ9ZADIXkE715Fi3ySkNDDwlY3PC6ZSi34ZYXzBbs,4418
|
|
162
167
|
sycommon/database/redis_service.py,sha256=tPw8UgeuyYQBxWfPRjx7VqlSRFNxIsnR0WSGd36GaA8,20509
|
|
163
168
|
sycommon/database/token_usage_db_service.py,sha256=_hoeB4lYPhDOlJLaUIHIl7z-DNpzsRYrPn5oboD1Y38,6254
|
|
164
169
|
sycommon/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -171,7 +176,7 @@ sycommon/heartbeat_process/heartbeat_process_manager.py,sha256=24qUKs8qegdWHqcox
|
|
|
171
176
|
sycommon/heartbeat_process/heartbeat_process_worker.py,sha256=duuAEFwda43Y6pZE8tOOspitlyxecaFsg1n1iH9jQDQ,16863
|
|
172
177
|
sycommon/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
173
178
|
sycommon/llm/embedding.py,sha256=Xwmg1HcgbdW7OcpYyzu8k7U-27rC5lzjIqbg221CcyY,19129
|
|
174
|
-
sycommon/llm/get_llm.py,sha256=
|
|
179
|
+
sycommon/llm/get_llm.py,sha256=9F2EJhP3ujbk78h1jW8T9Jafb-79U2QAUutYaSbTHvY,7572
|
|
175
180
|
sycommon/llm/llm_logger.py,sha256=LLXiESwDP5f8dB50nFabShVoLKv8UCf2ll69zo1FOso,3365
|
|
176
181
|
sycommon/llm/llm_tokens.py,sha256=yGEessxfk5wRMrPGyWHhiiIIQFDVT23FSaqnwqHGCoY,4712
|
|
177
182
|
sycommon/llm/llm_with_token_tracking.py,sha256=vrdH5LlXg1y2glDPJVawT9A-JoXL9qnvMfHAU3CNy70,12928
|
|
@@ -234,8 +239,8 @@ sycommon/sse/sse.py,sha256=OQ3ElV8WCi-AD3-e0nbiUF28Syf6GRpGztneWTn77EM,10356
|
|
|
234
239
|
sycommon/synacos/__init__.py,sha256=Re9YKVjL62AZURejgSQ3-OvIiMXY-KeAAjIcRJ8PsO0,329
|
|
235
240
|
sycommon/synacos/example.py,sha256=FOnBkvodR8WF_jf-RovM3ngVmvZQX6wKwMLscUTGn2M,8707
|
|
236
241
|
sycommon/synacos/example2.py,sha256=yYuQscfHUIl1HLZ8kSRBuZpHUcNWZMi5H3Mb-LjYnvk,8136
|
|
237
|
-
sycommon/synacos/feign.py,sha256=
|
|
238
|
-
sycommon/synacos/feign_client.py,sha256=
|
|
242
|
+
sycommon/synacos/feign.py,sha256=RU6p2gRP3LZoHYBBEUUY9z5KKzGiUmwj4qdo7aF9UNA,12610
|
|
243
|
+
sycommon/synacos/feign_client.py,sha256=i6O20JWl1g2fjD-et7olFaO-0z2yEbKT8-pKluAHCgs,19917
|
|
239
244
|
sycommon/synacos/nacos_client_base.py,sha256=iP5kLkBD2VOrx6X8v6_RnC9NWiBWmL6-Bgf493QOtxc,6899
|
|
240
245
|
sycommon/synacos/nacos_config_manager.py,sha256=Swqsd9X2xO5-x2VfKUrq8HjzRJn8JBPDqyXazWlF-T4,6859
|
|
241
246
|
sycommon/synacos/nacos_heartbeat_manager.py,sha256=LfimUKpG4KaqsVQl150sg3MLK8psanuUwQ07tjL3uBE,10963
|
|
@@ -260,8 +265,8 @@ sycommon/tools/syemail.py,sha256=BDFhgf7WDOQeTcjxJEQdu0dQhnHFPO_p3eI0-Ni3LhQ,561
|
|
|
260
265
|
sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
|
|
261
266
|
sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
|
|
262
267
|
sycommon/xxljob/xxljob_service.py,sha256=JIEJaGXhqrTLcyxlyynSrsHg9bBnDNzX-D4qIWLRPUE,6815
|
|
263
|
-
sycommon_python_lib-0.2.
|
|
264
|
-
sycommon_python_lib-0.2.
|
|
265
|
-
sycommon_python_lib-0.2.
|
|
266
|
-
sycommon_python_lib-0.2.
|
|
267
|
-
sycommon_python_lib-0.2.
|
|
268
|
+
sycommon_python_lib-0.2.4.dist-info/METADATA,sha256=EvJmf8i4ZkIwoaj0n_KVTUHnAeBGXKF2mI1kjU4EKnI,7877
|
|
269
|
+
sycommon_python_lib-0.2.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
270
|
+
sycommon_python_lib-0.2.4.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
|
|
271
|
+
sycommon_python_lib-0.2.4.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
|
|
272
|
+
sycommon_python_lib-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
{sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.3a11.dist-info → sycommon_python_lib-0.2.4.dist-info}/top_level.txt
RENAMED
|
File without changes
|