agi-med-common 5.0.16__tar.gz → 5.0.18__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.
Files changed (32) hide show
  1. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/PKG-INFO +1 -1
  2. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/__init__.py +2 -1
  3. agi_med_common-5.0.18/src/agi_med_common/api/critic_api.py +6 -0
  4. agi_med_common-5.0.18/src/agi_med_common/models/chat.py +267 -0
  5. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/chat_item.py +28 -40
  6. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common.egg-info/PKG-INFO +1 -1
  7. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common.egg-info/SOURCES.txt +1 -0
  8. agi_med_common-5.0.16/src/agi_med_common/models/chat.py +0 -107
  9. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/README.md +0 -0
  10. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/pyproject.toml +0 -0
  11. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/setup.cfg +0 -0
  12. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/api/chat_manager_api.py +0 -0
  13. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/api/classifier_api.py +0 -0
  14. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/api/content_interpreter_api.py +0 -0
  15. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/api/content_interpreter_remote_api.py +0 -0
  16. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/api/text_generator_api.py +0 -0
  17. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/file_storage.py +0 -0
  18. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/__init__.py +0 -0
  19. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/_base.py +0 -0
  20. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/base_config_models/__init__.py +0 -0
  21. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/base_config_models/gigachat_config.py +0 -0
  22. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/enums.py +0 -0
  23. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/tracks.py +0 -0
  24. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/models/widget.py +0 -0
  25. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/parallel_map.py +0 -0
  26. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/type_union.py +0 -0
  27. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/utils.py +0 -0
  28. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/validators.py +0 -0
  29. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common/xml_parser.py +0 -0
  30. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common.egg-info/dependency_links.txt +0 -0
  31. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common.egg-info/requires.txt +0 -0
  32. {agi_med_common-5.0.16 → agi_med_common-5.0.18}/src/agi_med_common.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agi_med_common
3
- Version: 5.0.16
3
+ Version: 5.0.18
4
4
  Summary: Сommon for agi-med team
5
5
  Author: AGI-MED-TEAM
6
6
  Requires-Python: >=3.11
@@ -1,4 +1,4 @@
1
- __version__ = "5.0.16"
1
+ __version__ = "5.0.18"
2
2
 
