sycommon-python-lib 0.1.56b5__py3-none-any.whl → 0.1.57b1__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.
Files changed (38) hide show
  1. sycommon/config/Config.py +24 -3
  2. sycommon/config/LangfuseConfig.py +15 -0
  3. sycommon/config/SentryConfig.py +13 -0
  4. sycommon/llm/embedding.py +78 -23
  5. sycommon/llm/get_llm.py +9 -218
  6. sycommon/llm/struct_token.py +192 -0
  7. sycommon/llm/sy_langfuse.py +103 -0
  8. sycommon/llm/usage_token.py +117 -0
  9. sycommon/logging/kafka_log.py +187 -433
  10. sycommon/middleware/exception.py +10 -16
  11. sycommon/middleware/timeout.py +2 -1
  12. sycommon/middleware/traceid.py +81 -76
  13. sycommon/notice/uvicorn_monitor.py +32 -27
  14. sycommon/rabbitmq/rabbitmq_client.py +228 -243
  15. sycommon/rabbitmq/rabbitmq_pool.py +201 -123
  16. sycommon/rabbitmq/rabbitmq_service.py +25 -843
  17. sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
  18. sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
  19. sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
  20. sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
  21. sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
  22. sycommon/sentry/__init__.py +0 -0
  23. sycommon/sentry/sy_sentry.py +35 -0
  24. sycommon/services.py +122 -96
  25. sycommon/synacos/nacos_client_base.py +119 -0
  26. sycommon/synacos/nacos_config_manager.py +107 -0
  27. sycommon/synacos/nacos_heartbeat_manager.py +144 -0
  28. sycommon/synacos/nacos_service.py +63 -783
  29. sycommon/synacos/nacos_service_discovery.py +157 -0
  30. sycommon/synacos/nacos_service_registration.py +270 -0
  31. sycommon/tools/env.py +62 -0
  32. sycommon/tools/merge_headers.py +20 -0
  33. sycommon/tools/snowflake.py +101 -153
  34. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +10 -8
  35. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/RECORD +38 -20
  36. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
  37. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
  38. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/top_level.txt +0 -0
sycommon/config/Config.py CHANGED
@@ -15,13 +15,13 @@ class Config(metaclass=SingletonMeta):
15
15
  with open(config_file, 'r', encoding='utf-8') as f:
16
16
  self.config = yaml.safe_load(f)
17
17
  self.MaxBytes = self.config.get('MaxBytes', 209715200)
18
- self.Timeout = self.config.get('Timeout', 300000)
18
+ self.Timeout = self.config.get('Timeout', 600000)
19
19
  self.MaxRetries = self.config.get('MaxRetries', 3)
20
- self.OCR = self.config.get('OCR', None)
21
- self.INVOICE_OCR = self.config.get('INVOICE_OCR', None)
22
20
  self.llm_configs = []
23
21
  self.embedding_configs = []
24
22
  self.reranker_configs = []
23
+ self.sentry_configs = []
24
+ self.langfuse_configs = []
25
25
  self._process_config()
26
26
 
27
27
  def get_llm_config(self, model_name):
@@ -42,6 +42,18 @@ class Config(metaclass=SingletonMeta):
42
42
  return llm
43
43
  raise ValueError(f"No configuration found for model: {model_name}")
44
44
 
45
+ def get_sentry_config(self, name):
46
+ for sentry in self.sentry_configs:
47
+ if sentry.get('name') == name:
48
+ return sentry
49
+ raise ValueError(f"No configuration found for server: {name}")
50
+
51
+ def get_langfuse_config(self, name):
52
+ for langfuse in self.langfuse_configs:
53
+ if langfuse.get('name') == name:
54
+ return langfuse
55
+ raise ValueError(f"No configuration found for server: {name}")
56
+
45
57
  def _process_config(self):
46
58
  llm_config_list = self.config.get('LLMConfig', [])
47
59
  for llm_config in llm_config_list:
@@ -71,6 +83,15 @@ class Config(metaclass=SingletonMeta):
71
83
  except ValueError as e:
