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.
- sycommon/config/Config.py +24 -3
- sycommon/config/LangfuseConfig.py +15 -0
- sycommon/config/SentryConfig.py +13 -0
- sycommon/llm/embedding.py +78 -23
- sycommon/llm/get_llm.py +9 -218
- sycommon/llm/struct_token.py +192 -0
- sycommon/llm/sy_langfuse.py +103 -0
- sycommon/llm/usage_token.py +117 -0
- sycommon/logging/kafka_log.py +187 -433
- sycommon/middleware/exception.py +10 -16
- sycommon/middleware/timeout.py +2 -1
- sycommon/middleware/traceid.py +81 -76
- sycommon/notice/uvicorn_monitor.py +32 -27
- sycommon/rabbitmq/rabbitmq_client.py +228 -243
- sycommon/rabbitmq/rabbitmq_pool.py +201 -123
- sycommon/rabbitmq/rabbitmq_service.py +25 -843
- sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
- sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
- sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
- sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
- sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
- sycommon/sentry/__init__.py +0 -0
- sycommon/sentry/sy_sentry.py +35 -0
- sycommon/services.py +122 -96
- sycommon/synacos/nacos_client_base.py +119 -0
- sycommon/synacos/nacos_config_manager.py +107 -0
- sycommon/synacos/nacos_heartbeat_manager.py +144 -0
- sycommon/synacos/nacos_service.py +63 -783
- sycommon/synacos/nacos_service_discovery.py +157 -0
- sycommon/synacos/nacos_service_registration.py +270 -0
- sycommon/tools/env.py +62 -0
- sycommon/tools/merge_headers.py +20 -0
- sycommon/tools/snowflake.py +101 -153
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +10 -8
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/RECORD +38 -20
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
- {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',
|
|
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
|
-
|
|
50
|
-
async with
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
88
|
-
async with
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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.
|
|
12
|
-
from sycommon.
|
|
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=
|
|
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)
|