3
3
  from .models import (
4
4
  MTRSLabelEnum,
@@ -26,3 +26,4 @@ from .api.content_interpreter_api import ContentInterpreterAPI, Interpretation
26
26
  from .api.content_interpreter_remote_api import ContentInterpreterRemoteAPI
27
27
  from .api.text_generator_api import TextGeneratorAPI
28
28
  from .api.classifier_api import ClassifierAPI
29
+ from .api.critic_api import CriticAPI
@@ -0,0 +1,6 @@
1
+ from agi_med_common.models import ChatItem
2
+
3
+
4
+ class CriticAPI:
5
+ def evaluate(self, text: str, chat: ChatItem | None = None, request_id: str = "") -> float:
6
+ raise NotImplementedError
@@ -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,11 +1,8 @@
1
- import json
2
- import warnings
3
1
  from datetime import datetime
4
- from typing import Any, List
2
+ from typing import Annotated, Any, List
5
3
 
6
4
  from agi_med_common.models.widget import Widget
7
- from loguru import logger
8
- from pydantic import Field, ConfigDict, ValidationError
5
+ from pydantic import Field, ConfigDict, BeforeValidator
9
6
 
10
7
  from ._base import _Base
11
8
 
@@ -42,6 +39,24 @@ class OuterContextItem(_Base):
42
39
  def to_dict(self) -> dict[str, Any]:
43
40
  return self.model_dump(by_alias=True)
44
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
+
45
60
 
46
61
  class ReplicaItem(_Base):
47
62
  body: str = Field("", alias="Body", examples=["Привет"])
@@ -55,7 +70,9 @@ class ReplicaItem(_Base):
55
70
  state: str = Field("", alias="State", description="chat manager fsm state", examples=["COLLECTION"])
56
71
  action: str = Field("", alias="Action", description="chat manager fsm action", examples=["DIAGNOSIS"])
57
72
  # todo fix: support loading from `moderation: int`
58
- moderation: str = Field("OK", alias="Moderation", description="moderation outcome", examples=["OK"])
73
+ moderation: Annotated[str, BeforeValidator(str)] = Field(
74
+ "OK", alias="Moderation", description="moderation outcome", examples=["OK"]
75
+ )
59
76
  extra: dict | None = Field(None, alias="Extra", examples=[None])
60
77
 
61
78
  def to_dict(self) -> dict[str, Any]:
@@ -99,40 +116,11 @@ class ChatItem(_Base):
99
116
 
100
117
  @classmethod
101
118
  def parse(cls, chat_obj: str | dict) -> "ChatItem":
102
- return _parse_chat(chat_obj)
103
-
119
+ return _parse_chat_item(chat_obj)
104
120
 
105
- def _is_moderation_int_error(err):
106
- # fmt: off
107
- if err['type'] != 'string_type': return False
108
- if err['loc'][0] != 'InnerContext': return False
109
- if err['loc'][1] != 'Replicas': return False
110
- if err['loc'][3] != 'Moderation': return False
111
- if err['msg'] != 'Input should be a valid string': return False
112
- # fmt: on
113
- return True
114
121
 
115
-
116
- def _is_moderation_int_errors(ex):
117
- errs = ex.errors()
118
- return all(map(_is_moderation_int_error, errs))
119
-
120
-
121
- def _parse_chat(chat_obj: str | dict) -> ChatItem:
122
+ def _parse_chat_item(chat_obj: str | dict) -> ChatItem:
122
123
  if isinstance(chat_obj, dict):
123
- return ChatItem(**chat_obj)
124
- try:
125
- return ChatItem.model_validate_json(chat_obj)
126
- except ValidationError as ex:
127
- if _is_moderation_int_errors(ex):
128
- msg = "Failed to parse ChatItem, fallback to old version with `Moderation:int`"
129
- else:
130
- msg = f"Failed to parse: {ex}"
131
- warnings.warn(msg)
132
-
133
- # old version
134
- chat_dict = json.loads(chat_obj)
135
- for rep in chat_dict["InnerContext"]["Replicas"]:
136
- rep["Moderation"] = "OK"
137
-
138
- return ChatItem.model_validate(chat_dict)
124
+ return ChatItem.model_validate(chat_obj)
125
+
126
+ return ChatItem.model_validate_json(chat_obj)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agi_med_common
3
- Version: 5.0.16
3
+ Version: 5.0.18
4
4
  Summary: Сommon for agi-med team
5
5
  Author: AGI-MED-TEAM
6
6
  Requires-Python: >=3.11
@@ -16,6 +16,7 @@ src/agi_med_common/api/chat_manager_api.py
16
16
  src/agi_med_common/api/classifier_api.py
17
17
  src/agi_med_common/api/content_interpreter_api.py
18
18
  src/agi_med_common/api/content_interpreter_remote_api.py
19
+ src/agi_med_common/api/critic_api.py
19
20
  src/agi_med_common/api/text_generator_api.py
20
21
  src/agi_med_common/models/__init__.py
21
22
  src/agi_med_common/models/_base.py
@@ -1,107 +0,0 @@
1
- from datetime import datetime
2
- from typing import Any, List, Dict, Literal
3
-
4
- from agi_med_common.models.widget import Widget
5
- from agi_med_common.type_union import TypeUnion
6
- from agi_med_common.utils import first_nonnull
7
- from pydantic import Field
8
-
9
- from ._base import _Base
10
-
11
-
12
- _DT_FORMAT: str = "%Y-%m-%d-%H-%M-%S"
13
- _EXAMPLE_DT: str = datetime(year=1970, month=1, day=1).strftime(_DT_FORMAT)
14
- StrDict = Dict[str, Any]
15
- ContentBase = str | Widget | StrDict
16
- Content = ContentBase | List[ContentBase]
17
-
18
-
19
- def now_pretty() -> str:
20
- return datetime.now().strftime(_DT_FORMAT)
21
-
22
-
23
- class Context(_Base):
24
- client_id: str = Field("", examples=["543216789"])
25
- user_id: str = Field("", examples=["123456789"])
26
- session_id: str = Field("", examples=["987654321"])
27
- track_id: str = Field(examples=["Hello"])
28
- extra: StrDict | None = Field(None, examples=[None])
29
-
30
- def create_id(self, short: bool = False) -> str:
31
- uid, sid, cid = self.user_id, self.session_id, self.client_id
32
- if short:
33
- return f"{cid}_{uid}_{sid}"
34
- return f"client_{cid}_user_{uid}_session_{sid}"
35
-
36
-
37
- def _get_str_field(obj: dict, field) -> str | None:
38
- if not isinstance(obj, dict):
39
- return None
40
- text = obj.get(field)
41
- if text is not None and isinstance(text, str):
42
- return text
43
- return None
44
-
45
-
46
- def _get_text(obj: Content) -> str:
47
- if isinstance(obj, str):
48
- return obj
49
- if isinstance(obj, list):
50
- return "".join(map(_get_text, obj))
51
- if isinstance(obj, dict) and obj.get("type") == "text":
52
- return _get_str_field(obj, "text") or ""
53
- return ""
54
-
55
-
56
- def _get_resource_id(obj: Content) -> str | None:
57
- if isinstance(obj, list):
58
- return first_nonnull(_get_str_field(el, "resource_id") for el in obj)
59
- if isinstance(obj, dict) and obj.get("type") == "resource_id":
60
- return _get_str_field(obj, "resource_id")
61
- return None
62
-
63
-
64
- class BaseMessage(_Base):
65
- type: str
66
- content: Content = Field("", examples=["Привет"])
67
- date_time: str = Field(default_factory=now_pretty, examples=[_EXAMPLE_DT])
68
- extra: StrDict | None = Field(None, examples=[None])
69
-
70
- @property
71
- def text(self) -> str:
72
- return _get_text(self.content)
73
-
74
- @property
75
- def resource_id(self) -> str | None:
76
- return _get_resource_id(self.content)
77
-
78
- @staticmethod
79
- def DATETIME_FORMAT() -> str:
80
- return _DT_FORMAT
81
-
82
- def with_now_datetime(self):
83
- return self.model_copy(update=dict(date_time=now_pretty()))
84
-
85
-
86
- class HumanMessage(BaseMessage):
87
- type: Literal["human"] = "human"
88
-
89
-
90
- class AIMessage(BaseMessage):
91
- type: Literal["ai"] = "ai"
92
- state: str = Field("", examples=["COLLECTION"])
93
-
94
-
95
- class MiscMessage(BaseMessage):
96
- type: Literal["misc"] = "misc"
97
-
98
-
99
- ChatMessage = TypeUnion[HumanMessage, AIMessage, MiscMessage]
100
-
101
-
102
- class Chat(_Base):
103
- context: Context
104
- messages: List[ChatMessage] = []
105
-
106
- def create_id(self, short: bool = False) -> str:
107
- return self.context.create_id(short)