72
84
  print(f"Invalid LLM configuration: {e}")
73
85
 
86
+ sentry_config_list = self.config.get('SentryConfig', [])
87
+ for sentry_config in sentry_config_list:
88
+ try:
89
+ from sycommon.config.SentryConfig import SentryConfig
90
+ validated_config = SentryConfig(**sentry_config)
91
+ self.sentry_configs.append(validated_config.model_dump())
92
+ except ValueError as e:
93
+ print(f"Invalid Sentry configuration: {e}")
94
+
74
95
  def set_attr(self, share_configs: dict):
75
96
  self.config = {**self.config, **
76
97
  share_configs.get('llm', {}), **share_configs}
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class LangfuseConfig(BaseModel):
5
+ name: str
6
+ secretKey: str
7
+ publicKey: str
8
+ baseUrl: str
9
+ enable: bool
10
+
11
+ @classmethod
12
+ def from_config(cls, server_name: str):
13
+ from sycommon.config.Config import Config
14
+ langfuse_config = Config().get_langfuse_config(server_name)
15
+ return cls(**langfuse_config)
@@ -0,0 +1,13 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class SentryConfig(BaseModel):
5
+ name: str
6
+ dsn: str
7
+ enable: bool
8
+
9
+ @classmethod
10
+ def from_config(cls, server_name: str):
11
+ from sycommon.config.Config import Config
12
+ sentry_config = Config().get_sentry_config(server_name)
13
+ return cls(**sentry_config)
sycommon/llm/embedding.py CHANGED
@@ -25,15 +25,21 @@ class Embedding(metaclass=SingletonMeta):
25
25
 
26
26
  # 并发信号量
27
27
  self.semaphore = asyncio.Semaphore(self.max_concurrency)
28
+ # 全局默认超时:永不超时(None)
29
+ self.default_timeout = aiohttp.ClientTimeout(total=None)
28
30
 
29
31
  async def _get_embeddings_http_async(
30
32
  self,
31
33
  input: Union[str, List[str]],
32
34
  encoding_format: str = None,
33
35
  model: str = None,
36
+ timeout: aiohttp.ClientTimeout = None,
34
37
  **kwargs
35
38
  ):
36
39
  async with self.semaphore:
40
+ # 优先使用传入的超时,无则用全局默认
41
+ request_timeout = timeout or self.default_timeout
42
+
37
43
  # 优先使用传入的模型名,无则用默认值
38
44
  target_model = model or self.default_embedding_model
39
45
  target_base_url = EmbeddingConfig.from_config(target_model).baseUrl
@@ -46,14 +52,23 @@ class Embedding(metaclass=SingletonMeta):
46
52
  }
47
53
  request_body.update(kwargs)
48
54
 
49
- async with aiohttp.ClientSession() as session:
50
- async with session.post(url, json=request_body) as response:
51
- if response.status != 200:
52
- error_detail = await response.text()
53
- SYLogger.error(
54
- f"Embedding request failed (model: {target_model}): {error_detail}")
55
- return None
56
- return await response.json()
55
+ try:
56
+ async with aiohttp.ClientSession(timeout=request_timeout) as session:
57
+ async with session.post(url, json=request_body) as response:
58
+ if response.status != 200:
59
+ error_detail = await response.text()
60
+ SYLogger.error(
61
+ f"Embedding request failed (model: {target_model}): {error_detail}")
62
+ return None
63
+ return await response.json()
64
+ except asyncio.TimeoutError:
65
+ SYLogger.error(
66
+ f"Embedding request timeout (model: {target_model})")
67
+ return None
68
+ except Exception as e:
69
+ SYLogger.error(
70
+ f"Embedding request unexpected error (model: {target_model}): {str(e)}")
71
+ return None
57
72
 
58
73
  async def _get_reranker_http_async(
59
74
  self,
@@ -64,9 +79,13 @@ class Embedding(metaclass=SingletonMeta):
64
79
  max_chunks_per_doc: Optional[int] = None,
65
80
  return_documents: Optional[bool] = True,
66
81
  return_len: Optional[bool] = True,
82
+ timeout: aiohttp.ClientTimeout = None,
67
83
  **kwargs
68
84
  ):
