waldiez 0.4.6__py3-none-any.whl → 0.4.8__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.
Potentially problematic release.
This version of waldiez might be problematic. Click here for more details.
- waldiez/__init__.py +5 -5
- waldiez/_version.py +1 -1
- waldiez/cli.py +112 -73
- waldiez/exporter.py +61 -19
- waldiez/exporting/__init__.py +25 -6
- waldiez/exporting/agent/__init__.py +7 -3
- waldiez/exporting/agent/code_execution.py +114 -0
- waldiez/exporting/agent/exporter.py +354 -0
- waldiez/exporting/agent/extras/__init__.py +15 -0
- waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
- waldiez/exporting/agent/extras/group/target.py +178 -0
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
- waldiez/exporting/agent/extras/group_member_extras.py +181 -0
- waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
- waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
- waldiez/exporting/agent/extras/handoffs/available.py +74 -0
- waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
- waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
- waldiez/exporting/agent/extras/handoffs/target.py +189 -0
- waldiez/exporting/agent/extras/rag/__init__.py +10 -0
- waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
- waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
- waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
- waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
- waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
- waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
- waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
- waldiez/exporting/agent/factory.py +95 -0
- waldiez/exporting/agent/processor.py +150 -0
- waldiez/exporting/agent/system_message.py +36 -0
- waldiez/exporting/agent/termination.py +50 -0
- waldiez/exporting/chats/__init__.py +7 -3
- waldiez/exporting/chats/exporter.py +97 -0
- waldiez/exporting/chats/factory.py +65 -0
- waldiez/exporting/chats/processor.py +226 -0
- waldiez/exporting/chats/utils/__init__.py +6 -5
- waldiez/exporting/chats/utils/common.py +11 -45
- waldiez/exporting/chats/utils/group.py +55 -0
- waldiez/exporting/chats/utils/nested.py +37 -52
- waldiez/exporting/chats/utils/sequential.py +72 -61
- waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
- waldiez/exporting/core/__init__.py +196 -0
- waldiez/exporting/core/constants.py +17 -0
- waldiez/exporting/core/content.py +69 -0
- waldiez/exporting/core/context.py +244 -0
- waldiez/exporting/core/enums.py +89 -0
- waldiez/exporting/core/errors.py +19 -0
- waldiez/exporting/core/exporter.py +390 -0
- waldiez/exporting/core/exporters.py +67 -0
- waldiez/exporting/core/extras/__init__.py +39 -0
- waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
- waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
- waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
- waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
- waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
- waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
- waldiez/exporting/core/extras/base.py +241 -0
- waldiez/exporting/core/extras/chat_extras.py +118 -0
- waldiez/exporting/core/extras/flow_extras.py +70 -0
- waldiez/exporting/core/extras/model_extras.py +73 -0
- waldiez/exporting/core/extras/path_resolver.py +93 -0
- waldiez/exporting/core/extras/serializer.py +138 -0
- waldiez/exporting/core/extras/tool_extras.py +82 -0
- waldiez/exporting/core/protocols.py +259 -0
- waldiez/exporting/core/result.py +705 -0
- waldiez/exporting/core/types.py +329 -0
- waldiez/exporting/core/utils/__init__.py +11 -0
- waldiez/exporting/core/utils/comment.py +33 -0
- waldiez/exporting/core/utils/llm_config.py +117 -0
- waldiez/exporting/core/validation.py +96 -0
- waldiez/exporting/flow/__init__.py +6 -2
- waldiez/exporting/flow/execution_generator.py +193 -0
- waldiez/exporting/flow/exporter.py +107 -0
- waldiez/exporting/flow/factory.py +94 -0
- waldiez/exporting/flow/file_generator.py +214 -0
- waldiez/exporting/flow/merger.py +387 -0
- waldiez/exporting/flow/orchestrator.py +411 -0
- waldiez/exporting/flow/utils/__init__.py +9 -36
- waldiez/exporting/flow/utils/common.py +206 -0
- waldiez/exporting/flow/utils/importing.py +373 -0
- waldiez/exporting/flow/utils/linting.py +200 -0
- waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
- waldiez/exporting/models/__init__.py +3 -1
- waldiez/exporting/models/exporter.py +233 -0
- waldiez/exporting/models/factory.py +66 -0
- waldiez/exporting/models/processor.py +139 -0
- waldiez/exporting/tools/__init__.py +11 -0
- waldiez/exporting/tools/exporter.py +207 -0
- waldiez/exporting/tools/factory.py +57 -0
- waldiez/exporting/tools/processor.py +248 -0
- waldiez/exporting/tools/registration.py +133 -0
- waldiez/io/__init__.py +128 -0
- waldiez/io/_ws.py +199 -0
- waldiez/io/models/__init__.py +60 -0
- waldiez/io/models/base.py +66 -0
- waldiez/io/models/constants.py +78 -0
- waldiez/io/models/content/__init__.py +23 -0
- waldiez/io/models/content/audio.py +43 -0
- waldiez/io/models/content/base.py +45 -0
- waldiez/io/models/content/file.py +43 -0
- waldiez/io/models/content/image.py +96 -0
- waldiez/io/models/content/text.py +37 -0
- waldiez/io/models/content/video.py +43 -0
- waldiez/io/models/user_input.py +269 -0
- waldiez/io/models/user_response.py +215 -0
- waldiez/io/mqtt.py +681 -0
- waldiez/io/redis.py +782 -0
- waldiez/io/structured.py +419 -0
- waldiez/io/utils.py +184 -0
- waldiez/io/ws.py +298 -0
- waldiez/logger.py +481 -0
- waldiez/models/__init__.py +108 -51
- waldiez/models/agents/__init__.py +34 -70
- waldiez/models/agents/agent/__init__.py +10 -4
- waldiez/models/agents/agent/agent.py +466 -65
- waldiez/models/agents/agent/agent_data.py +119 -47
- waldiez/models/agents/agent/agent_type.py +13 -2
- waldiez/models/agents/agent/code_execution.py +12 -12
- waldiez/models/agents/agent/human_input_mode.py +8 -0
- waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
- waldiez/models/agents/agent/nested_chat.py +35 -7
- waldiez/models/agents/agent/termination_message.py +30 -22
- waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
- waldiez/models/agents/agents.py +58 -63
- waldiez/models/agents/assistant/assistant.py +4 -4
- waldiez/models/agents/assistant/assistant_data.py +13 -1
- waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
- waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
- waldiez/models/agents/extra_requirements.py +11 -16
- waldiez/models/agents/group_manager/group_manager.py +103 -13
- waldiez/models/agents/group_manager/group_manager_data.py +36 -14
- waldiez/models/agents/group_manager/speakers.py +77 -24
- waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
- waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
- waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
- waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
- waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
- waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
- waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
- waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
- waldiez/models/agents/user_proxy/user_proxy.py +11 -7
- waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
- waldiez/models/chat/__init__.py +2 -1
- waldiez/models/chat/chat.py +166 -87
- waldiez/models/chat/chat_data.py +99 -136
- waldiez/models/chat/chat_message.py +33 -23
- waldiez/models/chat/chat_nested.py +31 -30
- waldiez/models/chat/chat_summary.py +10 -8
- waldiez/models/common/__init__.py +52 -2
- waldiez/models/common/ag2_version.py +1 -1
- waldiez/models/common/base.py +38 -7
- waldiez/models/common/dict_utils.py +42 -17
- waldiez/models/common/handoff.py +459 -0
- waldiez/models/common/id_generator.py +19 -0
- waldiez/models/common/method_utils.py +130 -68
- waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
- waldiez/models/common/waldiez_version.py +37 -0
- waldiez/models/flow/__init__.py +9 -2
- waldiez/models/flow/connection.py +18 -0
- waldiez/models/flow/flow.py +311 -215
- waldiez/models/flow/flow_data.py +207 -40
- waldiez/models/flow/info.py +85 -0
- waldiez/models/flow/naming.py +131 -0
- waldiez/models/model/__init__.py +7 -1
- waldiez/models/model/extra_requirements.py +3 -12
- waldiez/models/model/model.py +76 -21
- waldiez/models/model/model_data.py +108 -20
- waldiez/models/tool/__init__.py +16 -0
- waldiez/models/tool/extra_requirements.py +36 -0
- waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
- waldiez/models/tool/tool_data.py +51 -0
- waldiez/models/tool/tool_type.py +8 -0
- waldiez/models/waldiez.py +97 -80
- waldiez/runner.py +114 -49
- waldiez/running/__init__.py +1 -1
- waldiez/running/environment.py +49 -68
- waldiez/running/gen_seq_diagram.py +16 -14
- waldiez/running/running.py +53 -34
- waldiez/utils/__init__.py +0 -4
- waldiez/utils/cli_extras/jupyter.py +5 -3
- waldiez/utils/cli_extras/runner.py +6 -4
- waldiez/utils/cli_extras/studio.py +6 -4
- waldiez/utils/conflict_checker.py +15 -9
- waldiez/utils/flaml_warnings.py +5 -5
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
- waldiez-0.4.8.dist-info/RECORD +200 -0
- waldiez/exporting/agent/agent_exporter.py +0 -297
- waldiez/exporting/agent/utils/__init__.py +0 -23
- waldiez/exporting/agent/utils/captain_agent.py +0 -263
- waldiez/exporting/agent/utils/code_execution.py +0 -65
- waldiez/exporting/agent/utils/group_manager.py +0 -220
- waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
- waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
- waldiez/exporting/agent/utils/reasoning.py +0 -36
- waldiez/exporting/agent/utils/swarm_agent.py +0 -469
- waldiez/exporting/agent/utils/teachability.py +0 -41
- waldiez/exporting/agent/utils/termination_message.py +0 -44
- waldiez/exporting/base/__init__.py +0 -25
- waldiez/exporting/base/agent_position.py +0 -75
- waldiez/exporting/base/base_exporter.py +0 -118
- waldiez/exporting/base/export_position.py +0 -48
- waldiez/exporting/base/import_position.py +0 -23
- waldiez/exporting/base/mixin.py +0 -137
- waldiez/exporting/base/utils/__init__.py +0 -18
- waldiez/exporting/base/utils/comments.py +0 -96
- waldiez/exporting/base/utils/path_check.py +0 -68
- waldiez/exporting/base/utils/to_string.py +0 -84
- waldiez/exporting/chats/chats_exporter.py +0 -240
- waldiez/exporting/chats/utils/swarm.py +0 -210
- waldiez/exporting/flow/flow_exporter.py +0 -528
- waldiez/exporting/flow/utils/agent_utils.py +0 -204
- waldiez/exporting/flow/utils/chat_utils.py +0 -71
- waldiez/exporting/flow/utils/def_main.py +0 -77
- waldiez/exporting/flow/utils/flow_content.py +0 -202
- waldiez/exporting/flow/utils/flow_names.py +0 -116
- waldiez/exporting/flow/utils/importing_utils.py +0 -227
- waldiez/exporting/models/models_exporter.py +0 -199
- waldiez/exporting/models/utils.py +0 -174
- waldiez/exporting/skills/__init__.py +0 -9
- waldiez/exporting/skills/skills_exporter.py +0 -176
- waldiez/exporting/skills/utils.py +0 -369
- waldiez/models/agents/agent/teachability.py +0 -70
- waldiez/models/agents/rag_user/rag_user.py +0 -60
- waldiez/models/agents/swarm_agent/__init__.py +0 -50
- waldiez/models/agents/swarm_agent/after_work.py +0 -179
- waldiez/models/agents/swarm_agent/on_condition.py +0 -105
- waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
- waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
- waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
- waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
- waldiez/models/flow/utils.py +0 -232
- waldiez/models/skill/__init__.py +0 -16
- waldiez/models/skill/extra_requirements.py +0 -36
- waldiez/models/skill/skill_data.py +0 -53
- waldiez/models/skill/skill_type.py +0 -8
- waldiez/utils/pysqlite3_checker.py +0 -308
- waldiez/utils/rdps_checker.py +0 -122
- waldiez-0.4.6.dist-info/RECORD +0 -149
- /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
- /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# pylint: disable=unused-argument
|
|
4
|
+
"""Video and audio media content models."""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from typing_extensions import Literal
|
|
10
|
+
|
|
11
|
+
from .base import VideoContent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VideoMediaContent(BaseModel):
|
|
15
|
+
"""Video media content."""
|
|
16
|
+
|
|
17
|
+
type: Literal["video"] = "video"
|
|
18
|
+
video: VideoContent
|
|
19
|
+
|
|
20
|
+
def to_string(
|
|
21
|
+
self,
|
|
22
|
+
uploads_root: Path | None,
|
|
23
|
+
base_name: str | None = None,
|
|
24
|
+
) -> str:
|
|
25
|
+
"""Convert the content to a string.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
uploads_root : Path | None
|
|
30
|
+
The root directory for storing images, optional.
|
|
31
|
+
base_name : str | None
|
|
32
|
+
The base name for the image file, optional.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
str
|
|
37
|
+
The string representation of the content.
|
|
38
|
+
"""
|
|
39
|
+
if self.video.url:
|
|
40
|
+
return f"<video src='{self.video.url}'></video>"
|
|
41
|
+
if self.video.file:
|
|
42
|
+
return f"<video src='{self.video.file}'></video>"
|
|
43
|
+
return str(self.video.file)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
"""User input data models and validation."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Union
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field, field_validator
|
|
13
|
+
from typing_extensions import Annotated
|
|
14
|
+
|
|
15
|
+
from ..utils import detect_media_type
|
|
16
|
+
from .constants import CONTENT_MAPPING, ContentMappingEntry, MediaContent
|
|
17
|
+
from .content.audio import AudioMediaContent
|
|
18
|
+
from .content.base import (
|
|
19
|
+
AudioContent,
|
|
20
|
+
FileContent,
|
|
21
|
+
ImageContent,
|
|
22
|
+
VideoContent,
|
|
23
|
+
)
|
|
24
|
+
from .content.file import FileMediaContent
|
|
25
|
+
from .content.image import ImageMediaContent, ImageUrlMediaContent
|
|
26
|
+
from .content.text import TextMediaContent
|
|
27
|
+
from .content.video import VideoMediaContent
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UserInputData(BaseModel):
|
|
31
|
+
"""User's input data model."""
|
|
32
|
+
|
|
33
|
+
content: Annotated[
|
|
34
|
+
Union[MediaContent, list[MediaContent]],
|
|
35
|
+
Field(
|
|
36
|
+
description="The content of the input data.",
|
|
37
|
+
title="Content",
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
def __str__(self) -> str:
|
|
42
|
+
"""Get the string representation of the content."""
|
|
43
|
+
return self.to_string()
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
"""Get the string representation of the UserInputData."""
|
|
47
|
+
return f"UserInputData(content={self.to_string()})"
|
|
48
|
+
|
|
49
|
+
def to_string(
|
|
50
|
+
self,
|
|
51
|
+
uploads_root: Path | None = None,
|
|
52
|
+
base_name: str | None = None,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Convert the content to a string.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
uploads_root : Path | None
|
|
59
|
+
The root directory for storing images, optional.
|
|
60
|
+
base_name : str | None
|
|
61
|
+
The base name for the image file, optional.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
str
|
|
66
|
+
The string representation of the content.
|
|
67
|
+
"""
|
|
68
|
+
if isinstance(self.content, list):
|
|
69
|
+
return " ".join(
|
|
70
|
+
[
|
|
71
|
+
item.to_string(uploads_root, base_name) # pyright: ignore
|
|
72
|
+
for item in self.content
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
return self.content.to_string(uploads_root, base_name)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def content_from_string(cls, value: str) -> MediaContent:
|
|
79
|
+
"""Convert a string to the appropriate MediaContent type.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
value : str
|
|
84
|
+
The input string
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
MediaContent
|
|
89
|
+
the appropriate MediaContent.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
parsed = json.loads(value)
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
return TextMediaContent(type="text", text=value)
|
|
95
|
+
if isinstance(parsed, str):
|
|
96
|
+
try:
|
|
97
|
+
parsed = json.loads(parsed)
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
return TextMediaContent(type="text", text=parsed)
|
|
100
|
+
if isinstance(parsed, str):
|
|
101
|
+
return TextMediaContent(type="text", text=parsed)
|
|
102
|
+
return cls.validate_content(parsed)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def content_from_dict(cls, value: dict[str, Any]) -> MediaContent:
|
|
106
|
+
"""Convert a dictionary to the appropriate MediaContent type.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
value : dict[str, Any]
|
|
111
|
+
The input dictionary
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
MediaContent
|
|
116
|
+
the appropriate MediaContent.
|
|
117
|
+
|
|
118
|
+
Raises
|
|
119
|
+
------
|
|
120
|
+
ValueError
|
|
121
|
+
If the content type is not supported or
|
|
122
|
+
if any required field is missing.
|
|
123
|
+
"""
|
|
124
|
+
content_type = detect_media_type(value)
|
|
125
|
+
|
|
126
|
+
# Get the mapping for the detected content type
|
|
127
|
+
if content_type not in CONTENT_MAPPING:
|
|
128
|
+
raise ValueError(f"Unsupported content type: {content_type}")
|
|
129
|
+
|
|
130
|
+
# Get the mapping for the content type
|
|
131
|
+
mapping = CONTENT_MAPPING[content_type]
|
|
132
|
+
return cls._build_media_content(value, mapping, content_type)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def _build_media_content(
|
|
136
|
+
cls,
|
|
137
|
+
value: dict[str, Any],
|
|
138
|
+
mapping: ContentMappingEntry,
|
|
139
|
+
content_type: str,
|
|
140
|
+
) -> MediaContent:
|
|
141
|
+
"""Try to construct the appropriate MediaContent from the mapping."""
|
|
142
|
+
for field in mapping["fields"]:
|
|
143
|
+
if field not in value:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
raw_val = value[field]
|
|
147
|
+
|
|
148
|
+
if field == mapping["required_field"]:
|
|
149
|
+
# if we have direct mapping to the required field,
|
|
150
|
+
# let's try to instantiate the class directly
|
|
151
|
+
try:
|
|
152
|
+
return mapping["cls"](**{field: raw_val})
|
|
153
|
+
except ValueError:
|
|
154
|
+
pass # let's try to convert it
|
|
155
|
+
|
|
156
|
+
converted = cls._convert_simple_content(
|
|
157
|
+
raw_val, mapping["required_field"]
|
|
158
|
+
)
|
|
159
|
+
if converted is not None: # pragma: no branch
|
|
160
|
+
return mapping["cls"](
|
|
161
|
+
**{mapping["required_field"]: converted} # type: ignore
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"Missing required field for content type "
|
|
166
|
+
f"'{content_type}' in: {value}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def _convert_simple_content(
|
|
171
|
+
raw_val: Any, target_field: str
|
|
172
|
+
) -> ImageContent | VideoContent | AudioContent | FileContent | None:
|
|
173
|
+
"""Convert a simple string to the appropriate content, if applicable."""
|
|
174
|
+
if not isinstance(raw_val, str): # pragma: no cover
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
if target_field in ("image_url", "image"):
|
|
178
|
+
return ImageContent(url=raw_val)
|
|
179
|
+
if target_field == "video":
|
|
180
|
+
return VideoContent(url=raw_val)
|
|
181
|
+
if target_field == "audio":
|
|
182
|
+
return AudioContent(url=raw_val)
|
|
183
|
+
if target_field == "file":
|
|
184
|
+
filename = extract_filename_from_path(raw_val)
|
|
185
|
+
return FileContent(name=filename, url=raw_val)
|
|
186
|
+
|
|
187
|
+
return None # pragma: no cover
|
|
188
|
+
|
|
189
|
+
@field_validator("content", mode="before")
|
|
190
|
+
@classmethod
|
|
191
|
+
def validate_content(cls, v: Any) -> MediaContent: # noqa: C901,D102
|
|
192
|
+
"""Validate the input data content.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
v: Any
|
|
197
|
+
The input data content
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
MediaContent
|
|
202
|
+
The validated content
|
|
203
|
+
|
|
204
|
+
Raises
|
|
205
|
+
------
|
|
206
|
+
ValueError
|
|
207
|
+
If the content is not valid.
|
|
208
|
+
"""
|
|
209
|
+
if isinstance(
|
|
210
|
+
v,
|
|
211
|
+
(
|
|
212
|
+
TextMediaContent,
|
|
213
|
+
ImageMediaContent,
|
|
214
|
+
ImageUrlMediaContent,
|
|
215
|
+
VideoMediaContent,
|
|
216
|
+
AudioMediaContent,
|
|
217
|
+
FileMediaContent,
|
|
218
|
+
),
|
|
219
|
+
):
|
|
220
|
+
return v
|
|
221
|
+
|
|
222
|
+
# If it's a string, check if it is a dumped one
|
|
223
|
+
if isinstance(v, str):
|
|
224
|
+
return cls.content_from_string(v)
|
|
225
|
+
|
|
226
|
+
# If it's a dictionary, check if it has a 'type' field
|
|
227
|
+
if isinstance(v, dict):
|
|
228
|
+
return cls.content_from_dict(v) # pyright: ignore
|
|
229
|
+
|
|
230
|
+
# If it's a list
|
|
231
|
+
if isinstance(v, list):
|
|
232
|
+
return [cls.validate_content(item) for item in v] # type: ignore
|
|
233
|
+
|
|
234
|
+
# Default fallback
|
|
235
|
+
return TextMediaContent(type="text", text=str(v))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def extract_filename_from_path(path_or_url: str) -> str:
|
|
239
|
+
"""Extract the filename from a given path or URL.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
path_or_url: str
|
|
244
|
+
The path or URL from which to extract the filename.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
str
|
|
249
|
+
The extracted filename.
|
|
250
|
+
"""
|
|
251
|
+
if "://" in path_or_url:
|
|
252
|
+
# It's a URL - parse it properly
|
|
253
|
+
parsed = urlparse(path_or_url)
|
|
254
|
+
# Extract filename from the path component
|
|
255
|
+
filename = os.path.basename(parsed.path)
|
|
256
|
+
|
|
257
|
+
# Handle edge cases where path might be empty or end with /
|
|
258
|
+
if not filename and parsed.path.endswith("/"):
|
|
259
|
+
# Try to get the last directory name
|
|
260
|
+
path_parts = [p for p in parsed.path.split("/") if p]
|
|
261
|
+
filename = path_parts[-1] if path_parts else parsed.netloc
|
|
262
|
+
elif not filename:
|
|
263
|
+
# Fallback to using netloc (domain) if no path
|
|
264
|
+
filename = parsed.netloc
|
|
265
|
+
|
|
266
|
+
return filename
|
|
267
|
+
# else:
|
|
268
|
+
# Local file path
|
|
269
|
+
return os.path.basename(path_or_url)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""User response model and validation."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable, Optional, Union
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError, field_validator
|
|
10
|
+
|
|
11
|
+
from ..utils import MessageType
|
|
12
|
+
from .base import StructuredBase
|
|
13
|
+
from .content.text import TextMediaContent
|
|
14
|
+
from .user_input import UserInputData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UserResponse(StructuredBase):
|
|
18
|
+
"""User response model."""
|
|
19
|
+
|
|
20
|
+
request_id: str
|
|
21
|
+
type: MessageType = "input_response"
|
|
22
|
+
data: UserInputData | list[UserInputData] | str
|
|
23
|
+
|
|
24
|
+
@field_validator("data", mode="before")
|
|
25
|
+
@classmethod
|
|
26
|
+
def validate_data(
|
|
27
|
+
cls, value: Any
|
|
28
|
+
) -> Optional[Union[str, UserInputData, list[UserInputData]]]:
|
|
29
|
+
"""Validate the data field in UserResponse.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
value : Any
|
|
34
|
+
The value to validate.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
Union[str, UserInputData, list[UserInputData]]
|
|
39
|
+
The validated value.
|
|
40
|
+
|
|
41
|
+
Raises
|
|
42
|
+
------
|
|
43
|
+
ValueError
|
|
44
|
+
If the value is not valid.
|
|
45
|
+
"""
|
|
46
|
+
if value is None:
|
|
47
|
+
return ""
|
|
48
|
+
if cls._is_valid_type(value):
|
|
49
|
+
return value
|
|
50
|
+
|
|
51
|
+
handlers: dict[
|
|
52
|
+
type,
|
|
53
|
+
Callable[[Any], Union[str, UserInputData, list[UserInputData]]],
|
|
54
|
+
] = {
|
|
55
|
+
str: cls._handle_string,
|
|
56
|
+
dict: cls._handle_dict,
|
|
57
|
+
list: cls._handle_list,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
value_type = type(value) # pyright: ignore
|
|
61
|
+
handler = handlers.get(
|
|
62
|
+
value_type, # pyright: ignore
|
|
63
|
+
cls._handle_default,
|
|
64
|
+
)
|
|
65
|
+
result = handler(value)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def _is_valid_type(cls, value: Any) -> bool:
|
|
70
|
+
"""Check if value is already a valid type."""
|
|
71
|
+
return isinstance(value, UserInputData) or (
|
|
72
|
+
isinstance(value, list)
|
|
73
|
+
and all(
|
|
74
|
+
isinstance(item, UserInputData)
|
|
75
|
+
for item in value # pyright: ignore
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _handle_string(
|
|
81
|
+
cls, value: str
|
|
82
|
+
) -> Union[str, UserInputData, list[UserInputData]]:
|
|
83
|
+
"""Handle string input.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
value : str
|
|
88
|
+
The string value to handle.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
Union[str, UserInputData, list[UserInputData]]
|
|
93
|
+
The handled value.
|
|
94
|
+
"""
|
|
95
|
+
# pylint: disable=too-many-try-statements
|
|
96
|
+
try:
|
|
97
|
+
parsed_value = json.loads(value)
|
|
98
|
+
if isinstance(parsed_value, dict):
|
|
99
|
+
return cls._handle_dict(parsed_value) # pyright: ignore
|
|
100
|
+
if isinstance(parsed_value, list):
|
|
101
|
+
return cls._handle_list(parsed_value) # pyright: ignore
|
|
102
|
+
return cls._create_text_input(str(parsed_value))
|
|
103
|
+
except json.JSONDecodeError:
|
|
104
|
+
return cls._create_text_input(value)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def _handle_dict(cls, value: dict[str, Any]) -> UserInputData:
|
|
108
|
+
if "content" in value:
|
|
109
|
+
return cls._try_parse_user_input(value)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
return UserInputData(content=value) # type: ignore
|
|
113
|
+
except (ValueError, TypeError, ValidationError):
|
|
114
|
+
return cls._create_invalid_input(value, label="Invalid data")
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def _try_parse_user_input(cls, value: dict[str, Any]) -> UserInputData:
|
|
118
|
+
try:
|
|
119
|
+
return UserInputData(content=value["content"])
|
|
120
|
+
except (KeyError, ValueError, TypeError, ValidationError):
|
|
121
|
+
try:
|
|
122
|
+
return UserInputData(content=value) # type: ignore
|
|
123
|
+
except (ValueError, TypeError, ValidationError):
|
|
124
|
+
return cls._create_invalid_input(value, label="Invalid content")
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def _handle_list(
|
|
128
|
+
cls, value: list[Any]
|
|
129
|
+
) -> Union[UserInputData, list[UserInputData]]:
|
|
130
|
+
result: list[UserInputData] = []
|
|
131
|
+
|
|
132
|
+
for item in value:
|
|
133
|
+
if isinstance(item, UserInputData):
|
|
134
|
+
result.append(item)
|
|
135
|
+
elif isinstance(item, dict):
|
|
136
|
+
result.append(cls._handle_dict(item)) # pyright: ignore
|
|
137
|
+
elif isinstance(item, str):
|
|
138
|
+
result.append(cls._create_text_input(item))
|
|
139
|
+
elif isinstance(item, list):
|
|
140
|
+
nested_result = cls._handle_list(item) # pyright: ignore
|
|
141
|
+
if isinstance(nested_result, list):
|
|
142
|
+
result.extend(nested_result)
|
|
143
|
+
else:
|
|
144
|
+
result.append(nested_result)
|
|
145
|
+
else:
|
|
146
|
+
result.append(cls._create_text_input(str(item)))
|
|
147
|
+
|
|
148
|
+
return result[0] if len(result) == 1 else result
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def _handle_default(cls, value: Any) -> UserInputData:
|
|
152
|
+
return cls._create_text_input(str(value))
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def _create_text_input(cls, text: str) -> UserInputData:
|
|
156
|
+
return UserInputData(content=TextMediaContent(text=text))
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def _create_invalid_input(cls, raw: Any, label: str) -> UserInputData:
|
|
160
|
+
try:
|
|
161
|
+
preview = json.dumps(raw)[:100]
|
|
162
|
+
except (TypeError, ValueError): # pragma: no cover
|
|
163
|
+
preview = str(raw)[:100]
|
|
164
|
+
return UserInputData(
|
|
165
|
+
content=TextMediaContent(text=f"{label}: {preview}...")
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def to_string(
|
|
169
|
+
self,
|
|
170
|
+
uploads_root: Path | None = None,
|
|
171
|
+
base_name: str | None = None,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""Convert the UserResponse to a string.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
uploads_root : Path | None
|
|
178
|
+
The root directory for storing images, optional.
|
|
179
|
+
base_name : str | None
|
|
180
|
+
The base name for the image file, optional.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
str
|
|
185
|
+
The string representation of the UserResponse.
|
|
186
|
+
"""
|
|
187
|
+
if isinstance(self.data, list):
|
|
188
|
+
return " ".join(
|
|
189
|
+
[
|
|
190
|
+
(
|
|
191
|
+
item.to_string(
|
|
192
|
+
uploads_root=uploads_root, base_name=base_name
|
|
193
|
+
)
|
|
194
|
+
if hasattr(item, "to_string")
|
|
195
|
+
else (
|
|
196
|
+
item
|
|
197
|
+
if isinstance(item, str) # pragma: no cover
|
|
198
|
+
else str(item)
|
|
199
|
+
) # pragma: no cover
|
|
200
|
+
)
|
|
201
|
+
for item in self.data
|
|
202
|
+
]
|
|
203
|
+
).strip()
|
|
204
|
+
if isinstance(self.data, UserInputData):
|
|
205
|
+
return self.data.to_string(
|
|
206
|
+
uploads_root=uploads_root, base_name=base_name
|
|
207
|
+
).strip()
|
|
208
|
+
# we have probably returned sth till here
|
|
209
|
+
if isinstance(self.data, str): # pyright: ignore # pragma: no cover
|
|
210
|
+
return self.data
|
|
211
|
+
return ( # pragma: no cover
|
|
212
|
+
json.dumps(self.data)
|
|
213
|
+
if hasattr(self.data, "__dict__")
|
|
214
|
+
else str(self.data)
|
|
215
|
+
)
|