sycommon-python-lib 0.2.0b17__tar.gz → 0.2.0b19__tar.gz
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_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/pyproject.toml +1 -1
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/database/async_base_db_service.py +0 -6
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/database/base_db_service.py +0 -6
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/get_llm.py +18 -13
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/struct_token.py +38 -15
- sycommon_python_lib-0.2.0b19/src/sycommon/llm/usage_token.py +264 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/notice/uvicorn_monitor.py +160 -2
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_pool.py +102 -2
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_heartbeat_manager.py +73 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_service_discovery.py +54 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- sycommon_python_lib-0.2.0b17/src/sycommon/llm/usage_token.py +0 -126
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/README.md +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/setup.cfg +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/console.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/models.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/project.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/command/utils.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/01_basic_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/02_tool_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/03_structured_output.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/04_memory_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/05_streaming.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/06_multi_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/07_skills_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/08_middleware.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/09_interrupt.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/10_custom_llm.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/11_complex_workflow.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/12_batch_processing.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/01_basic_monitoring.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/02_permission_control.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/03_tool_skill_filter.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/04_caching_retry.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/05_sanitization.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/06_tracking.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/07_advanced.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/08_progressive_skills.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/examples/middleware/override_examples.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/agent/get_agent.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/LangfuseConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/SentryConfig.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/database/async_database_service.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/embedding.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/llm_logger.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/llm_tokens.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/sy_langfuse.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/async_sql_logger.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/logger_levels.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/notice/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_client.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service_client_manager.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service_core.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/rabbitmq/rabbitmq_service_producer_manager.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/sentry/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/sentry/sy_sentry.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_client_base.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_config_manager.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/nacos_service_registration.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tests/test_email.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tests/test_mq.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/async_utils.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/env.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/merge_headers.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/syemail.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
|
@@ -8,13 +8,7 @@ from sycommon.logging.kafka_log import SYLogger
|
|
|
8
8
|
class AsyncBaseDBService(metaclass=SingletonMeta):
|
|
9
9
|
"""数据库操作基础服务类,封装异步会话管理功能"""
|
|
10
10
|
|
|
11
|
-
_initialized: bool = False
|
|
12
|
-
|
|
13
11
|
def __init__(self):
|
|
14
|
-
if AsyncBaseDBService._initialized:
|
|
15
|
-
return
|
|
16
|
-
AsyncBaseDBService._initialized = True
|
|
17
|
-
|
|
18
12
|
# 获取异步引擎 (假设 DatabaseService.engine() 返回的是 AsyncEngine)
|
|
19
13
|
self.engine = AsyncDatabaseService.engine()
|
|
20
14
|
|
|
@@ -8,13 +8,7 @@ from sycommon.logging.kafka_log import SYLogger
|
|
|
8
8
|
class BaseDBService(metaclass=SingletonMeta):
|
|
9
9
|
"""数据库操作基础服务类,封装会话管理功能"""
|
|
10
10
|
|
|
11
|
-
_initialized: bool = False
|
|
12
|
-
|
|
13
11
|
def __init__(self):
|
|
14
|
-
if BaseDBService._initialized:
|
|
15
|
-
return
|
|
16
|
-
BaseDBService._initialized = True
|
|
17
|
-
|
|
18
12
|
self.engine = DatabaseService.engine()
|
|
19
13
|
self.Session = sessionmaker(bind=self.engine)
|
|
20
14
|
|
|
@@ -2,7 +2,7 @@ from sycommon.llm.llm_logger import LLMLogger
|
|
|
2
2
|
from langchain.chat_models import init_chat_model
|
|
3
3
|
from sycommon.config.LLMConfig import LLMConfig
|
|
4
4
|
from sycommon.llm.sy_langfuse import LangfuseInitializer
|
|
5
|
-
from sycommon.llm.usage_token import LLMWithAutoTokenUsage
|
|
5
|
+
from sycommon.llm.usage_token import LLMWithAutoTokenUsage, LLMWithTokenTracking
|
|
6
6
|
from typing import Any, Union
|
|
7
7
|
from langchain_core.language_models import BaseChatModel
|
|
8
8
|
|
|
@@ -16,19 +16,19 @@ def get_llm(
|
|
|
16
16
|
max_retries: int = 3,
|
|
17
17
|
wrap_structured: bool = True,
|
|
18
18
|
**kwargs: Any
|
|
19
|
-
) -> Union[LLMWithAutoTokenUsage, BaseChatModel]:
|
|
19
|
+
) -> Union[LLMWithAutoTokenUsage, LLMWithTokenTracking, BaseChatModel]:
|
|
20
20
|
"""
|
|
21
21
|
获取LLM实例
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
|
-
model: 模型名称,默认为
|
|
24
|
+
model: 模型名称,默认为 Qwen3.5-122B-A10B
|
|
25
25
|
streaming: 是否启用流式输出
|
|
26
26
|
temperature: 温度参数
|
|
27
27
|
timeout: 请求超时时间(秒),默认180秒
|
|
28
28
|
max_retries: 最大重试次数,默认3次
|
|
29
29
|
wrap_structured: 是否包装为结构化输出实例,默认True
|
|
30
30
|
- True: 返回 LLMWithAutoTokenUsage,支持 with_structured_output()
|
|
31
|
-
- False:
|
|
31
|
+
- False: 返回 LLMWithTokenTracking,保留日志、Langfuse 跟踪和 Token 统计
|
|
32
32
|
**kwargs: 其他透传参数,支持 langchain init_chat_model 所有参数
|
|
33
33
|
- presence_penalty: 存在惩罚
|
|
34
34
|
- extra_body: 额外请求体参数,如 {"top_k": 20, "chat_template_kwargs": {"enable_thinking": False}}
|
|
@@ -38,17 +38,20 @@ def get_llm(
|
|
|
38
38
|
- summary_prompt: 结构化输出时的摘要提示词(仅 wrap_structured=True 时有效)
|
|
39
39
|
|
|
40
40
|
Returns:
|
|
41
|
-
LLMWithAutoTokenUsage | BaseChatModel: 根据 wrap_structured 参数返回对应类型
|
|
41
|
+
LLMWithAutoTokenUsage | LLMWithTokenTracking | BaseChatModel: 根据 wrap_structured 参数返回对应类型
|
|
42
42
|
|
|
43
43
|
Example:
|
|
44
44
|
```python
|
|
45
|
-
#
|
|
46
|
-
llm = get_llm("
|
|
45
|
+
# 结构化输出(默认使用原生模式)
|
|
46
|
+
llm = get_llm("Qwen3.5-122B-A10B")
|
|
47
47
|
chain = llm.with_structured_output(MyModel)
|
|
48
|
+
result = await chain.ainvoke([HumanMessage(content="你好")])
|
|
49
|
+
print(result._token_usage_) # Token 统计
|
|
48
50
|
|
|
49
|
-
# 普通 LLM
|
|
50
|
-
llm = get_llm("
|
|
51
|
+
# 普通 LLM 调用,保留日志、Langfuse 和 Token 统计
|
|
52
|
+
llm = get_llm("Qwen3.5-122B-A10B", wrap_structured=False)
|
|
51
53
|
response = await llm.ainvoke([HumanMessage(content="你好")])
|
|
54
|
+
print(response._token_usage_) # Token 统计
|
|
52
55
|
|
|
53
56
|
# 透传额外参数
|
|
54
57
|
llm = get_llm(
|
|
@@ -63,7 +66,6 @@ def get_llm(
|
|
|
63
66
|
```
|
|
64
67
|
"""
|
|
65
68
|
if not model or model == "Qwen2.5-72B":
|
|
66
|
-
# model = "Qwen2.5-72B"
|
|
67
69
|
model = "Qwen3.5-122B-A10B"
|
|
68
70
|
kwargs["presence_penalty"] = 0
|
|
69
71
|
kwargs["extra_body"] = {
|
|
@@ -99,11 +101,14 @@ def get_llm(
|
|
|
99
101
|
if llm is None:
|
|
100
102
|
raise Exception(f"初始化原始LLM实例失败:{model}")
|
|
101
103
|
|
|
102
|
-
#
|
|
104
|
+
# 如果不需要结构化输出包装,返回带 Token 统计的包装类
|
|
103
105
|
if not wrap_structured:
|
|
104
|
-
return llm
|
|
106
|
+
return LLMWithTokenTracking(llm, langfuse, llmConfig)
|
|
105
107
|
|
|
106
108
|
# 获取kwargs中summary_prompt参数
|
|
107
109
|
summary_prompt: str = kwargs.get("summary_prompt")
|
|
108
110
|
|
|
109
|
-
return LLMWithAutoTokenUsage(
|
|
111
|
+
return LLMWithAutoTokenUsage(
|
|
112
|
+
llm, langfuse, llmConfig, summary_prompt,
|
|
113
|
+
max_retries=max_retries
|
|
114
|
+
)
|
{sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/llm/struct_token.py
RENAMED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import tiktoken
|
|
2
|
-
from typing import Dict, List, Optional, Any
|
|
3
|
-
from langfuse import Langfuse, LangfuseSpan, propagate_attributes
|
|
4
|
-
from sycommon.llm.llm_logger import LLMLogger
|
|
5
|
-
from langchain_core.runnables import Runnable, RunnableConfig
|
|
6
|
-
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
|
|
7
|
-
from sycommon.llm.llm_tokens import TokensCallbackHandler
|
|
8
|
-
from sycommon.logging.kafka_log import SYLogger
|
|
9
|
-
from sycommon.config.LLMConfig import LLMConfig
|
|
10
|
-
from sycommon.tools.env import get_env_var
|
|
11
1
|
from sycommon.tools.merge_headers import get_header_value
|
|
2
|
+
from sycommon.tools.env import get_env_var
|
|
3
|
+
from sycommon.config.LLMConfig import LLMConfig
|
|
4
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
5
|
+
from sycommon.llm.llm_tokens import TokensCallbackHandler
|
|
6
|
+
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
|
|
7
|
+
from langchain_core.runnables import Runnable, RunnableConfig
|
|
8
|
+
from sycommon.llm.llm_logger import LLMLogger
|
|
9
|
+
from langfuse import Langfuse, LangfuseSpan, propagate_attributes
|
|
10
|
+
from typing import Dict, List, Optional, Any
|
|
11
|
+
import tiktoken
|
|
12
|
+
import warnings
|
|
13
|
+
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class StructuredRunnableWithToken(Runnable):
|
|
15
17
|
"""
|
|
16
18
|
统一功能 Runnable:Trace追踪 + Token统计 + 自动上下文压缩
|
|
19
|
+
|
|
20
|
+
支持两种模式:
|
|
21
|
+
1. 兼容模式(is_native_mode=False):retry_chain 期望输入 {"messages": [...]}
|
|
22
|
+
2. 原生模式(is_native_mode=True):retry_chain 直接接收消息列表
|
|
17
23
|
"""
|
|
18
24
|
|
|
19
25
|
def __init__(
|
|
@@ -26,7 +32,8 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
26
32
|
enable_compression: bool = True,
|
|
27
33
|
threshold_ratio: float = 0.8,
|
|
28
34
|
keep_last_n: int = 2,
|
|
29
|
-
max_compression_attempts: int = 5
|
|
35
|
+
max_compression_attempts: int = 5,
|
|
36
|
+
is_native_mode: bool = False
|
|
30
37
|
):
|
|
31
38
|
super().__init__()
|
|
32
39
|
self.retry_chain = retry_chain
|
|
@@ -38,6 +45,7 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
38
45
|
self.threshold_ratio = threshold_ratio
|
|
39
46
|
self.keep_last_n = keep_last_n # 保留最近N条消息不压缩
|
|
40
47
|
self.max_compression_attempts = max_compression_attempts # 最大压缩尝试次数
|
|
48
|
+
self.is_native_mode = is_native_mode # 是否为原生模式
|
|
41
49
|
|
|
42
50
|
# 初始化 Tokenizer
|
|
43
51
|
try:
|
|
@@ -80,7 +88,8 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
80
88
|
max_tokens = int(self.llmConfig.maxTokens * self.threshold_ratio)
|
|
81
89
|
# 计算每批消息的压缩阈值:平均分配可用 token 空间
|
|
82
90
|
# 预留给 system_msgs 和 keep_recent 的 token 空间
|
|
83
|
-
reserved_tokens = self._count_tokens(
|
|
91
|
+
reserved_tokens = self._count_tokens(
|
|
92
|
+
system_msgs) + self._count_tokens(conversation[-self.keep_last_n:])
|
|
84
93
|
available_tokens = max_tokens - reserved_tokens
|
|
85
94
|
|
|
86
95
|
current_messages = messages
|
|
@@ -162,7 +171,8 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
162
171
|
|
|
163
172
|
# 根据目标 token 计算压缩比例
|
|
164
173
|
if target_tokens and original_tokens > 0:
|
|
165
|
-
compression_ratio = max(
|
|
174
|
+
compression_ratio = max(
|
|
175
|
+
0.3, min(0.7, target_tokens / original_tokens))
|
|
166
176
|
ratio_text = f"{int(compression_ratio * 100)}%"
|
|
167
177
|
else:
|
|
168
178
|
compression_ratio = 0.5
|
|
@@ -331,7 +341,14 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
331
341
|
# 【同步模式下不建议触发压缩,因为压缩本身是异步调用 LLM】
|
|
332
342
|
# 如果同步也要压缩,需要用 asyncio.run(...),这里暂时保持原逻辑直接透传
|
|
333
343
|
adapted_input = self._adapt_input(input)
|
|
334
|
-
|
|
344
|
+
|
|
345
|
+
# 根据模式选择输入格式
|
|
346
|
+
if self.is_native_mode:
|
|
347
|
+
# 原生模式:直接传递消息列表
|
|
348
|
+
input_data = adapted_input
|
|
349
|
+
else:
|
|
350
|
+
# 兼容模式:包装成 {"messages": [...]}
|
|
351
|
+
input_data = {"messages": adapted_input}
|
|
335
352
|
|
|
336
353
|
if span:
|
|
337
354
|
span.update_trace(input=input_data)
|
|
@@ -382,7 +399,13 @@ class StructuredRunnableWithToken(Runnable):
|
|
|
382
399
|
# 执行压缩,替换 adapted_input
|
|
383
400
|
adapted_input = await self._acompress_context(adapted_input)
|
|
384
401
|
|
|
385
|
-
|
|
402
|
+
# 根据模式选择输入格式
|
|
403
|
+
if self.is_native_mode:
|
|
404
|
+
# 原生模式:直接传递消息列表
|
|
405
|
+
input_data = adapted_input
|
|
406
|
+
else:
|
|
407
|
+
# 兼容模式:包装成 {"messages": [...]}
|
|
408
|
+
input_data = {"messages": adapted_input}
|
|
386
409
|
|
|
387
410
|
if span:
|
|
388
411
|
span.update_trace(input=input_data)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from typing import Type, List, Optional, Callable
|
|
2
|
+
from langfuse import Langfuse
|
|
3
|
+
from langchain_core.language_models import BaseChatModel
|
|
4
|
+
from langchain_core.runnables import Runnable, RunnableLambda
|
|
5
|
+
from langchain_core.output_parsers import PydanticOutputParser
|
|
6
|
+
from langchain_core.messages import BaseMessage, HumanMessage
|
|
7
|
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
8
|
+
from pydantic import BaseModel, ValidationError, Field
|
|
9
|
+
from sycommon.config.LLMConfig import LLMConfig
|
|
10
|
+
from sycommon.llm.struct_token import StructuredRunnableWithToken
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LLMWithTokenTracking(BaseChatModel):
|
|
14
|
+
"""
|
|
15
|
+
带Token统计的LLM包装类(非结构化输出场景)
|
|
16
|
+
用于 wrap_structured=False 时的 Token 统计和 Langfuse 追踪
|
|
17
|
+
|
|
18
|
+
设计原则:最小化重写,只重写核心的 _generate 和 _agenerate
|
|
19
|
+
所有其他方法(invoke, ainvoke, batch, abatch, stream, astream)
|
|
20
|
+
都会自动通过 BaseChatModel 的默认实现调用这两个核心方法
|
|
21
|
+
"""
|
|
22
|
+
llm: BaseChatModel = Field(default=None)
|
|
23
|
+
langfuse: Optional[Langfuse] = Field(default=None, exclude=True)
|
|
24
|
+
llmConfig: Optional[LLMConfig] = Field(default=None, exclude=True)
|
|
25
|
+
|
|
26
|
+
def __init__(self, llm: BaseChatModel, langfuse: Langfuse = None, llmConfig: LLMConfig = None, **kwargs):
|
|
27
|
+
super().__init__(llm=llm, langfuse=langfuse, llmConfig=llmConfig, **kwargs)
|
|
28
|
+
|
|
29
|
+
def _generate(self, messages, stop=None, run_manager=None, **kwargs):
|
|
30
|
+
"""
|
|
31
|
+
同步生成 - 核心方法
|
|
32
|
+
BaseChatModel 的 invoke/batch/stream 最终都会调用此方法
|
|
33
|
+
"""
|
|
34
|
+
result = self.llm._generate(
|
|
35
|
+
messages, stop=stop, run_manager=run_manager, **kwargs)
|
|
36
|
+
return self._inject_token_usage(result)
|
|
37
|
+
|
|
38
|
+
async def _agenerate(self, messages, stop=None, run_manager=None, **kwargs):
|
|
39
|
+
"""
|
|
40
|
+
异步生成 - 核心方法
|
|
41
|
+
BaseChatModel 的 ainvoke/abatch/astream 最终都会调用此方法
|
|
42
|
+
"""
|
|
43
|
+
result = await self.llm._agenerate(messages, stop=stop, run_manager=run_manager, **kwargs)
|
|
44
|
+
return self._inject_token_usage(result)
|
|
45
|
+
|
|
46
|
+
def _inject_token_usage(self, result):
|
|
47
|
+
"""
|
|
48
|
+
从 LLM 结果中提取 Token 统计并注入到响应消息中
|
|
49
|
+
支持单条和多条 generations
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
if hasattr(result, 'generations') and result.generations:
|
|
53
|
+
for gen_group in result.generations:
|
|
54
|
+
for generation in gen_group:
|
|
55
|
+
if hasattr(generation, 'message') and generation.message:
|
|
56
|
+
# 优先从 generation_info 获取
|
|
57
|
+
if hasattr(generation, 'generation_info') and generation.generation_info:
|
|
58
|
+
gen_info = generation.generation_info
|
|
59
|
+
usage = {
|
|
60
|
+
"input_tokens": gen_info.get('input_tokens', gen_info.get('prompt_tokens', 0)),
|
|
61
|
+
"output_tokens": gen_info.get('output_tokens', gen_info.get('completion_tokens', 0)),
|
|
62
|
+
"total_tokens": gen_info.get('total_tokens', 0)
|
|
63
|
+
}
|
|
64
|
+
generation.message._token_usage_ = usage
|
|
65
|
+
# 从 llm_output 获取(部分模型)
|
|
66
|
+
elif hasattr(result, 'llm_output') and result.llm_output:
|
|
67
|
+
llm_output = result.llm_output
|
|
68
|
+
if 'token_usage' in llm_output:
|
|
69
|
+
token_usage = llm_output['token_usage']
|
|
70
|
+
usage = {
|
|
71
|
+
"input_tokens": token_usage.get('prompt_tokens', token_usage.get('input_tokens', 0)),
|
|
72
|
+
"output_tokens": token_usage.get('completion_tokens', token_usage.get('output_tokens', 0)),
|
|
73
|
+
"total_tokens": token_usage.get('total_tokens', 0)
|
|
74
|
+
}
|
|
75
|
+
generation.message._token_usage_ = usage
|
|
76
|
+
except Exception as e:
|
|
77
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
78
|
+
SYLogger.warning(f"Token 统计注入失败: {str(e)}")
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def _llm_type(self) -> str:
|
|
84
|
+
return self.llm._llm_type
|
|
85
|
+
|
|
86
|
+
def __getattr__(self, name):
|
|
87
|
+
"""代理所有其他属性到原始 llm(如 model_name, max_tokens, with_structured_output 等)"""
|
|
88
|
+
try:
|
|
89
|
+
return super().__getattribute__(name)
|
|
90
|
+
except AttributeError:
|
|
91
|
+
return getattr(self.llm, name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class LLMWithAutoTokenUsage(BaseChatModel):
|
|
95
|
+
"""自动为结构化调用返回token_usage的LLM包装类"""
|
|
96
|
+
llm: BaseChatModel = Field(default=None)
|
|
97
|
+
langfuse: Optional[Langfuse] = Field(default=None, exclude=True)
|
|
98
|
+
llmConfig: Optional[LLMConfig] = Field(default=None, exclude=True)
|
|
99
|
+
summary_prompt: Optional[str] = Field(default=None, exclude=True)
|
|
100
|
+
max_retries: int = Field(default=3, exclude=True)
|
|
101
|
+
|
|
102
|
+
def __init__(self, llm: BaseChatModel, langfuse: Langfuse, llmConfig: LLMConfig, summary_prompt: str, max_retries: int = 3, **kwargs):
|
|
103
|
+
super().__init__(llm=llm, langfuse=langfuse, llmConfig=llmConfig,
|
|
104
|
+
summary_prompt=summary_prompt, max_retries=max_retries, **kwargs)
|
|
105
|
+
|
|
106
|
+
def with_structured_output(
|
|
107
|
+
self,
|
|
108
|
+
output_model: Type[BaseModel],
|
|
109
|
+
max_retries: int = None,
|
|
110
|
+
is_extract: bool = False,
|
|
111
|
+
override_prompt: ChatPromptTemplate = None,
|
|
112
|
+
custom_processors: Optional[List[Callable[[str], str]]] = None,
|
|
113
|
+
custom_parser: Optional[Callable[[str], BaseModel]] = None,
|
|
114
|
+
use_native: bool = None # True/None: 原生模式(默认), False: 兼容模式
|
|
115
|
+
) -> Runnable:
|
|
116
|
+
"""
|
|
117
|
+
返回支持自动统计Token的结构化Runnable
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
output_model: Pydantic 模型
|
|
121
|
+
max_retries: 最大重试次数
|
|
122
|
+
is_extract: 是否为提取模式
|
|
123
|
+
override_prompt: 自定义提示词模板
|
|
124
|
+
custom_processors: 自定义文本处理器
|
|
125
|
+
custom_parser: 自定义解析器
|
|
126
|
+
use_native: 是否使用原生结构化输出(默认True)
|
|
127
|
+
- True/None: 使用原生模式(推荐)
|
|
128
|
+
- False: 使用兼容模式(仅用于不支持原生的旧模型)
|
|
129
|
+
"""
|
|
130
|
+
# 使用实例的max_retries作为默认值
|
|
131
|
+
if max_retries is None:
|
|
132
|
+
max_retries = self.max_retries
|
|
133
|
+
|
|
134
|
+
# 默认使用原生模式,仅当显式指定 False 时才使用兼容模式
|
|
135
|
+
if use_native is False:
|
|
136
|
+
return self._with_compatible_structured_output(
|
|
137
|
+
output_model, max_retries, is_extract, override_prompt, custom_processors, custom_parser
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
return self._with_native_structured_output(output_model, max_retries)
|
|
141
|
+
|
|
142
|
+
def _with_native_structured_output(
|
|
143
|
+
self,
|
|
144
|
+
output_model: Type[BaseModel],
|
|
145
|
+
max_retries: int
|
|
146
|
+
) -> Runnable:
|
|
147
|
+
"""
|
|
148
|
+
使用模型原生的 with_structured_output 方法
|
|
149
|
+
适用于:Qwen3.5-122B-A10B 等原生支持的模型
|
|
150
|
+
"""
|
|
151
|
+
# 调用原生方法
|
|
152
|
+
native_runnable = self.llm.with_structured_output(output_model)
|
|
153
|
+
|
|
154
|
+
# 包装为支持 Token 统计和 Langfuse 追踪的 Runnable
|
|
155
|
+
return StructuredRunnableWithToken(
|
|
156
|
+
retry_chain=native_runnable,
|
|
157
|
+
langfuse=self.langfuse,
|
|
158
|
+
llmConfig=self.llmConfig,
|
|
159
|
+
summary_prompt=self.summary_prompt,
|
|
160
|
+
model_name=self.llmConfig.model if self.llmConfig else "Qwen2.5-72B",
|
|
161
|
+
is_native_mode=True
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _with_compatible_structured_output(
|
|
165
|
+
self,
|
|
166
|
+
output_model: Type[BaseModel],
|
|
167
|
+
max_retries: int,
|
|
168
|
+
is_extract: bool,
|
|
169
|
+
override_prompt: ChatPromptTemplate,
|
|
170
|
+
custom_processors: Optional[List[Callable[[str], str]]],
|
|
171
|
+
custom_parser: Optional[Callable[[str], BaseModel]]
|
|
172
|
+
) -> Runnable:
|
|
173
|
+
"""
|
|
174
|
+
使用兼容模式(手动解析 JSON)
|
|
175
|
+
适用于:不支持原生结构化输出的模型
|
|
176
|
+
"""
|
|
177
|
+
parser = PydanticOutputParser(pydantic_object=output_model)
|
|
178
|
+
|
|
179
|
+
# 提示词模板
|
|
180
|
+
accuracy_instructions = """
|
|
181
|
+
字段值的抽取准确率(0~1之间),评分规则:
|
|
182
|
+
1.0(完全准确):直接从原文提取,无需任何加工,且格式与原文完全一致
|
|
183
|
+
0.9(轻微处理):数据来源明确,但需进行格式标准化或冗余信息剔除(不改变原始数值)
|
|
184
|
+
0.8(有限推断):数据需通过上下文关联或简单计算得出,仍有明确依据
|
|
185
|
+
0.8以下(不可靠):数据需大量推测、存在歧义或来源不明,处理方式:直接忽略该数据,设置为None
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
if is_extract:
|
|
189
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
190
|
+
MessagesPlaceholder(variable_name="messages"),
|
|
191
|
+
HumanMessage(content=f"""
|
|
192
|
+
请提取信息并遵循以下规则:
|
|
193
|
+
1. 准确率要求:{accuracy_instructions.strip()}
|
|
194
|
+
2. 输出格式:{parser.get_format_instructions()}
|
|
195
|
+
""")
|
|
196
|
+
])
|
|
197
|
+
else:
|
|
198
|
+
prompt = override_prompt or ChatPromptTemplate.from_messages([
|
|
199
|
+
MessagesPlaceholder(variable_name="messages"),
|
|
200
|
+
HumanMessage(content=f"""
|
|
201
|
+
输出格式:{parser.get_format_instructions()}
|
|
202
|
+
""")
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
# 文本处理函数
|
|
206
|
+
def extract_response_content(response: BaseMessage) -> str:
|
|
207
|
+
try:
|
|
208
|
+
return response.content
|
|
209
|
+
except Exception as e:
|
|
210
|
+
raise ValueError(f"提取响应内容失败:{str(e)}") from e
|
|
211
|
+
|
|
212
|
+
def strip_code_block_markers(content: str) -> str:
|
|
213
|
+
try:
|
|
214
|
+
return content.strip("```json").strip("```").strip()
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise ValueError(f"移除代码块标记失败:{str(e)}") from e
|
|
217
|
+
|
|
218
|
+
def normalize_in_json(content: str) -> str:
|
|
219
|
+
try:
|
|
220
|
+
return content.replace("None", "null").replace("none", "null").replace("NONE", "null").replace("''", '""')
|
|
221
|
+
except Exception as e:
|
|
222
|
+
raise ValueError(f"JSON格式化失败:{str(e)}") from e
|
|
223
|
+
|
|
224
|
+
def default_parse_to_pydantic(content: str) -> BaseModel:
|
|
225
|
+
try:
|
|
226
|
+
return parser.parse(content)
|
|
227
|
+
except (ValidationError, ValueError) as e:
|
|
228
|
+
raise ValueError(f"解析结构化结果失败:{str(e)}") from e
|
|
229
|
+
|
|
230
|
+
# ========== 构建处理链 ==========
|
|
231
|
+
base_chain = prompt | self.llm | RunnableLambda(
|
|
232
|
+
extract_response_content)
|
|
233
|
+
|
|
234
|
+
# 文本处理链
|
|
235
|
+
process_runnables = custom_processors or [
|
|
236
|
+
RunnableLambda(strip_code_block_markers),
|
|
237
|
+
RunnableLambda(normalize_in_json)
|
|
238
|
+
]
|
|
239
|
+
process_chain = base_chain
|
|
240
|
+
for runnable in process_runnables:
|
|
241
|
+
process_chain = process_chain | runnable
|
|
242
|
+
|
|
243
|
+
# 解析链
|
|
244
|
+
parse_chain = process_chain | RunnableLambda(
|
|
245
|
+
custom_parser or default_parse_to_pydantic)
|
|
246
|
+
|
|
247
|
+
# 重试链
|
|
248
|
+
retry_chain = parse_chain.with_retry(
|
|
249
|
+
retry_if_exception_type=(ValidationError, ValueError),
|
|
250
|
+
stop_after_attempt=max_retries,
|
|
251
|
+
wait_exponential_jitter=True,
|
|
252
|
+
exponential_jitter_params={
|
|
253
|
+
"initial": 0.1, "max": 3.0, "exp_base": 2.0, "jitter": 1.0}
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return StructuredRunnableWithToken(retry_chain, self.langfuse, self.llmConfig, self.summary_prompt)
|
|
257
|
+
|
|
258
|
+
# ========== 实现BaseChatModel抽象方法 ==========
|
|
259
|
+
def _generate(self, messages, stop=None, run_manager=None, ** kwargs):
|
|
260
|
+
return self.llm._generate(messages, stop=stop, run_manager=run_manager, ** kwargs)
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def _llm_type(self) -> str:
|
|
264
|
+
return self.llm._llm_type
|
{sycommon_python_lib-0.2.0b17 → sycommon_python_lib-0.2.0b19}/src/sycommon/notice/uvicorn_monitor.py
RENAMED
|
@@ -9,6 +9,46 @@ from sycommon.config.Config import Config
|
|
|
9
9
|
from sycommon.logging.kafka_log import SYLogger
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def get_webhook() -> Optional[str]:
|
|
13
|
+
"""
|
|
14
|
+
获取企业微信 WebHook 配置
|
|
15
|
+
|
|
16
|
+
支持两种配置格式:
|
|
17
|
+
1. 字符串格式(旧版):
|
|
18
|
+
llm:
|
|
19
|
+
WebHook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
|
|
20
|
+
|
|
21
|
+
2. 对象格式(新版,支持启用开关):
|
|
22
|
+
llm:
|
|
23
|
+
WebHook:
|
|
24
|
+
enabled: true
|
|
25
|
+
url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
config = Config().config
|
|
29
|
+
webhook_config = config.get('llm', {}).get('WebHook')
|
|
30
|
+
|
|
31
|
+
if webhook_config is None:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
# 字符串格式(旧版兼容)
|
|
35
|
+
if isinstance(webhook_config, str):
|
|
36
|
+
return webhook_config
|
|
37
|
+
|
|
38
|
+
# 对象格式(新版)
|
|
39
|
+
if isinstance(webhook_config, dict):
|
|
40
|
+
# 检查是否启用
|
|
41
|
+
if not webhook_config.get('enabled', True):
|
|
42
|
+
SYLogger.debug("企业微信 WebHook 已禁用")
|
|
43
|
+
return None
|
|
44
|
+
return webhook_config.get('url')
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
except Exception as e:
|
|
48
|
+
SYLogger.warning(f"读取 WebHook 配置失败: {str(e)}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
12
52
|
async def send_wechat_markdown_msg(
|
|
13
53
|
content: str,
|
|
14
54
|
webhook: str = None
|
|
@@ -55,8 +95,7 @@ async def send_webhook(error_info: dict = None, webhook: str = None):
|
|
|
55
95
|
config = Config().config
|
|
56
96
|
service_name = config.get('Name', "未知服务")
|
|
57
97
|
env = config.get('Nacos', {}).get('namespaceId', '未知环境')
|
|
58
|
-
|
|
59
|
-
webHook = config.get('llm', {}).get('WebHook')
|
|
98
|
+
webHook = get_webhook()
|
|
60
99
|
except Exception as e:
|
|
61
100
|
service_name = "未知服务"
|
|
62
101
|
env = "未知环境"
|
|
@@ -186,3 +225,122 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
186
225
|
|
|
187
226
|
# 只有确实有错误时才以状态码 1 退出
|
|
188
227
|
sys.exit(1)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def send_mq_disconnect_alert(
|
|
231
|
+
error_msg: str,
|
|
232
|
+
host: str = None,
|
|
233
|
+
app_name: str = None,
|
|
234
|
+
disconnect_count: int = 0,
|
|
235
|
+
reconnect_attempts: int = 0,
|
|
236
|
+
is_recovered: bool = False
|
|
237
|
+
) -> Optional[dict]:
|
|
238
|
+
"""
|
|
239
|
+
发送 MQ 连接断开告警
|
|
240
|
+
|
|
241
|
+
:param error_msg: 错误信息
|
|
242
|
+
:param host: RabbitMQ 主机地址
|
|
243
|
+
:param app_name: 应用名称(MQ连接名)
|
|
244
|
+
:param disconnect_count: 累计断开次数
|
|
245
|
+
:param reconnect_attempts: 重连尝试次数
|
|
246
|
+
:param is_recovered: 是否已恢复
|
|
247
|
+
:return: 发送结果
|
|
248
|
+
"""
|
|
249
|
+
try:
|
|
250
|
+
config = Config().config
|
|
251
|
+
service_name = config.get('Name', "未知服务")
|
|
252
|
+
env = config.get('Nacos', {}).get('namespaceId', '未知环境')
|
|
253
|
+
except Exception:
|
|
254
|
+
service_name = "未知服务"
|
|
255
|
+
env = "未知环境"
|
|
256
|
+
|
|
257
|
+
webhook = get_webhook()
|
|
258
|
+
if not webhook:
|
|
259
|
+
SYLogger.debug("未配置企业微信 WebHook,跳过 MQ 断连告警")
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
alert_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
263
|
+
# 显示服务名和应用名
|
|
264
|
+
display_name = f"{service_name}" + (f" ({app_name})" if app_name else "")
|
|
265
|
+
|
|
266
|
+
if is_recovered:
|
|
267
|
+
# 恢复通知
|
|
268
|
+
markdown_content = f"""### {display_name} RabbitMQ 连接已恢复 ✅
|
|
269
|
+
> 环境: <font color="info">{env}</font>
|
|
270
|
+
> 时间: <font color="comment">{alert_time}</font>
|
|
271
|
+
> 节点: <font color="comment">{host or '未知'}</font>
|
|
272
|
+
> 恢复前重试次数: {reconnect_attempts}
|
|
273
|
+
> 本次断开次数: {disconnect_count}"""
|
|
274
|
+
else:
|
|
275
|
+
# 断开告警
|
|
276
|
+
markdown_content = f"""### {display_name} RabbitMQ 连接断开告警 🚨
|
|
277
|
+
> 环境: <font color="warning">{env}</font>
|
|
278
|
+
> 时间: <font color="comment">{alert_time}</font>
|
|
279
|
+
> 节点: <font color="comment">{host or '未知'}</font>
|
|
280
|
+
> 累计断开次数: <font color="danger">{disconnect_count}</font>
|
|
281
|
+
> 重连尝试: {reconnect_attempts} 次
|
|
282
|
+
> 错误信息: <font color="danger">{error_msg}</font>"""
|
|
283
|
+
|
|
284
|
+
return await send_wechat_markdown_msg(
|
|
285
|
+
content=markdown_content,
|
|
286
|
+
webhook=webhook
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
async def send_nacos_disconnect_alert(
|
|
291
|
+
error_msg: str,
|
|
292
|
+
service_name: str = None,
|
|
293
|
+
host: str = None,
|
|
294
|
+
disconnect_count: int = 0,
|
|
295
|
+
fail_count: int = 0,
|
|
296
|
+
is_recovered: bool = False
|
|
297
|
+
) -> Optional[dict]:
|
|
298
|
+
"""
|
|
299
|
+
发送 Nacos 连接断开告警
|
|
300
|
+
|
|
301
|
+
:param error_msg: 错误信息
|
|
302
|
+
:param service_name: 服务名称
|
|
303
|
+
:param host: 服务地址
|
|
304
|
+
:param disconnect_count: 累计断开次数
|
|
305
|
+
:param fail_count: 连续失败次数
|
|
306
|
+
:param is_recovered: 是否已恢复
|
|
307
|
+
:return: 发送结果
|
|
308
|
+
"""
|
|
309
|
+
try:
|
|
310
|
+
config = Config().config
|
|
311
|
+
display_service_name = config.get('Name', "未知服务")
|
|
312
|
+
env = config.get('Nacos', {}).get('namespaceId', '未知环境')
|
|
313
|
+
except Exception:
|
|
314
|
+
display_service_name = "未知服务"
|
|
315
|
+
env = "未知环境"
|
|
316
|
+
|
|
317
|
+
webhook = get_webhook()
|
|
318
|
+
if not webhook:
|
|
319
|
+
SYLogger.debug("未配置企业微信 WebHook,跳过 Nacos 断连告警")
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
alert_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
323
|
+
# 显示服务名
|
|
324
|
+
display_name = service_name or display_service_name
|
|
325
|
+
|
|
326
|
+
if is_recovered:
|
|
327
|
+
# 恢复通知
|
|
328
|
+
markdown_content = f"""### {display_name} Nacos 心跳已恢复 ✅
|
|
329
|
+
> 环境: <font color="info">{env}</font>
|
|
330
|
+
> 时间: <font color="comment">{alert_time}</font>
|
|
331
|
+
> 服务地址: <font color="comment">{host or '未知'}</font>
|
|
332
|
+
> 累计断开次数: {disconnect_count}"""
|
|
333
|
+
else:
|
|
334
|
+
# 断开告警
|
|
335
|
+
markdown_content = f"""### {display_name} Nacos 心跳失败告警 🚨
|
|
336
|
+
> 环境: <font color="warning">{env}</font>
|
|
337
|
+
> 时间: <font color="comment">{alert_time}</font>
|
|
338
|
+
> 服务地址: <font color="comment">{host or '未知'}</font>
|
|
339
|
+
> 累计断开次数: <font color="danger">{disconnect_count}</font>
|
|
340
|
+
> 连续失败: {fail_count} 次
|
|
341
|
+
> 错误信息: <font color="danger">{error_msg}</font>"""
|
|
342
|
+
|
|
343
|
+
return await send_wechat_markdown_msg(
|
|
344
|
+
content=markdown_content,
|
|
345
|
+
webhook=webhook
|
|
346
|
+
)
|