69
85
  async with self.semaphore:
86
+ # 优先使用传入的超时,无则用全局默认
87
+ request_timeout = timeout or self.default_timeout
88
+
70
89
  # 优先使用传入的模型名,无则用默认值
71
90
  target_model = model or self.default_reranker_model
72
91
  target_base_url = RerankerConfig.from_config(target_model).baseUrl
@@ -84,19 +103,29 @@ class Embedding(metaclass=SingletonMeta):
84
103
  }
85
104
  request_body.update(kwargs)
86
105
 
87
- async with aiohttp.ClientSession() as session:
88
- async with session.post(url, json=request_body) as response:
89
- if response.status != 200:
90
- error_detail = await response.text()
91
- SYLogger.error(
92
- f"Rerank request failed (model: {target_model}): {error_detail}")
93
- return None
94
- return await response.json()
106
+ try:
107
+ async with aiohttp.ClientSession(timeout=request_timeout) as session:
108
+ async with session.post(url, json=request_body) as response:
109
+ if response.status != 200:
110
+ error_detail = await response.text()
111
+ SYLogger.error(
112
+ f"Rerank request failed (model: {target_model}): {error_detail}")
113
+ return None
114
+ return await response.json()
115
+ except asyncio.TimeoutError:
116
+ SYLogger.error(
117
+ f"Rerank request timeout (model: {target_model})")
118
+ return None
119
+ except Exception as e:
120
+ SYLogger.error(
121
+ f"Rerank request unexpected error (model: {target_model}): {str(e)}")
122
+ return None
95
123
 
96
124
  async def get_embeddings(
97
125
  self,
98
126
  corpus: List[str],
99
- model: str = None
127
+ model: str = None,
128
+ timeout: Optional[Union[int, float]] = None
100
129
  ):
101
130
  """
102
131
  获取语料库的嵌入向量,结果顺序与输入语料库顺序一致
@@ -104,12 +133,24 @@ class Embedding(metaclass=SingletonMeta):
104
133
  Args:
105
134
  corpus: 待生成嵌入向量的文本列表
106
135
  model: 可选,指定使用的embedding模型名称,默认使用bge-large-zh-v1.5
136
+ timeout: 可选,超时时间(秒):
137
+ - 传int/float:表示总超时时间(秒)
138
+ - 不传/None:使用默认永不超时配置
107
139
  """
140
+ request_timeout = None
141
+ if timeout is not None:
142
+ if isinstance(timeout, (int, float)):
143
+ request_timeout = aiohttp.ClientTimeout(total=timeout)
144
+ else:
145
+ SYLogger.warning(
146
+ f"Invalid timeout type: {type(timeout)}, must be int/float, use default timeout")
147
+
108
148
  SYLogger.info(
109
- f"Requesting embeddings for corpus: {corpus} (model: {model or self.default_embedding_model}, max_concurrency: {self.max_concurrency})")
110
- # 给每个异步任务传入模型名称
149
+ f"Requesting embeddings for corpus: {corpus} (model: {model or self.default_embedding_model}, max_concurrency: {self.max_concurrency}, timeout: {timeout or 'None'})")
150
+
151
+ # 给每个异步任务传入模型名称和超时配置
111
152
  tasks = [self._get_embeddings_http_async(
112
- text, model=model) for text in corpus]
153
+ text, model=model, timeout=request_timeout) for text in corpus]
113
154
  results = await asyncio.gather(*tasks)
114
155
 
115
156
  vectors = []
@@ -131,7 +172,8 @@ class Embedding(metaclass=SingletonMeta):
131
172
  self,
132
173
  top_results: List[str],
133
174
  query: str,
134
- model: str = None
175
+ model: str = None,
176
+ timeout: Optional[Union[int, float]] = None
135
177
  ):
