agi-med-common 5.0.15__tar.gz → 5.0.17__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.
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/PKG-INFO +1 -2
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/pyproject.toml +5 -5
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/__init__.py +9 -4
- agi_med_common-5.0.17/src/agi_med_common/api/classifier_api.py +14 -0
- agi_med_common-5.0.17/src/agi_med_common/api/text_generator_api.py +6 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/__init__.py +1 -0
- agi_med_common-5.0.17/src/agi_med_common/models/chat.py +267 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/chat_item.py +29 -37
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/widget.py +3 -1
- agi_med_common-5.0.17/src/agi_med_common/type_union.py +63 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/utils.py +11 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/PKG-INFO +1 -2
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/SOURCES.txt +4 -6
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/requires.txt +0 -1
- agi_med_common-5.0.15/requirements.txt +0 -3
- agi_med_common-5.0.15/src/agi_med_common/api/critic_api.py +0 -6
- agi_med_common-5.0.15/src/agi_med_common/api/text_processor_api.py +0 -6
- agi_med_common-5.0.15/src/agi_med_common/logger/__init__.py +0 -2
- agi_med_common-5.0.15/src/agi_med_common/logger/log_level_enum.py +0 -15
- agi_med_common-5.0.15/src/agi_med_common/logger/logger.py +0 -28
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/README.md +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/setup.cfg +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/api/chat_manager_api.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/api/content_interpreter_api.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/api/content_interpreter_remote_api.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/file_storage.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/_base.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/base_config_models/__init__.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/base_config_models/gigachat_config.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/enums.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/models/tracks.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/parallel_map.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/validators.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/xml_parser.py +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/dependency_links.txt +0 -0
- {agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/top_level.txt +0 -0
@@ -1,12 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: agi_med_common
|
3
|
-
Version: 5.0.
|
3
|
+
Version: 5.0.17
|
4
4
|
Summary: Сommon for agi-med team
|
5
5
|
Author: AGI-MED-TEAM
|
6
6
|
Requires-Python: >=3.11
|
7
7
|
Description-Content-Type: text/markdown
|
8
8
|
Requires-Dist: pydantic<=3.0.0,>=2.9.2
|
9
|
-
Requires-Dist: loguru~=0.7.2
|
10
9
|
Requires-Dist: tqdm==4.67.*
|
11
10
|
|
12
11
|
# agi-med-common
|
@@ -5,17 +5,17 @@ build-backend = "setuptools.build_meta"
|
|
5
5
|
[project]
|
6
6
|
name = 'agi_med_common'
|
7
7
|
requires-python = ">= 3.11"
|
8
|
-
|
9
8
|
authors = [{ name = 'AGI-MED-TEAM' }]
|
10
9
|
description = 'Сommon for agi-med team'
|
11
10
|
readme = 'README.md'
|
12
|
-
|
13
|
-
|
11
|
+
dynamic = ['version']
|
12
|
+
dependencies = [
|
13
|
+
"pydantic>=2.9.2,<=3.0.0",
|
14
|
+
"tqdm==4.67.*",
|
15
|
+
]
|
14
16
|
|
15
17
|
[tool.setuptools.packages.find]
|
16
18
|
where = ["src"]
|
17
19
|
|
18
|
-
|
19
20
|
[tool.setuptools.dynamic]
|
20
21
|
version = { attr = "agi_med_common.__version__" }
|
21
|
-
dependencies = { file = ['requirements.txt'] }
|
@@ -1,12 +1,17 @@
|
|
1
|
-
__version__ = "5.0.
|
1
|
+
__version__ = "5.0.17"
|
2
2
|
|
3
|
-
from .logger import LogLevelEnum, logger_init, log_llm_error
|
4
3
|
from .models import (
|
5
4
|
MTRSLabelEnum,
|
6
5
|
ChatItem,
|
7
6
|
InnerContextItem,
|
8
7
|
OuterContextItem,
|
9
8
|
ReplicaItem,
|
9
|
+
Chat,
|
10
|
+
Context,
|
11
|
+
ChatMessage,
|
12
|
+
AIMessage,
|
13
|
+
HumanMessage,
|
14
|
+
MiscMessage,
|
10
15
|
)
|
11
16
|
from .models.widget import Widget
|
12
17
|
from .file_storage import FileStorage, ResourceId
|
@@ -19,5 +24,5 @@ from .models.tracks import TrackInfo, DomainInfo
|
|
19
24
|
from .api.chat_manager_api import ChatManagerAPI
|
20
25
|
from .api.content_interpreter_api import ContentInterpreterAPI, Interpretation
|
21
26
|
from .api.content_interpreter_remote_api import ContentInterpreterRemoteAPI
|
22
|
-
from .api.
|
23
|
-
from .api.
|
27
|
+
from .api.text_generator_api import TextGeneratorAPI
|
28
|
+
from .api.classifier_api import ClassifierAPI
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from agi_med_common.models import ChatItem
|
4
|
+
|
5
|
+
|
6
|
+
Value = str
|
7
|
+
|
8
|
+
|
9
|
+
class ClassifierAPI:
|
10
|
+
def get_values(self) -> List[Value]:
|
11
|
+
raise NotImplementedError
|
12
|
+
|
13
|
+
def evaluate(self, chat: ChatItem, request_id: str = "") -> Value:
|
14
|
+
raise NotImplementedError
|
@@ -2,5 +2,6 @@ from ._base import _Base
|
|
2
2
|
|
3
3
|
from .enums import MTRSLabelEnum, DiagnosticsXMLTagEnum, MTRSXMLTagEnum, DoctorChoiceXMLTagEnum
|
4
4
|
from .chat_item import ChatItem, OuterContextItem, InnerContextItem, ReplicaItem
|
5
|
+
from .chat import Chat, Context, ChatMessage, AIMessage, HumanMessage, MiscMessage
|
5
6
|
from .base_config_models import GigaChatConfig
|
6
7
|
from .tracks import TrackInfo, DomainInfo
|
@@ -0,0 +1,267 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Any, List, Dict, Literal
|
3
|
+
|
4
|
+
from agi_med_common.models.chat_item import ChatItem, ReplicaItem, OuterContextItem
|
5
|
+
from agi_med_common.models.widget import Widget
|
6
|
+
from agi_med_common.type_union import TypeUnion
|
7
|
+
from agi_med_common.utils import first_nonnull
|
8
|
+
from pydantic import Field
|
9
|
+
|
10
|
+
from ._base import _Base
|
11
|
+
|
12
|
+
|
13
|
+
_DT_FORMAT: str = "%Y-%m-%d-%H-%M-%S"
|
14
|
+
_EXAMPLE_DT: str = datetime(year=1970, month=1, day=1).strftime(_DT_FORMAT)
|
15
|
+
StrDict = Dict[str, Any]
|
16
|
+
ContentBase = str | Widget | StrDict
|
17
|
+
Content = ContentBase | List[ContentBase]
|
18
|
+
|
19
|
+
|
20
|
+
def now_pretty() -> str:
|
21
|
+
return datetime.now().strftime(_DT_FORMAT)
|
22
|
+
|
23
|
+
|
24
|
+
class Context(_Base):
|
25
|
+
client_id: str = Field("", examples=["543216789"])
|
26
|
+
user_id: str = Field("", examples=["123456789"])
|
27
|
+
session_id: str = Field("", examples=["987654321"])
|
28
|
+
track_id: str = Field(examples=["Hello"])
|
29
|
+
extra: StrDict | None = Field(None, examples=[None])
|
30
|
+
|
31
|
+
def create_id(self, short: bool = False) -> str:
|
32
|
+
uid, sid, cid = self.user_id, self.session_id, self.client_id
|
33
|
+
if short:
|
34
|
+
return f"{cid}_{uid}_{sid}"
|
35
|
+
return f"client_{cid}_user_{uid}_session_{sid}"
|
36
|
+
|
37
|
+
|
38
|
+
def _get_str_field(obj: dict, field) -> str | None:
|
39
|
+
if not isinstance(obj, dict):
|
40
|
+
return None
|
41
|
+
text = obj.get(field)
|
42
|
+
if text is not None and isinstance(text, str):
|
43
|
+
return text
|
44
|
+
return None
|
45
|
+
|
46
|
+
|
47
|
+
def _get_text(obj: Content) -> str:
|
48
|
+
if isinstance(obj, str):
|
49
|
+
return obj
|
50
|
+
if isinstance(obj, list):
|
51
|
+
return "".join(map(_get_text, obj))
|
52
|
+
if isinstance(obj, dict) and obj.get("type") == "text":
|
53
|
+
return _get_str_field(obj, "text") or ""
|
54
|
+
return ""
|
55
|
+
|
56
|
+
|
57
|
+
def _get_resource_id(obj: Content) -> str | None:
|
58
|
+
if isinstance(obj, list):
|
59
|
+
return first_nonnull(map(_get_resource_id, obj))
|
60
|
+
if isinstance(obj, dict) and obj.get("type") == "resource_id":
|
61
|
+
return _get_str_field(obj, "resource_id")
|
62
|
+
return None
|
63
|
+
|
64
|
+
|
65
|
+
def _get_command(obj: Content) -> dict | None:
|
66
|
+
if isinstance(obj, list):
|
67
|
+
return first_nonnull(map(_get_command, obj))
|
68
|
+
if isinstance(obj, dict) and obj.get("type") == "command":
|
69
|
+
return _get_str_field(obj, "command")
|
70
|
+
return None
|
71
|
+
|
72
|
+
|
73
|
+
def _get_widget(obj: Content) -> Widget | None:
|
74
|
+
if isinstance(obj, list):
|
75
|
+
return first_nonnull(map(_get_widget, obj))
|
76
|
+
if isinstance(obj, Widget):
|
77
|
+
return obj
|
78
|
+
return None
|
79
|
+
|
80
|
+
|
81
|
+
# todo fix: generalize functions above
|
82
|
+
|
83
|
+
|
84
|
+
class BaseMessage(_Base):
|
85
|
+
type: str
|
86
|
+
content: Content = Field("", examples=["Привет"])
|
87
|
+
date_time: str = Field(default_factory=now_pretty, examples=[_EXAMPLE_DT])
|
88
|
+
extra: StrDict | None = Field(None, examples=[None])
|
89
|
+
|
90
|
+
@property
|
91
|
+
def text(self) -> str:
|
92
|
+
return _get_text(self.content)
|
93
|
+
|
94
|
+
@property
|
95
|
+
def resource_id(self) -> str | None:
|
96
|
+
return _get_resource_id(self.content)
|
97
|
+
|
98
|
+
@property
|
99
|
+
def command(self) -> dict | None:
|
100
|
+
return _get_command(self.content)
|
101
|
+
|
102
|
+
@property
|
103
|
+
def widget(self) -> Widget | None:
|
104
|
+
return _get_widget(self.content)
|
105
|
+
|
106
|
+
@staticmethod
|
107
|
+
def DATETIME_FORMAT() -> str:
|
108
|
+
return _DT_FORMAT
|
109
|
+
|
110
|
+
def with_now_datetime(self):
|
111
|
+
return self.model_copy(update=dict(date_time=now_pretty()))
|
112
|
+
|
113
|
+
|
114
|
+
class HumanMessage(BaseMessage):
|
115
|
+
type: Literal["human"] = "human"
|
116
|
+
|
117
|
+
|
118
|
+
class AIMessage(BaseMessage):
|
119
|
+
type: Literal["ai"] = "ai"
|
120
|
+
state: str = Field("", examples=["COLLECTION"])
|
121
|
+
|
122
|
+
|
123
|
+
class MiscMessage(BaseMessage):
|
124
|
+
type: Literal["misc"] = "misc"
|
125
|
+
|
126
|
+
|
127
|
+
ChatMessage = TypeUnion[HumanMessage, AIMessage, MiscMessage]
|
128
|
+
|
129
|
+
|
130
|
+
class Chat(_Base):
|
131
|
+
context: Context
|
132
|
+
messages: List[ChatMessage] = []
|
133
|
+
|
134
|
+
def create_id(self, short: bool = False) -> str:
|
135
|
+
return self.context.create_id(short)
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def parse(self, chat_obj: str | dict) -> "Chat":
|
139
|
+
return _parse_chat_compat(chat_obj)
|
140
|
+
|
141
|
+
|
142
|
+
def convert_replica_item_to_message(replica: ReplicaItem) -> ChatMessage:
|
143
|
+
resource_id = (replica.resource_id or None) and {"type": "resource_id", "resource_id": replica.resource_id}
|
144
|
+
body = replica.body
|
145
|
+
command = replica.command
|
146
|
+
widget = replica.widget
|
147
|
+
|
148
|
+
content = list(filter(None, [body, resource_id, command, widget]))
|
149
|
+
if len(content) == 0:
|
150
|
+
content = ""
|
151
|
+
elif len(content) == 1:
|
152
|
+
content = content[0]
|
153
|
+
|
154
|
+
kwargs = dict(
|
155
|
+
content=content,
|
156
|
+
date_time=replica.date_time,
|
157
|
+
extra=replica.extra,
|
158
|
+
)
|
159
|
+
if not replica.role:
|
160
|
+
return HumanMessage(**kwargs)
|
161
|
+
return AIMessage(
|
162
|
+
**kwargs,
|
163
|
+
state=replica.state,
|
164
|
+
extra=dict(
|
165
|
+
action=replica.action,
|
166
|
+
moderation=replica.moderation,
|
167
|
+
),
|
168
|
+
)
|
169
|
+
|
170
|
+
|
171
|
+
def convert_outer_context_to_context(octx: OuterContextItem) -> Context:
|
172
|
+
# legacy: eliminate
|
173
|
+
context = Context(
|
174
|
+
client_id=octx.client_id,
|
175
|
+
user_id=octx.user_id,
|
176
|
+
session_id=octx.session_id,
|
177
|
+
track_id=octx.track_id,
|
178
|
+
extra=dict(
|
179
|
+
sex=octx.sex,
|
180
|
+
age=octx.age,
|
181
|
+
parent_session_id=octx.parent_session_id,
|
182
|
+
entrypoint_key=octx.entrypoint_key,
|
183
|
+
language_code=octx.language_code,
|
184
|
+
),
|
185
|
+
)
|
186
|
+
return context
|
187
|
+
|
188
|
+
|
189
|
+
def convert_chat_item_to_chat(chat_item: ChatItem) -> Chat:
|
190
|
+
# legacy: eliminate
|
191
|
+
context = convert_outer_context_to_context(chat_item.outer_context)
|
192
|
+
messages = [convert_replica_item_to_message(replica) for replica in chat_item.inner_context.replicas]
|
193
|
+
res = Chat(context=context, messages=messages)
|
194
|
+
return res
|
195
|
+
|
196
|
+
|
197
|
+
def convert_context_to_outer_context(context: Context) -> OuterContextItem:
|
198
|
+
# legacy: eliminate
|
199
|
+
extra = context.extra or {}
|
200
|
+
return OuterContextItem(
|
201
|
+
client_id=context.client_id,
|
202
|
+
user_id=context.user_id,
|
203
|
+
session_id=context.session_id,
|
204
|
+
track_id=context.track_id,
|
205
|
+
sex=extra.get("sex"),
|
206
|
+
age=extra.get("age"),
|
207
|
+
parent_session_id=extra.get("parent_session_id"),
|
208
|
+
entrypoint_key=extra.get("entrypoint_key"),
|
209
|
+
language_code=extra.get("language_code"),
|
210
|
+
)
|
211
|
+
|
212
|
+
|
213
|
+
def convert_message_to_replica_item(message: ChatMessage) -> ReplicaItem | None:
|
214
|
+
# legacy: eliminate
|
215
|
+
m_type = message.type
|
216
|
+
if m_type in {"ai", "human"}:
|
217
|
+
role = m_type == "ai"
|
218
|
+
else:
|
219
|
+
return None
|
220
|
+
|
221
|
+
extra = message.extra or {}
|
222
|
+
action = extra.pop("action")
|
223
|
+
moderation = extra.pop("moderation")
|
224
|
+
|
225
|
+
kwargs = dict(
|
226
|
+
role=role,
|
227
|
+
body=message.body,
|
228
|
+
resource_id=message.resource_id,
|
229
|
+
command=message.command,
|
230
|
+
widget=message.widget,
|
231
|
+
date_time=message.date_time,
|
232
|
+
extra=extra or None,
|
233
|
+
state=getattr(message, "state", ""),
|
234
|
+
action=action,
|
235
|
+
moderation=moderation,
|
236
|
+
)
|
237
|
+
return ReplicaItem(**kwargs)
|
238
|
+
|
239
|
+
|
240
|
+
def convert_chat_to_chat_item(chat: Chat) -> ChatItem:
|
241
|
+
# legacy: eliminate
|
242
|
+
return ChatItem(
|
243
|
+
outer_context=convert_context_to_outer_context(chat.context),
|
244
|
+
inner_context=dict(replicas=list(map(convert_message_to_replica_item, chat.messages))),
|
245
|
+
)
|
246
|
+
|
247
|
+
|
248
|
+
def parse_chat_item_as_chat(chat_obj: str | dict) -> Chat:
|
249
|
+
# legacy: eliminate
|
250
|
+
chat_item = ChatItem.parse(chat_obj)
|
251
|
+
res = convert_chat_item_to_chat(chat_item)
|
252
|
+
return res
|
253
|
+
|
254
|
+
|
255
|
+
def _parse_chat(chat_obj: str | dict) -> Chat:
|
256
|
+
if isinstance(chat_obj, dict):
|
257
|
+
return Chat.model_validate(chat_obj)
|
258
|
+
|
259
|
+
return Chat.model_validate_json(chat_obj)
|
260
|
+
|
261
|
+
|
262
|
+
def _parse_chat_compat(chat_obj: str | dict) -> Chat:
|
263
|
+
# legacy: eliminate
|
264
|
+
try:
|
265
|
+
return _parse_chat(chat_obj)
|
266
|
+
except Exception:
|
267
|
+
return parse_chat_item_as_chat(chat_obj)
|
@@ -1,10 +1,8 @@
|
|
1
|
-
import json
|
2
1
|
from datetime import datetime
|
3
|
-
from typing import Any, List
|
2
|
+
from typing import Annotated, Any, List
|
4
3
|
|
5
4
|
from agi_med_common.models.widget import Widget
|
6
|
-
from
|
7
|
-
from pydantic import Field, ConfigDict, ValidationError
|
5
|
+
from pydantic import Field, ConfigDict, BeforeValidator
|
8
6
|
|
9
7
|
from ._base import _Base
|
10
8
|
|
@@ -41,6 +39,24 @@ class OuterContextItem(_Base):
|
|
41
39
|
def to_dict(self) -> dict[str, Any]:
|
42
40
|
return self.model_dump(by_alias=True)
|
43
41
|
|
42
|
+
LABELS = {
|
43
|
+
0: "OK",
|
44
|
+
1: "NON_MED",
|
45
|
+
2: "CHILD",
|
46
|
+
3: "ABSURD",
|
47
|
+
4: "GREETING",
|
48
|
+
5: "RECEIPT",
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
def fix_deprecated_moderation(moderation):
|
53
|
+
if isinstance(moderation, int):
|
54
|
+
return LABELS.get(moderation, "OK")
|
55
|
+
elif isinstance(moderation, str):
|
56
|
+
return moderation
|
57
|
+
else:
|
58
|
+
raise ValueError(f"Unsupported moderation: {moderation} :: {type(moderation)}")
|
59
|
+
|
44
60
|
|
45
61
|
class ReplicaItem(_Base):
|
46
62
|
body: str = Field("", alias="Body", examples=["Привет"])
|
@@ -54,7 +70,9 @@ class ReplicaItem(_Base):
|
|
54
70
|
state: str = Field("", alias="State", description="chat manager fsm state", examples=["COLLECTION"])
|
55
71
|
action: str = Field("", alias="Action", description="chat manager fsm action", examples=["DIAGNOSIS"])
|
56
72
|
# todo fix: support loading from `moderation: int`
|
57
|
-
moderation: str = Field(
|
73
|
+
moderation: Annotated[str, BeforeValidator(str)] = Field(
|
74
|
+
"OK", alias="Moderation", description="moderation outcome", examples=["OK"]
|
75
|
+
)
|
58
76
|
extra: dict | None = Field(None, alias="Extra", examples=[None])
|
59
77
|
|
60
78
|
def to_dict(self) -> dict[str, Any]:
|
@@ -97,38 +115,12 @@ class ChatItem(_Base):
|
|
97
115
|
return [replica.to_dict().get(field, None) for replica in self.inner_context.replicas]
|
98
116
|
|
99
117
|
@classmethod
|
100
|
-
def parse(cls,
|
101
|
-
return
|
102
|
-
|
103
|
-
|
104
|
-
def _is_moderation_int_error(err):
|
105
|
-
# fmt: off
|
106
|
-
if err['type'] != 'string_type': return False
|
107
|
-
if err['loc'][0] != 'InnerContext': return False
|
108
|
-
if err['loc'][1] != 'Replicas': return False
|
109
|
-
if err['loc'][3] != 'Moderation': return False
|
110
|
-
if err['msg'] != 'Input should be a valid string': return False
|
111
|
-
# fmt: on
|
112
|
-
return True
|
113
|
-
|
114
|
-
|
115
|
-
def _is_moderation_int_errors(ex):
|
116
|
-
errs = ex.errors()
|
117
|
-
return all(map(_is_moderation_int_error, errs))
|
118
|
-
|
118
|
+
def parse(cls, chat_obj: str | dict) -> "ChatItem":
|
119
|
+
return _parse_chat_item(chat_obj)
|
119
120
|
|
120
|
-
def _parse_chat(chat_text: str) -> ChatItem:
|
121
|
-
try:
|
122
|
-
return ChatItem.model_validate_json(chat_text)
|
123
|
-
except ValidationError as ex:
|
124
|
-
if _is_moderation_int_errors(ex):
|
125
|
-
logger.warning("Failed to parse ChatItem, fallback to old version with `Moderation:int`")
|
126
|
-
else:
|
127
|
-
logger.error(f"Failed to parse: {ex}")
|
128
121
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
rep["Moderation"] = "OK"
|
122
|
+
def _parse_chat_item(chat_obj: str | dict) -> ChatItem:
|
123
|
+
if isinstance(chat_obj, dict):
|
124
|
+
return ChatItem.model_validate(chat_obj)
|
133
125
|
|
134
|
-
return ChatItem.
|
126
|
+
return ChatItem.model_validate_json(chat_obj)
|
@@ -1,8 +1,10 @@
|
|
1
|
-
from typing import List, Self
|
1
|
+
from typing import List, Self, Literal
|
2
|
+
|
2
3
|
from pydantic import BaseModel, model_validator
|
3
4
|
|
4
5
|
|
5
6
|
class Widget(BaseModel):
|
7
|
+
type: Literal["widget"] = "widget"
|
6
8
|
buttons: List[List[str]] | None = None
|
7
9
|
ibuttons: List[List[str]] | None = None
|
8
10
|
|
@@ -0,0 +1,63 @@
|
|
1
|
+
from typing import Any, Union, Annotated, get_args, get_origin, Literal
|
2
|
+
|
3
|
+
from pydantic import Discriminator, BaseModel, Tag
|
4
|
+
|
5
|
+
|
6
|
+
class _TypeUnionMeta(type):
|
7
|
+
def __getitem__(cls, types):
|
8
|
+
if not isinstance(types, tuple):
|
9
|
+
types = (types,)
|
10
|
+
|
11
|
+
for tp in types:
|
12
|
+
if not isinstance(tp, type) or not issubclass(tp, BaseModel):
|
13
|
+
raise ValueError(f"Type {tp} must derived from BaseModel")
|
14
|
+
|
15
|
+
# Create tagged union
|
16
|
+
tagged_types = []
|
17
|
+
type_map = {}
|
18
|
+
type_names = set()
|
19
|
+
for t in types:
|
20
|
+
type_annot = t.__annotations__.get("type")
|
21
|
+
if not type_annot or get_origin(type_annot) != Literal:
|
22
|
+
raise ValueError(f"Type {t} must have a 'type' Literal[..] field for discrimination")
|
23
|
+
type_name = get_args(type_annot)[0]
|
24
|
+
type_map[type_name] = t
|
25
|
+
tagged_types.append(Annotated[t, Tag(type_name)])
|
26
|
+
type_names.add(type_name)
|
27
|
+
|
28
|
+
# Create the union type
|
29
|
+
union_type = Union[tuple(tagged_types)]
|
30
|
+
|
31
|
+
# Create discriminator function
|
32
|
+
def type_discriminator(v: Any) -> str:
|
33
|
+
if isinstance(v, types):
|
34
|
+
return v.type
|
35
|
+
if isinstance(v, dict):
|
36
|
+
tp = v.get("type")
|
37
|
+
return tp if tp in type_names else None
|
38
|
+
return None
|
39
|
+
|
40
|
+
# Add discriminator
|
41
|
+
return Annotated[union_type, Discriminator(type_discriminator)]
|
42
|
+
|
43
|
+
|
44
|
+
class TypeUnion(metaclass=_TypeUnionMeta):
|
45
|
+
"""
|
46
|
+
Wrapper around Union which derive type from field 'type' for effective deserialization via TypeAdapter.
|
47
|
+
|
48
|
+
Usage example:
|
49
|
+
```
|
50
|
+
class BaseFruit(BaseModel):
|
51
|
+
type: str
|
52
|
+
|
53
|
+
class Apple(BaseFruit):
|
54
|
+
type: Literal['apple'] = 'apple'
|
55
|
+
|
56
|
+
class Orange(BaseFruit):
|
57
|
+
type: Literal['orange'] = 'orange'
|
58
|
+
|
59
|
+
Fruit = TypeUnion[Apple, Orange]
|
60
|
+
```
|
61
|
+
"""
|
62
|
+
|
63
|
+
pass
|
@@ -3,6 +3,7 @@ import json
|
|
3
3
|
import os
|
4
4
|
from datetime import datetime
|
5
5
|
from pathlib import Path
|
6
|
+
from typing import Iterable
|
6
7
|
|
7
8
|
|
8
9
|
def make_session_id() -> str:
|
@@ -36,17 +37,20 @@ def try_parse_int(text: str) -> int | None:
|
|
36
37
|
except (ValueError, TypeError):
|
37
38
|
return None
|
38
39
|
|
40
|
+
|
39
41
|
def try_parse_float(text: str) -> float | None:
|
40
42
|
try:
|
41
43
|
return float(text)
|
42
44
|
except (ValueError, TypeError):
|
43
45
|
return None
|
44
46
|
|
47
|
+
|
45
48
|
def try_parse_bool(v: str | bool) -> bool:
|
46
49
|
if isinstance(v, bool):
|
47
50
|
return v
|
48
51
|
return v.lower() in ("yes", "true", "t", "1")
|
49
52
|
|
53
|
+
|
50
54
|
def pretty_line(text: str, cut_count: int = 100) -> str:
|
51
55
|
if len(text) > 100:
|
52
56
|
text_cut = text[:cut_count]
|
@@ -56,3 +60,10 @@ def pretty_line(text: str, cut_count: int = 100) -> str:
|
|
56
60
|
text_pretty = text
|
57
61
|
text_pretty = text_pretty.replace("\n", "\\n")
|
58
62
|
return text_pretty
|
63
|
+
|
64
|
+
|
65
|
+
def first_nonnull(obj: Iterable):
|
66
|
+
for elem in obj:
|
67
|
+
if elem:
|
68
|
+
return elem
|
69
|
+
return None
|
@@ -1,12 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: agi_med_common
|
3
|
-
Version: 5.0.
|
3
|
+
Version: 5.0.17
|
4
4
|
Summary: Сommon for agi-med team
|
5
5
|
Author: AGI-MED-TEAM
|
6
6
|
Requires-Python: >=3.11
|
7
7
|
Description-Content-Type: text/markdown
|
8
8
|
Requires-Dist: pydantic<=3.0.0,>=2.9.2
|
9
|
-
Requires-Dist: loguru~=0.7.2
|
10
9
|
Requires-Dist: tqdm==4.67.*
|
11
10
|
|
12
11
|
# agi-med-common
|
@@ -1,9 +1,9 @@
|
|
1
1
|
README.md
|
2
2
|
pyproject.toml
|
3
|
-
requirements.txt
|
4
3
|
src/agi_med_common/__init__.py
|
5
4
|
src/agi_med_common/file_storage.py
|
6
5
|
src/agi_med_common/parallel_map.py
|
6
|
+
src/agi_med_common/type_union.py
|
7
7
|
src/agi_med_common/utils.py
|
8
8
|
src/agi_med_common/validators.py
|
9
9
|
src/agi_med_common/xml_parser.py
|
@@ -13,15 +13,13 @@ src/agi_med_common.egg-info/dependency_links.txt
|
|
13
13
|
src/agi_med_common.egg-info/requires.txt
|
14
14
|
src/agi_med_common.egg-info/top_level.txt
|
15
15
|
src/agi_med_common/api/chat_manager_api.py
|
16
|
+
src/agi_med_common/api/classifier_api.py
|
16
17
|
src/agi_med_common/api/content_interpreter_api.py
|
17
18
|
src/agi_med_common/api/content_interpreter_remote_api.py
|
18
|
-
src/agi_med_common/api/
|
19
|
-
src/agi_med_common/api/text_processor_api.py
|
20
|
-
src/agi_med_common/logger/__init__.py
|
21
|
-
src/agi_med_common/logger/log_level_enum.py
|
22
|
-
src/agi_med_common/logger/logger.py
|
19
|
+
src/agi_med_common/api/text_generator_api.py
|
23
20
|
src/agi_med_common/models/__init__.py
|
24
21
|
src/agi_med_common/models/_base.py
|
22
|
+
src/agi_med_common/models/chat.py
|
25
23
|
src/agi_med_common/models/chat_item.py
|
26
24
|
src/agi_med_common/models/enums.py
|
27
25
|
src/agi_med_common/models/tracks.py
|
@@ -1,15 +0,0 @@
|
|
1
|
-
from enum import StrEnum, auto
|
2
|
-
|
3
|
-
|
4
|
-
class LogLevelEnum(StrEnum):
|
5
|
-
@staticmethod
|
6
|
-
def _generate_next_value_(name: str, start: int, count: int, last_values: list[str]) -> str:
|
7
|
-
return name.upper()
|
8
|
-
|
9
|
-
TRACE = auto() # logger.trace()
|
10
|
-
DEBUG = auto() # logger.debug()
|
11
|
-
INFO = auto() # logger.info()
|
12
|
-
SUCCESS = auto() # logger.success()
|
13
|
-
WARNING = auto() # logger.warning()
|
14
|
-
ERROR = auto() # logger.error()
|
15
|
-
CRITICAL = auto() # logger.critical()
|
@@ -1,28 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
|
-
from loguru import logger
|
4
|
-
|
5
|
-
from . import LogLevelEnum
|
6
|
-
|
7
|
-
|
8
|
-
def logger_init(log_level: LogLevelEnum) -> None:
|
9
|
-
logger.remove()
|
10
|
-
extra = {"request_id": "SYSTEM_LOG"}
|
11
|
-
format_ = "{time:DD-MM-YYYY HH:mm:ss} | <level>{level: <8}</level> | {extra[request_id]}"
|
12
|
-
format_ = f"{format_} | <level>{{message}}</level>"
|
13
|
-
logger.add(sys.stdout, colorize=True, format=format_, level=log_level)
|
14
|
-
logger.configure(extra=extra)
|
15
|
-
|
16
|
-
|
17
|
-
def log_llm_error(
|
18
|
-
text: str | None = None,
|
19
|
-
vector: list[float] | None = None,
|
20
|
-
model: str = "gigachat",
|
21
|
-
) -> None:
|
22
|
-
if text is not None and not text:
|
23
|
-
logger.error(f"No response from {model}!!!")
|
24
|
-
return None
|
25
|
-
if vector is not None and all(not item for item in vector):
|
26
|
-
logger.error(f"No response from {model} encoder!!!")
|
27
|
-
return None
|
28
|
-
return None
|
File without changes
|
File without changes
|
File without changes
|
{agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common/api/content_interpreter_api.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{agi_med_common-5.0.15 → agi_med_common-5.0.17}/src/agi_med_common.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|