waldiez 0.6.0__py3-none-any.whl → 0.6.1__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 +1 -1
- waldiez/_version.py +1 -1
- waldiez/cli.py +18 -7
- waldiez/cli_extras/jupyter.py +3 -0
- waldiez/cli_extras/runner.py +3 -1
- waldiez/cli_extras/studio.py +3 -1
- waldiez/exporter.py +9 -3
- waldiez/exporting/agent/exporter.py +9 -10
- waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
- waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +34 -23
- waldiez/exporting/agent/extras/group_member_extras.py +6 -5
- waldiez/exporting/agent/extras/handoffs/after_work.py +1 -1
- waldiez/exporting/agent/extras/handoffs/available.py +1 -1
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
- waldiez/exporting/agent/extras/handoffs/handoff.py +1 -1
- waldiez/exporting/agent/extras/handoffs/target.py +6 -4
- waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
- waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
- waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
- waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
- waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
- waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
- waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
- waldiez/exporting/chats/exporter.py +4 -4
- waldiez/exporting/chats/processor.py +1 -2
- waldiez/exporting/chats/utils/common.py +89 -48
- waldiez/exporting/chats/utils/group.py +9 -9
- waldiez/exporting/chats/utils/nested.py +7 -7
- waldiez/exporting/chats/utils/sequential.py +1 -1
- waldiez/exporting/chats/utils/single.py +2 -2
- waldiez/exporting/core/content.py +7 -7
- waldiez/exporting/core/context.py +5 -3
- waldiez/exporting/core/exporter.py +5 -3
- waldiez/exporting/core/exporters.py +2 -2
- waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
- waldiez/exporting/core/extras/base.py +7 -5
- waldiez/exporting/core/extras/flow_extras.py +4 -5
- waldiez/exporting/core/extras/model_extras.py +2 -2
- waldiez/exporting/core/extras/path_resolver.py +1 -2
- waldiez/exporting/core/extras/serializer.py +2 -2
- waldiez/exporting/core/protocols.py +6 -5
- waldiez/exporting/core/result.py +25 -28
- waldiez/exporting/core/types.py +10 -10
- waldiez/exporting/core/utils/llm_config.py +2 -2
- waldiez/exporting/core/validation.py +10 -11
- waldiez/exporting/flow/execution_generator.py +98 -10
- waldiez/exporting/flow/exporter.py +2 -2
- waldiez/exporting/flow/factory.py +2 -2
- waldiez/exporting/flow/file_generator.py +4 -2
- waldiez/exporting/flow/merger.py +5 -3
- waldiez/exporting/flow/orchestrator.py +72 -2
- waldiez/exporting/flow/utils/common.py +5 -5
- waldiez/exporting/flow/utils/importing.py +6 -7
- waldiez/exporting/flow/utils/linting.py +25 -9
- waldiez/exporting/flow/utils/logging.py +2 -2
- waldiez/exporting/models/exporter.py +8 -8
- waldiez/exporting/models/processor.py +5 -5
- waldiez/exporting/tools/exporter.py +2 -2
- waldiez/exporting/tools/processor.py +7 -4
- waldiez/io/__init__.py +8 -4
- waldiez/io/_ws.py +10 -6
- waldiez/io/models/constants.py +10 -10
- waldiez/io/models/content/audio.py +1 -0
- waldiez/io/models/content/base.py +20 -18
- waldiez/io/models/content/file.py +1 -0
- waldiez/io/models/content/image.py +1 -0
- waldiez/io/models/content/text.py +1 -0
- waldiez/io/models/content/video.py +1 -0
- waldiez/io/models/user_input.py +10 -5
- waldiez/io/models/user_response.py +17 -16
- waldiez/io/mqtt.py +18 -31
- waldiez/io/redis.py +18 -22
- waldiez/io/structured.py +52 -53
- waldiez/io/utils.py +3 -0
- waldiez/io/ws.py +5 -1
- waldiez/logger.py +16 -3
- waldiez/models/agents/__init__.py +3 -0
- waldiez/models/agents/agent/agent.py +23 -16
- waldiez/models/agents/agent/agent_data.py +25 -22
- waldiez/models/agents/agent/code_execution.py +9 -11
- waldiez/models/agents/agent/termination_message.py +10 -12
- waldiez/models/agents/agent/update_system_message.py +2 -4
- waldiez/models/agents/agents.py +8 -8
- waldiez/models/agents/assistant/assistant.py +6 -3
- waldiez/models/agents/assistant/assistant_data.py +2 -2
- waldiez/models/agents/captain/captain_agent.py +7 -4
- waldiez/models/agents/captain/captain_agent_data.py +5 -7
- waldiez/models/agents/doc_agent/doc_agent.py +7 -4
- waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
- waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
- waldiez/models/agents/extra_requirements.py +3 -3
- waldiez/models/agents/group_manager/group_manager.py +12 -7
- waldiez/models/agents/group_manager/group_manager_data.py +13 -12
- waldiez/models/agents/group_manager/speakers.py +17 -19
- waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
- waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
- waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
- waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
- waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
- waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
- waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
- waldiez/models/agents/user_proxy/user_proxy.py +6 -3
- waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
- waldiez/models/chat/chat.py +27 -20
- waldiez/models/chat/chat_data.py +22 -19
- waldiez/models/chat/chat_message.py +9 -9
- waldiez/models/chat/chat_nested.py +9 -9
- waldiez/models/chat/chat_summary.py +6 -6
- waldiez/models/common/__init__.py +2 -0
- waldiez/models/common/ag2_version.py +2 -0
- waldiez/models/common/dict_utils.py +8 -6
- waldiez/models/common/handoff.py +18 -17
- waldiez/models/common/method_utils.py +7 -7
- waldiez/models/common/naming.py +49 -0
- waldiez/models/flow/flow.py +11 -6
- waldiez/models/flow/flow_data.py +23 -17
- waldiez/models/flow/info.py +3 -3
- waldiez/models/flow/naming.py +2 -1
- waldiez/models/model/_aws.py +11 -13
- waldiez/models/model/_llm.py +5 -0
- waldiez/models/model/_price.py +2 -4
- waldiez/models/model/extra_requirements.py +1 -3
- waldiez/models/model/model.py +2 -2
- waldiez/models/model/model_data.py +21 -21
- waldiez/models/tool/extra_requirements.py +2 -4
- waldiez/models/tool/predefined/_duckduckgo.py +1 -0
- waldiez/models/tool/predefined/_email.py +1 -0
- waldiez/models/tool/predefined/_google.py +1 -0
- waldiez/models/tool/predefined/_perplexity.py +1 -0
- waldiez/models/tool/predefined/_searxng.py +1 -0
- waldiez/models/tool/predefined/_tavily.py +1 -0
- waldiez/models/tool/predefined/_wikipedia.py +1 -0
- waldiez/models/tool/predefined/_youtube.py +1 -0
- waldiez/models/tool/tool.py +8 -5
- waldiez/models/tool/tool_data.py +2 -2
- waldiez/models/waldiez.py +152 -4
- waldiez/runner.py +11 -5
- waldiez/running/async_utils.py +192 -0
- waldiez/running/base_runner.py +117 -264
- waldiez/running/dir_utils.py +52 -0
- waldiez/running/environment.py +10 -44
- waldiez/running/events_mixin.py +252 -0
- waldiez/running/exceptions.py +20 -0
- waldiez/running/gen_seq_diagram.py +18 -15
- waldiez/running/io_utils.py +216 -0
- waldiez/running/protocol.py +11 -5
- waldiez/running/requirements_mixin.py +65 -0
- waldiez/running/results_mixin.py +926 -0
- waldiez/running/standard_runner.py +22 -25
- waldiez/running/step_by_step/breakpoints_mixin.py +192 -60
- waldiez/running/step_by_step/command_handler.py +3 -0
- waldiez/running/step_by_step/events_processor.py +194 -14
- waldiez/running/step_by_step/step_by_step_models.py +110 -43
- waldiez/running/step_by_step/step_by_step_runner.py +107 -57
- waldiez/running/subprocess_runner/__base__.py +9 -1
- waldiez/running/subprocess_runner/_async_runner.py +5 -3
- waldiez/running/subprocess_runner/_sync_runner.py +6 -2
- waldiez/running/subprocess_runner/runner.py +39 -23
- waldiez/running/timeline_processor.py +1 -1
- waldiez/utils/__init__.py +2 -0
- waldiez/utils/conflict_checker.py +4 -4
- waldiez/utils/python_manager.py +415 -0
- waldiez/ws/_file_handler.py +18 -18
- waldiez/ws/_mock.py +2 -1
- waldiez/ws/cli.py +36 -12
- waldiez/ws/client_manager.py +35 -27
- waldiez/ws/errors.py +3 -0
- waldiez/ws/models.py +43 -52
- waldiez/ws/reloader.py +12 -4
- waldiez/ws/server.py +85 -55
- waldiez/ws/session_manager.py +8 -9
- waldiez/ws/session_stats.py +1 -1
- waldiez/ws/utils.py +4 -1
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/METADATA +82 -93
- waldiez-0.6.1.dist-info/RECORD +254 -0
- waldiez/running/post_run.py +0 -186
- waldiez/running/pre_run.py +0 -281
- waldiez/running/run_results.py +0 -14
- waldiez/running/utils.py +0 -625
- waldiez-0.6.0.dist-info/RECORD +0 -251
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -18,10 +18,12 @@ class ToolProcessingResult:
|
|
|
18
18
|
"""Result from processing tools."""
|
|
19
19
|
|
|
20
20
|
content: str = ""
|
|
21
|
-
builtin_imports: list[str] = field(default_factory=list
|
|
22
|
-
third_party_imports: list[str] = field(
|
|
21
|
+
builtin_imports: list[str] = field(default_factory=list)
|
|
22
|
+
third_party_imports: list[str] = field(
|
|
23
|
+
default_factory=list,
|
|
24
|
+
)
|
|
23
25
|
environment_variables: list[EnvironmentVariable] = field(
|
|
24
|
-
default_factory=list
|
|
26
|
+
default_factory=list,
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
|
|
@@ -198,10 +200,11 @@ ag2_{tool_name} = ag2_{tool_name}_interop.convert_tool(
|
|
|
198
200
|
for key, value in tool.secrets.items():
|
|
199
201
|
# f.write(f'os.environ["{key}"] = "{value}"\n')
|
|
200
202
|
# check first if the key already exists in os.environ
|
|
201
|
-
|
|
203
|
+
to_write = (
|
|
202
204
|
f'os.environ["{key}"] = '
|
|
203
205
|
f'os.environ.get("{key}", "{value}")\n'
|
|
204
206
|
)
|
|
207
|
+
f.write(to_write)
|
|
205
208
|
except Exception as exc: # pragma: no cover
|
|
206
209
|
raise ExporterContentError(
|
|
207
210
|
f"Failed to write secrets file for tool '{tool_name}': {exc}"
|
waldiez/io/__init__.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
|
|
4
4
|
# flake8: noqa: F401
|
|
5
|
-
# pylint: disable=unused-import,too-few-public-methods
|
|
5
|
+
# pylint: disable=unused-import,unused-argument,too-few-public-methods
|
|
6
|
+
# pyright: reportAssignmentType=false
|
|
6
7
|
|
|
7
8
|
"""IOSTream extensions.
|
|
8
9
|
|
|
@@ -49,10 +50,11 @@ except ImportError: # pragma: no cover
|
|
|
49
50
|
kwargs : dict
|
|
50
51
|
Keyword arguments.
|
|
51
52
|
"""
|
|
52
|
-
|
|
53
|
+
msg = (
|
|
53
54
|
"RedisIOStream is not available. "
|
|
54
55
|
"Please install the required package."
|
|
55
56
|
)
|
|
57
|
+
raise ImportError(msg)
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
try:
|
|
@@ -74,10 +76,11 @@ except ImportError: # pragma: no cover
|
|
|
74
76
|
kwargs : dict
|
|
75
77
|
Keyword arguments.
|
|
76
78
|
"""
|
|
77
|
-
|
|
79
|
+
msg = (
|
|
78
80
|
"AsyncWebsocketsIOStream is not available. "
|
|
79
81
|
"Please install the required package."
|
|
80
82
|
)
|
|
83
|
+
raise ImportError(msg)
|
|
81
84
|
|
|
82
85
|
|
|
83
86
|
try:
|
|
@@ -97,10 +100,11 @@ except ImportError: # pragma: no cover
|
|
|
97
100
|
kwargs : dict
|
|
98
101
|
Keyword arguments.
|
|
99
102
|
"""
|
|
100
|
-
|
|
103
|
+
msg = (
|
|
101
104
|
"MqttIOStream is not available. "
|
|
102
105
|
"Please install the required package."
|
|
103
106
|
)
|
|
107
|
+
raise ImportError(msg)
|
|
104
108
|
|
|
105
109
|
|
|
106
110
|
__all__ = [
|
waldiez/io/_ws.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
# flake8: noqa: E501
|
|
4
|
-
# pylint: disable=line-too-long
|
|
4
|
+
# pylint: disable=line-too-long,invalid-name
|
|
5
5
|
# pyright: reportUnknownMemberType=false,reportUnknownParameterType=false
|
|
6
6
|
# pyright: reportUnknownVariableType=false,reportUnknownArgumentType=false
|
|
7
|
+
# pyright: reportConstantRedefinition=false,reportUnnecessaryIsInstance=false
|
|
8
|
+
# pyright: reportMissingImports=false
|
|
9
|
+
|
|
7
10
|
"""WebSocket IOStream implementation for AsyncIO."""
|
|
8
11
|
|
|
9
12
|
import asyncio
|
|
@@ -15,14 +18,14 @@ HAS_WS_LIB = False
|
|
|
15
18
|
try:
|
|
16
19
|
from starlette.websockets import WebSocket # type: ignore[unused-ignore, unused-import, import-not-found, import-untyped] # noqa
|
|
17
20
|
|
|
18
|
-
HAS_WS_LIB = True
|
|
21
|
+
HAS_WS_LIB = True
|
|
19
22
|
except ImportError: # pragma: no cover
|
|
20
23
|
pass
|
|
21
24
|
|
|
22
25
|
try:
|
|
23
26
|
import websockets # type: ignore[unused-ignore, unused-import, import-not-found, import-untyped] # noqa
|
|
24
27
|
|
|
25
|
-
HAS_WS_LIB = True
|
|
28
|
+
HAS_WS_LIB = True
|
|
26
29
|
except ImportError: # pragma: no cover
|
|
27
30
|
pass
|
|
28
31
|
|
|
@@ -42,7 +45,7 @@ class WebSocketConnection(Protocol):
|
|
|
42
45
|
async def receive_message(
|
|
43
46
|
self,
|
|
44
47
|
timeout: float = 120,
|
|
45
|
-
) -> str: # pyright: ignore
|
|
48
|
+
) -> str: # pyright: ignore[reportReturnType]
|
|
46
49
|
"""Receive a message from the WebSocket connection.
|
|
47
50
|
|
|
48
51
|
Parameters
|
|
@@ -103,7 +106,7 @@ class WebSocketsAdapter:
|
|
|
103
106
|
self.websocket.recv(), timeout=timeout
|
|
104
107
|
)
|
|
105
108
|
if isinstance(response, bytes):
|
|
106
|
-
return response.decode("utf-8")
|
|
109
|
+
return response.decode("utf-8", errors="replace")
|
|
107
110
|
# noinspection PyUnreachableCode
|
|
108
111
|
return response if isinstance(response, str) else str(response)
|
|
109
112
|
except asyncio.TimeoutError:
|
|
@@ -192,12 +195,13 @@ def create_websocket_adapter(websocket: Any) -> WebSocketConnection:
|
|
|
192
195
|
if hasattr(websocket, "send") and hasattr(websocket, "recv"):
|
|
193
196
|
return WebSocketsAdapter(websocket)
|
|
194
197
|
|
|
195
|
-
|
|
198
|
+
msg = (
|
|
196
199
|
"Unsupported WebSocket type. "
|
|
197
200
|
"Must be either websockets.ServerConnection "
|
|
198
201
|
"or starlette.websockets.WebSocket. "
|
|
199
202
|
f"Received: {type(websocket)}"
|
|
200
203
|
)
|
|
204
|
+
raise ValueError(msg)
|
|
201
205
|
|
|
202
206
|
|
|
203
207
|
def is_websocket_available() -> bool:
|
waldiez/io/models/constants.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
"""Constants and type mappings for content models."""
|
|
5
5
|
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import TypedDict
|
|
7
7
|
|
|
8
8
|
from typing_extensions import Literal
|
|
9
9
|
|
|
@@ -13,14 +13,14 @@ from .content.image import ImageMediaContent, ImageUrlMediaContent
|
|
|
13
13
|
from .content.text import TextMediaContent
|
|
14
14
|
from .content.video import VideoMediaContent
|
|
15
15
|
|
|
16
|
-
MediaContent =
|
|
17
|
-
TextMediaContent
|
|
18
|
-
ImageMediaContent
|
|
19
|
-
ImageUrlMediaContent
|
|
20
|
-
VideoMediaContent
|
|
21
|
-
AudioMediaContent
|
|
22
|
-
FileMediaContent
|
|
23
|
-
|
|
16
|
+
MediaContent = (
|
|
17
|
+
TextMediaContent
|
|
18
|
+
| ImageMediaContent
|
|
19
|
+
| ImageUrlMediaContent
|
|
20
|
+
| VideoMediaContent
|
|
21
|
+
| AudioMediaContent
|
|
22
|
+
| FileMediaContent
|
|
23
|
+
)
|
|
24
24
|
|
|
25
25
|
ContentTypeKey = Literal["text", "image", "image_url", "video", "audio", "file"]
|
|
26
26
|
"""Possible content types for the mapping."""
|
|
@@ -40,7 +40,7 @@ class ContentMappingEntry(TypedDict):
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
fields: list[str]
|
|
43
|
-
cls:
|
|
43
|
+
cls: type[MediaContent]
|
|
44
44
|
required_field: str
|
|
45
45
|
|
|
46
46
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
|
|
3
5
|
"""Base content models for media types."""
|
|
4
6
|
|
|
5
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
9
|
from pydantic import BaseModel
|
|
8
10
|
|
|
@@ -10,36 +12,36 @@ from pydantic import BaseModel
|
|
|
10
12
|
class ImageContent(BaseModel):
|
|
11
13
|
"""Image content model."""
|
|
12
14
|
|
|
13
|
-
url:
|
|
14
|
-
file:
|
|
15
|
-
alt:
|
|
15
|
+
url: str | None = None
|
|
16
|
+
file: Any | None = None # File type not directly mappable in Python
|
|
17
|
+
alt: str | None = None
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class VideoContent(BaseModel):
|
|
19
21
|
"""Video content model."""
|
|
20
22
|
|
|
21
|
-
url:
|
|
22
|
-
file:
|
|
23
|
-
duration:
|
|
24
|
-
thumbnailUrl:
|
|
25
|
-
mimeType:
|
|
23
|
+
url: str | None = None
|
|
24
|
+
file: Any | None = None
|
|
25
|
+
duration: int | None = None
|
|
26
|
+
thumbnailUrl: str | None = None
|
|
27
|
+
mimeType: str | None = None
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class AudioContent(BaseModel):
|
|
29
31
|
"""Audio content model."""
|
|
30
32
|
|
|
31
|
-
url:
|
|
32
|
-
file:
|
|
33
|
-
duration:
|
|
34
|
-
transcript:
|
|
33
|
+
url: str | None = None
|
|
34
|
+
file: Any | None = None
|
|
35
|
+
duration: int | None = None
|
|
36
|
+
transcript: str | None = None
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class FileContent(BaseModel):
|
|
38
40
|
"""File content model."""
|
|
39
41
|
|
|
40
|
-
url:
|
|
41
|
-
file:
|
|
42
|
+
url: str | None = None
|
|
43
|
+
file: Any | None = None
|
|
42
44
|
name: str
|
|
43
|
-
size:
|
|
44
|
-
type:
|
|
45
|
-
previewUrl:
|
|
45
|
+
size: int | None = None
|
|
46
|
+
type: str | None = None
|
|
47
|
+
previewUrl: str | None = None
|
waldiez/io/models/user_input.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
|
|
4
|
+
# pyright: reportUnnecessaryIsInstance=false,reportUnknownVariableType=false
|
|
5
|
+
# pyright: reportUnknownArgumentType=false,reportArgumentType=false
|
|
6
|
+
# pyright: reportReturnType=false
|
|
7
|
+
|
|
4
8
|
"""User input data models and validation."""
|
|
5
9
|
|
|
6
10
|
import json
|
|
7
11
|
import os
|
|
8
12
|
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
10
14
|
from urllib.parse import urlparse
|
|
11
15
|
|
|
12
16
|
from pydantic import BaseModel, Field, field_validator
|
|
@@ -31,7 +35,7 @@ class UserInputData(BaseModel):
|
|
|
31
35
|
"""User's input data model."""
|
|
32
36
|
|
|
33
37
|
content: Annotated[
|
|
34
|
-
|
|
38
|
+
MediaContent | list[MediaContent],
|
|
35
39
|
Field(
|
|
36
40
|
description="The content of the input data.",
|
|
37
41
|
title="Content",
|
|
@@ -68,7 +72,7 @@ class UserInputData(BaseModel):
|
|
|
68
72
|
if isinstance(self.content, list):
|
|
69
73
|
return " ".join(
|
|
70
74
|
[
|
|
71
|
-
item.to_string(uploads_root, base_name)
|
|
75
|
+
item.to_string(uploads_root, base_name)
|
|
72
76
|
for item in self.content
|
|
73
77
|
]
|
|
74
78
|
)
|
|
@@ -161,10 +165,11 @@ class UserInputData(BaseModel):
|
|
|
161
165
|
**{mapping["required_field"]: converted} # type: ignore
|
|
162
166
|
)
|
|
163
167
|
|
|
164
|
-
|
|
168
|
+
msg = (
|
|
165
169
|
"Missing required field for content type "
|
|
166
170
|
f"'{content_type}' in: {value}"
|
|
167
171
|
)
|
|
172
|
+
raise ValueError(msg)
|
|
168
173
|
|
|
169
174
|
@staticmethod
|
|
170
175
|
def _convert_simple_content(
|
|
@@ -226,7 +231,7 @@ class UserInputData(BaseModel):
|
|
|
226
231
|
|
|
227
232
|
# If it's a dictionary, check if it has a 'type' field
|
|
228
233
|
if isinstance(v, dict):
|
|
229
|
-
return cls.content_from_dict(v)
|
|
234
|
+
return cls.content_from_dict(v)
|
|
230
235
|
|
|
231
236
|
# If it's a list
|
|
232
237
|
if isinstance(v, list):
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# pyright: reportUnnecessaryIsInstance=false,reportUnknownVariableType=false
|
|
5
|
+
# pyright: reportUnknownArgumentType=false,reportArgumentType=false
|
|
6
|
+
|
|
3
7
|
"""User response model and validation."""
|
|
4
8
|
|
|
5
9
|
import json
|
|
6
10
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Callable
|
|
11
|
+
from typing import Any, Callable
|
|
8
12
|
|
|
9
13
|
from pydantic import ValidationError, field_validator
|
|
10
14
|
|
|
@@ -26,7 +30,7 @@ class UserResponse(StructuredBase):
|
|
|
26
30
|
@classmethod
|
|
27
31
|
def validate_data(
|
|
28
32
|
cls, value: Any
|
|
29
|
-
) ->
|
|
33
|
+
) -> str | UserInputData | list[UserInputData] | None:
|
|
30
34
|
"""Validate the data field in UserResponse.
|
|
31
35
|
|
|
32
36
|
Parameters
|
|
@@ -51,16 +55,16 @@ class UserResponse(StructuredBase):
|
|
|
51
55
|
|
|
52
56
|
handlers: dict[
|
|
53
57
|
type,
|
|
54
|
-
Callable[[Any],
|
|
58
|
+
Callable[[Any], str | UserInputData | list[UserInputData]],
|
|
55
59
|
] = {
|
|
56
60
|
str: cls._handle_string,
|
|
57
61
|
dict: cls._handle_dict,
|
|
58
62
|
list: cls._handle_list,
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
value_type = type(value)
|
|
65
|
+
value_type = type(value)
|
|
62
66
|
handler = handlers.get(
|
|
63
|
-
value_type,
|
|
67
|
+
value_type,
|
|
64
68
|
cls._handle_default,
|
|
65
69
|
)
|
|
66
70
|
result = handler(value)
|
|
@@ -71,16 +75,13 @@ class UserResponse(StructuredBase):
|
|
|
71
75
|
"""Check if value is already a valid type."""
|
|
72
76
|
return isinstance(value, UserInputData) or (
|
|
73
77
|
isinstance(value, list)
|
|
74
|
-
and all(
|
|
75
|
-
isinstance(item, UserInputData)
|
|
76
|
-
for item in value # pyright: ignore
|
|
77
|
-
)
|
|
78
|
+
and all(isinstance(item, UserInputData) for item in value)
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
@classmethod
|
|
81
82
|
def _handle_string(
|
|
82
83
|
cls, value: str
|
|
83
|
-
) ->
|
|
84
|
+
) -> str | UserInputData | list[UserInputData]:
|
|
84
85
|
"""Handle string input.
|
|
85
86
|
|
|
86
87
|
Parameters
|
|
@@ -97,9 +98,9 @@ class UserResponse(StructuredBase):
|
|
|
97
98
|
try:
|
|
98
99
|
parsed_value = json.loads(value)
|
|
99
100
|
if isinstance(parsed_value, dict):
|
|
100
|
-
return cls._handle_dict(parsed_value)
|
|
101
|
+
return cls._handle_dict(parsed_value)
|
|
101
102
|
if isinstance(parsed_value, list):
|
|
102
|
-
return cls._handle_list(parsed_value)
|
|
103
|
+
return cls._handle_list(parsed_value)
|
|
103
104
|
return cls._create_text_input(str(parsed_value))
|
|
104
105
|
except json.JSONDecodeError:
|
|
105
106
|
return cls._create_text_input(value)
|
|
@@ -127,18 +128,18 @@ class UserResponse(StructuredBase):
|
|
|
127
128
|
@classmethod
|
|
128
129
|
def _handle_list(
|
|
129
130
|
cls, value: list[Any]
|
|
130
|
-
) ->
|
|
131
|
+
) -> UserInputData | list[UserInputData]:
|
|
131
132
|
result: list[UserInputData] = []
|
|
132
133
|
|
|
133
134
|
for item in value:
|
|
134
135
|
if isinstance(item, UserInputData):
|
|
135
136
|
result.append(item)
|
|
136
137
|
elif isinstance(item, dict):
|
|
137
|
-
result.append(cls._handle_dict(item))
|
|
138
|
+
result.append(cls._handle_dict(item))
|
|
138
139
|
elif isinstance(item, str):
|
|
139
140
|
result.append(cls._create_text_input(item))
|
|
140
141
|
elif isinstance(item, list):
|
|
141
|
-
nested_result = cls._handle_list(item)
|
|
142
|
+
nested_result = cls._handle_list(item)
|
|
142
143
|
if isinstance(nested_result, list):
|
|
143
144
|
result.extend(nested_result)
|
|
144
145
|
else:
|
|
@@ -207,7 +208,7 @@ class UserResponse(StructuredBase):
|
|
|
207
208
|
uploads_root=uploads_root, base_name=base_name
|
|
208
209
|
).strip()
|
|
209
210
|
# we have probably returned sth till here
|
|
210
|
-
if isinstance(self.data, str): #
|
|
211
|
+
if isinstance(self.data, str): # pragma: no cover
|
|
211
212
|
return self.data
|
|
212
213
|
# noinspection PyUnreachableCode
|
|
213
214
|
return ( # pragma: no cover
|
waldiez/io/mqtt.py
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
8
8
|
# pylint: disable=too-many-locals,too-many-instance-attributes
|
|
9
9
|
|
|
10
|
+
# pyright: reportMissingTypeStubs=false,reportUnknownMemberType=false
|
|
11
|
+
# pyright: reportUnusedParameter=false
|
|
12
|
+
|
|
10
13
|
"""An MQTT I/O stream for handling print and input messages."""
|
|
11
14
|
|
|
12
15
|
import json
|
|
@@ -17,12 +20,7 @@ import uuid
|
|
|
17
20
|
from pathlib import Path
|
|
18
21
|
from threading import Event, Lock
|
|
19
22
|
from types import TracebackType
|
|
20
|
-
from typing import
|
|
21
|
-
Any,
|
|
22
|
-
Callable,
|
|
23
|
-
Optional,
|
|
24
|
-
Type,
|
|
25
|
-
)
|
|
23
|
+
from typing import Any, Callable
|
|
26
24
|
|
|
27
25
|
try:
|
|
28
26
|
from paho.mqtt import client as mqtt
|
|
@@ -44,7 +42,7 @@ from .models import (
|
|
|
44
42
|
UserInputRequest,
|
|
45
43
|
UserResponse,
|
|
46
44
|
)
|
|
47
|
-
from .utils import gen_id, now
|
|
45
|
+
from .utils import gen_id, get_message_dump, now
|
|
48
46
|
|
|
49
47
|
LOG = logging.getLogger(__name__)
|
|
50
48
|
|
|
@@ -62,8 +60,8 @@ class MqttIOStream(IOStream):
|
|
|
62
60
|
client: mqtt.Client
|
|
63
61
|
task_id: str
|
|
64
62
|
input_timeout: int
|
|
65
|
-
on_input_request:
|
|
66
|
-
|
|
63
|
+
on_input_request: Callable[[str, str, str], None] | None
|
|
64
|
+
on_input_response: Callable[[str, str], None] | None
|
|
67
65
|
max_retain_messages: int
|
|
68
66
|
output_topic: str
|
|
69
67
|
input_request_topic: str
|
|
@@ -87,8 +85,8 @@ class MqttIOStream(IOStream):
|
|
|
87
85
|
input_timeout: int = 120,
|
|
88
86
|
connect_timeout: int = 10,
|
|
89
87
|
max_retain_messages: int = 1000,
|
|
90
|
-
on_input_request:
|
|
91
|
-
on_input_response:
|
|
88
|
+
on_input_request: Callable[[str, str, str], None] | None = None,
|
|
89
|
+
on_input_response: Callable[[str, str], None] | None = None,
|
|
92
90
|
mqtt_client_kwargs: dict[str, Any] | None = None,
|
|
93
91
|
uploads_root: Path | str | None = None,
|
|
94
92
|
username: str | None = None,
|
|
@@ -173,9 +171,9 @@ class MqttIOStream(IOStream):
|
|
|
173
171
|
# Set up TLS
|
|
174
172
|
if use_tls:
|
|
175
173
|
if ca_cert_path:
|
|
176
|
-
self.client.tls_set(ca_cert_path)
|
|
174
|
+
self.client.tls_set(ca_cert_path)
|
|
177
175
|
else: # pragma: no cover
|
|
178
|
-
self.client.tls_set()
|
|
176
|
+
self.client.tls_set()
|
|
179
177
|
|
|
180
178
|
# Set up callbacks
|
|
181
179
|
self.client.on_connect = self._on_connect
|
|
@@ -320,11 +318,13 @@ class MqttIOStream(IOStream):
|
|
|
320
318
|
LOG.debug(
|
|
321
319
|
"Received message on topic %s: %s",
|
|
322
320
|
msg.topic,
|
|
323
|
-
msg.payload.decode(),
|
|
321
|
+
msg.payload.decode("utf-8", errors="replace"),
|
|
324
322
|
)
|
|
325
323
|
|
|
326
324
|
if msg.topic == self.input_response_topic: # pragma: no branch
|
|
327
|
-
self._handle_input_response(
|
|
325
|
+
self._handle_input_response(
|
|
326
|
+
msg.payload.decode("utf-8", errors="replace")
|
|
327
|
+
)
|
|
328
328
|
|
|
329
329
|
except Exception as e: # pragma: no cover
|
|
330
330
|
LOG.error("Error handling message: %s", e)
|
|
@@ -396,7 +396,7 @@ class MqttIOStream(IOStream):
|
|
|
396
396
|
|
|
397
397
|
def __exit__(
|
|
398
398
|
self,
|
|
399
|
-
exc_type:
|
|
399
|
+
exc_type: type[Exception] | None,
|
|
400
400
|
exc_value: Exception | None,
|
|
401
401
|
traceback: TracebackType | None,
|
|
402
402
|
) -> None:
|
|
@@ -487,23 +487,10 @@ class MqttIOStream(IOStream):
|
|
|
487
487
|
message : BaseEvent | BaseMessage
|
|
488
488
|
The message or event to send.
|
|
489
489
|
"""
|
|
490
|
-
|
|
491
|
-
message_dump = message.model_dump(mode="json")
|
|
492
|
-
except Exception:
|
|
493
|
-
try:
|
|
494
|
-
message_dump = message.model_dump(
|
|
495
|
-
serialize_as_any=True, mode="json", fallback=str
|
|
496
|
-
)
|
|
497
|
-
except Exception as e:
|
|
498
|
-
message_dump = {
|
|
499
|
-
"error": str(e),
|
|
500
|
-
"type": message.__class__.__name__,
|
|
501
|
-
}
|
|
502
|
-
|
|
490
|
+
message_dump = get_message_dump(message)
|
|
503
491
|
message_type = message_dump.get("type", None)
|
|
504
492
|
if not message_type: # pragma: no cover
|
|
505
493
|
message_type = message.__class__.__name__
|
|
506
|
-
|
|
507
494
|
self._print(
|
|
508
495
|
{
|
|
509
496
|
"type": message_type,
|
|
@@ -652,7 +639,7 @@ class MqttIOStream(IOStream):
|
|
|
652
639
|
@staticmethod
|
|
653
640
|
def _create_user_response(
|
|
654
641
|
message_data: dict[str, Any],
|
|
655
|
-
) ->
|
|
642
|
+
) -> UserResponse | None:
|
|
656
643
|
"""Create UserResponse object from validated data."""
|
|
657
644
|
try:
|
|
658
645
|
# Handle nested JSON in 'data' field
|