136
178
  """
137
179
  对搜索结果进行重排序
@@ -140,10 +182,23 @@ class Embedding(metaclass=SingletonMeta):
140
182
  top_results: 待重排序的文本列表
141
183
  query: 排序参考的查询语句
142
184
  model: 可选,指定使用的reranker模型名称,默认使用bge-reranker-large
185
+ timeout: 可选,超时时间(秒):
186
+ - 传int/float:表示总超时时间(秒)
187
+ - 不传/None:使用默认永不超时配置
143
188
  """
189
+ request_timeout = None
190
+ if timeout is not None:
191
+ if isinstance(timeout, (int, float)):
192
+ request_timeout = aiohttp.ClientTimeout(total=timeout)
193
+ else:
194
+ SYLogger.warning(
195
+ f"Invalid timeout type: {type(timeout)}, must be int/float, use default timeout")
196
+
144
197
  SYLogger.info(
145
- f"Requesting reranker for top_results: {top_results} (model: {model or self.default_reranker_model}, max_concurrency: {self.max_concurrency})")
146
- data = await self._get_reranker_http_async(top_results, query, model=model)
198
+ f"Requesting reranker for top_results: {top_results} (model: {model or self.default_reranker_model}, max_concurrency: {self.max_concurrency}, timeout: {timeout or 'None'})")
199
+
200
+ data = await self._get_reranker_http_async(
201
+ top_results, query, model=model, timeout=request_timeout)
147
202
  SYLogger.info(
148
203
  f"Reranker for top_results: {top_results} completed (model: {model or self.default_reranker_model})")
149
204
  return data
sycommon/llm/get_llm.py CHANGED
@@ -1,222 +1,8 @@
1
- from typing import Dict, Type, List, Optional, Callable, Any
2
1
  from sycommon.llm.llm_logger import LLMLogger
3
- from langchain_core.language_models import BaseChatModel
4
- from langchain_core.runnables import Runnable, RunnableLambda, RunnableConfig
5
- from langchain_core.output_parsers import PydanticOutputParser
6
- from langchain_core.messages import BaseMessage, HumanMessage
7
2
  from langchain.chat_models import init_chat_model
8
- from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
9
- from pydantic import BaseModel, ValidationError, Field
10
3
  from sycommon.config.LLMConfig import LLMConfig
11
- from sycommon.llm.llm_tokens import TokensCallbackHandler
12
- from sycommon.logging.kafka_log import SYLogger
13
-
14
-
15
- class StructuredRunnableWithToken(Runnable):
16
- """带Token统计的Runnable类"""
17
-
18
- def __init__(self, retry_chain: Runnable):
19
- super().__init__()
20
- self.retry_chain = retry_chain
21
-
22
- def _adapt_input(self, input: Any) -> List[BaseMessage]:
23
- """适配输入格式"""
24
- if isinstance(input, list) and all(isinstance(x, BaseMessage) for x in input):
25
- return input
26
- elif isinstance(input, BaseMessage):
27
- return [input]
28
- elif isinstance(input, str):
29
- return [HumanMessage(content=input)]
30
- elif isinstance(input, dict) and "input" in input:
31
- return [HumanMessage(content=str(input["input"]))]
32
- else:
33
- raise ValueError(f"不支持的输入格式:{type(input)}")
34
-
35
- def _get_callback_config(self, config: Optional[RunnableConfig] = None) -> tuple[RunnableConfig, TokensCallbackHandler]:
36
- """构建包含Token统计的回调配置"""
37
- # 每次调用创建新的Token处理器实例
38
- token_handler = TokensCallbackHandler()
39
-
40
- # 初始化配置
41
- if config is None:
42
- processed_config = {"callbacks": []}
43
- else:
44
- processed_config = config.copy()
45
- if "callbacks" not in processed_config:
46
- processed_config["callbacks"] = []
47
-
48
- # 添加回调(去重)
49
- callbacks = processed_config["callbacks"]
50
- # 添加LLMLogger(如果不存在)
51
- if not any(isinstance(cb, LLMLogger) for cb in callbacks):
52
- callbacks.append(LLMLogger())
53
- # 添加Token处理器
54
- callbacks.append(token_handler)
55
-
56
- # 按类型去重
57
- callback_types = {}
58
- unique_callbacks = []
59
- for cb in callbacks:
60
- cb_type = type(cb)
61
- if cb_type not in callback_types:
62
- callback_types[cb_type] = cb
63
- unique_callbacks.append(cb)
64
-
65
- processed_config["callbacks"] = unique_callbacks
66
-
67
- return processed_config, token_handler
68
-
69
- # 同步调用
70
- def invoke(self, input: Any, config: Optional[RunnableConfig] = None, ** kwargs) -> Dict[str, Any]:
71
- try:
72
- processed_config, token_handler = self._get_callback_config(
73
- config)
74
- adapted_input = self._adapt_input(input)
75
-
76
- structured_result = self.retry_chain.invoke(
77
- {"messages": adapted_input},
78
- config=processed_config,
79
- **kwargs
80
- )
81
-
82
- # 获取Token统计结果
83
- token_usage = token_handler.usage_metadata
84
- structured_result._token_usage_ = token_usage
85
-
86
- return structured_result
87
-
88
- except Exception as e:
89
- SYLogger.error(f"同步LLM调用失败: {str(e)}", exc_info=True)
90
- return None
91
-
92
- # 异步调用
93
- async def ainvoke(self, input: Any, config: Optional[RunnableConfig] = None, ** kwargs) -> Dict[str, Any]:
94
- try:
95
- processed_config, token_handler = self._get_callback_config(
96
- config)
97
- adapted_input = self._adapt_input(input)
98
-
99
- structured_result = await self.retry_chain.ainvoke(
100
- {"messages": adapted_input},
101
- config=processed_config,
102
- **kwargs
103
- )
104
-
105
- token_usage = token_handler.usage_metadata
106
- structured_result._token_usage_ = token_usage
107
-
108
- return structured_result
109
-
110
- except Exception as e:
111
- SYLogger.error(f"异步LLM调用失败: {str(e)}", exc_info=True)
112
- return None
113
-
114
-
115
- class LLMWithAutoTokenUsage(BaseChatModel):
116
- """自动为结构化调用返回token_usage的LLM包装类"""
117
- llm: BaseChatModel = Field(default=None)
118
-
119
- def __init__(self, llm: BaseChatModel, **kwargs):
120
- super().__init__(llm=llm, ** kwargs)
121
-
122
- def with_structured_output(
123
- self,
124
- output_model: Type[BaseModel],
125
- max_retries: int = 3,
126
- is_extract: bool = False,
127
- override_prompt: ChatPromptTemplate = None,
128
- custom_processors: Optional[List[Callable[[str], str]]] = None,
129
- custom_parser: Optional[Callable[[str], BaseModel]] = None
130
- ) -> Runnable:
131
- """返回支持自动统计Token的结构化Runnable"""
132
- parser = PydanticOutputParser(pydantic_object=output_model)
133
-
134
- # 提示词模板
135
- accuracy_instructions = """
136
- 字段值的抽取准确率(0~1之间),评分规则:
137
- 1.0(完全准确):直接从原文提取,无需任何加工,且格式与原文完全一致
138
- 0.9(轻微处理):数据来源明确,但需进行格式标准化或冗余信息剔除(不改变原始数值)
139
- 0.8(有限推断):数据需通过上下文关联或简单计算得出,仍有明确依据
140
- 0.8以下(不可靠):数据需大量推测、存在歧义或来源不明,处理方式:直接忽略该数据,设置为None
141
- """
142
-
143
- if is_extract:
144
- prompt = ChatPromptTemplate.from_messages([
145
- MessagesPlaceholder(variable_name="messages"),
146
- HumanMessage(content=f"""
147
- 请提取信息并遵循以下规则:
148
- 1. 准确率要求:{accuracy_instructions.strip()}
149
- 2. 输出格式:{parser.get_format_instructions()}
150
- """)
151
- ])
152
- else:
153
- prompt = override_prompt or ChatPromptTemplate.from_messages([
154
- MessagesPlaceholder(variable_name="messages"),
155
- HumanMessage(content=f"""
156
- 输出格式:{parser.get_format_instructions()}
157
- """)
158
- ])
159
-
160
- # 文本处理函数
161
- def extract_response_content(response: BaseMessage) -> str:
162
- try:
163
- return response.content
164
- except Exception as e:
165
- raise ValueError(f"提取响应内容失败:{str(e)}") from e
166
-
167
- def strip_code_block_markers(content: str) -> str:
168
- try:
169
- return content.strip("```json").strip("```").strip()
170
- except Exception as e:
171
- raise ValueError(f"移除代码块标记失败:{str(e)}") from e
172
-
173
- def normalize_in_json(content: str) -> str:
174
- try:
175
- return content.replace("None", "null").replace("none", "null").replace("NONE", "null").replace("''", '""')
176
- except Exception as e:
177
- raise ValueError(f"JSON格式化失败:{str(e)}") from e
178
-
179
- def default_parse_to_pydantic(content: str) -> BaseModel:
180
- try:
181
- return parser.parse(content)
182
- except (ValidationError, ValueError) as e:
183
- raise ValueError(f"解析结构化结果失败:{str(e)}") from e
184
-
185
- # ========== 构建处理链 ==========
186
- base_chain = prompt | self.llm | RunnableLambda(
187
- extract_response_content)
188
-
189
- # 文本处理链
190
- process_runnables = custom_processors or [
191
- RunnableLambda(strip_code_block_markers),
192
- RunnableLambda(normalize_in_json)
193
- ]
194
- process_chain = base_chain
195
- for runnable in process_runnables:
196
- process_chain = process_chain | runnable
197
-
198
- # 解析链
199
- parse_chain = process_chain | RunnableLambda(
200
- custom_parser or default_parse_to_pydantic)
201
-
202
- # 重试链
203
- retry_chain = parse_chain.with_retry(
204
- retry_if_exception_type=(ValidationError, ValueError),
205
- stop_after_attempt=max_retries,
206
- wait_exponential_jitter=True,
207
- exponential_jitter_params={
208
- "initial": 0.1, "max": 3.0, "exp_base": 2.0, "jitter": 1.0}
209
- )
210
-
211
- return StructuredRunnableWithToken(retry_chain)
212
-
213
- # ========== 实现BaseChatModel抽象方法 ==========
214
- def _generate(self, messages, stop=None, run_manager=None, ** kwargs):
215
- return self.llm._generate(messages, stop=stop, run_manager=run_manager, ** kwargs)
216
-
217
- @property
218
- def _llm_type(self) -> str:
219
- return self.llm._llm_type
4
+ from sycommon.llm.sy_langfuse import LangfuseInitializer
5
+ from sycommon.llm.usage_token import LLMWithAutoTokenUsage
220
6
 
221
7
 
222
8
  def get_llm(
@@ -230,6 +16,11 @@ def get_llm(
230
16
  if not llmConfig:
231
17
  raise Exception(f"无效的模型配置:{model}")
232
18
 
19
+ # 初始化Langfuse
20
+ langfuse_callbacks, langfuse = LangfuseInitializer.get()
21
+
22
+ callbacks = [LLMLogger()] + langfuse_callbacks
23
+
233
24
  llm = init_chat_model(
234
25
  model_provider=llmConfig.provider,
235
26
  model=llmConfig.model,
@@ -237,10 +28,10 @@ def get_llm(
237
28
  api_key="-",
238
29
  temperature=0.1,
239
30
  streaming=streaming,
240
- callbacks=[LLMLogger()]
31
+ callbacks=callbacks
241
32
  )
242
33
 
243
34
  if llm is None:
244
35
  raise Exception(f"初始化原始LLM实例失败:{model}")
245
36
 
246
- return LLMWithAutoTokenUsage(llm)
37
+ return LLMWithAutoTokenUsage(llm, langfuse)