waldiez 0.1.0__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.

Files changed (94) hide show
  1. waldiez/__init__.py +15 -0
  2. waldiez/__main__.py +6 -0
  3. waldiez/_version.py +3 -0
  4. waldiez/cli.py +162 -0
  5. waldiez/exporter.py +293 -0
  6. waldiez/exporting/__init__.py +14 -0
  7. waldiez/exporting/agents/__init__.py +5 -0
  8. waldiez/exporting/agents/agent.py +229 -0
  9. waldiez/exporting/agents/agent_skills.py +67 -0
  10. waldiez/exporting/agents/code_execution.py +67 -0
  11. waldiez/exporting/agents/group_manager.py +209 -0
  12. waldiez/exporting/agents/llm_config.py +53 -0
  13. waldiez/exporting/agents/rag_user/__init__.py +5 -0
  14. waldiez/exporting/agents/rag_user/chroma_utils.py +134 -0
  15. waldiez/exporting/agents/rag_user/mongo_utils.py +83 -0
  16. waldiez/exporting/agents/rag_user/pgvector_utils.py +93 -0
  17. waldiez/exporting/agents/rag_user/qdrant_utils.py +112 -0
  18. waldiez/exporting/agents/rag_user/rag_user.py +165 -0
  19. waldiez/exporting/agents/rag_user/vector_db.py +119 -0
  20. waldiez/exporting/agents/teachability.py +37 -0
  21. waldiez/exporting/agents/termination_message.py +45 -0
  22. waldiez/exporting/chats/__init__.py +14 -0
  23. waldiez/exporting/chats/chats.py +46 -0
  24. waldiez/exporting/chats/helpers.py +395 -0
  25. waldiez/exporting/chats/nested.py +264 -0
  26. waldiez/exporting/flow/__init__.py +5 -0
  27. waldiez/exporting/flow/def_main.py +37 -0
  28. waldiez/exporting/flow/flow.py +185 -0
  29. waldiez/exporting/models/__init__.py +193 -0
  30. waldiez/exporting/skills/__init__.py +128 -0
  31. waldiez/exporting/utils/__init__.py +34 -0
  32. waldiez/exporting/utils/comments.py +136 -0
  33. waldiez/exporting/utils/importing.py +267 -0
  34. waldiez/exporting/utils/logging_utils.py +203 -0
  35. waldiez/exporting/utils/method_utils.py +35 -0
  36. waldiez/exporting/utils/naming.py +127 -0
  37. waldiez/exporting/utils/object_string.py +81 -0
  38. waldiez/io_stream.py +181 -0
  39. waldiez/models/__init__.py +107 -0
  40. waldiez/models/agents/__init__.py +65 -0
  41. waldiez/models/agents/agent/__init__.py +21 -0
  42. waldiez/models/agents/agent/agent.py +190 -0
  43. waldiez/models/agents/agent/agent_data.py +162 -0
  44. waldiez/models/agents/agent/code_execution.py +71 -0
  45. waldiez/models/agents/agent/linked_skill.py +30 -0
  46. waldiez/models/agents/agent/nested_chat.py +73 -0
  47. waldiez/models/agents/agent/teachability.py +68 -0
  48. waldiez/models/agents/agent/termination_message.py +167 -0
  49. waldiez/models/agents/agents.py +129 -0
  50. waldiez/models/agents/assistant/__init__.py +6 -0
  51. waldiez/models/agents/assistant/assistant.py +41 -0
  52. waldiez/models/agents/assistant/assistant_data.py +29 -0
  53. waldiez/models/agents/group_manager/__init__.py +19 -0
  54. waldiez/models/agents/group_manager/group_manager.py +87 -0
  55. waldiez/models/agents/group_manager/group_manager_data.py +91 -0
  56. waldiez/models/agents/group_manager/speakers.py +211 -0
  57. waldiez/models/agents/rag_user/__init__.py +26 -0
  58. waldiez/models/agents/rag_user/rag_user.py +58 -0
  59. waldiez/models/agents/rag_user/rag_user_data.py +32 -0
  60. waldiez/models/agents/rag_user/retrieve_config.py +592 -0
  61. waldiez/models/agents/rag_user/vector_db_config.py +162 -0
  62. waldiez/models/agents/user_proxy/__init__.py +6 -0
  63. waldiez/models/agents/user_proxy/user_proxy.py +41 -0
  64. waldiez/models/agents/user_proxy/user_proxy_data.py +30 -0
  65. waldiez/models/chat/__init__.py +22 -0
  66. waldiez/models/chat/chat.py +129 -0
  67. waldiez/models/chat/chat_data.py +326 -0
  68. waldiez/models/chat/chat_message.py +304 -0
  69. waldiez/models/chat/chat_nested.py +160 -0
  70. waldiez/models/chat/chat_summary.py +110 -0
  71. waldiez/models/common/__init__.py +38 -0
  72. waldiez/models/common/base.py +63 -0
  73. waldiez/models/common/method_utils.py +165 -0
  74. waldiez/models/flow/__init__.py +9 -0
  75. waldiez/models/flow/flow.py +302 -0
  76. waldiez/models/flow/flow_data.py +87 -0
  77. waldiez/models/model/__init__.py +11 -0
  78. waldiez/models/model/model.py +169 -0
  79. waldiez/models/model/model_data.py +86 -0
  80. waldiez/models/skill/__init__.py +9 -0
  81. waldiez/models/skill/skill.py +129 -0
  82. waldiez/models/skill/skill_data.py +37 -0
  83. waldiez/models/waldiez.py +301 -0
  84. waldiez/py.typed +0 -0
  85. waldiez/runner.py +304 -0
  86. waldiez/stream/__init__.py +7 -0
  87. waldiez/stream/consumer.py +139 -0
  88. waldiez/stream/provider.py +339 -0
  89. waldiez/stream/server.py +412 -0
  90. waldiez-0.1.0.dist-info/METADATA +181 -0
  91. waldiez-0.1.0.dist-info/RECORD +94 -0
  92. waldiez-0.1.0.dist-info/WHEEL +4 -0
  93. waldiez-0.1.0.dist-info/entry_points.txt +2 -0
  94. waldiez-0.1.0.dist-info/licenses/LICENSE +21 -0
waldiez/runner.py ADDED
@@ -0,0 +1,304 @@
1
+ """Waldiez Flow runner.
2
+
3
+ Run a waldiez flow.
4
+ The flow is first converted to an autogen flow with agents, chats and skills.
5
+ We then chown to temporary directory, call the flow's `main()` and
6
+ return the results. Before running the flow, any additional environment
7
+ variables specified in the waldiez file are set.
8
+ """
9
+
10
+ import datetime
11
+ import importlib.util
12
+ import io
13
+ import os
14
+ import shutil
15
+ import subprocess # nosemgrep # nosec
16
+ import sys
17
+ import tempfile
18
+ from contextlib import contextmanager
19
+ from contextvars import ContextVar
20
+ from pathlib import Path
21
+ from types import TracebackType
22
+ from typing import Callable, Dict, Iterator, List, Optional, Type, Union
23
+
24
+ from autogen import ChatResult # type: ignore
25
+
26
+ from .exporter import WaldiezExporter
27
+ from .io_stream import WaldiezIOStream
28
+ from .models.waldiez import Waldiez
29
+
30
+
31
+ @contextmanager
32
+ def _chdir(to: Union[str, Path]) -> Iterator[None]:
33
+ """Change the current working directory in a context.
34
+
35
+ Parameters
36
+ ----------
37
+ to : Union[str, Path]
38
+ The directory to change to.
39
+
40
+ Yields
41
+ ------
42
+ Iterator[None]
43
+ The context manager.
44
+ """
45
+ old_cwd = str(os.getcwd())
46
+ os.chdir(to)
47
+ try:
48
+ yield
49
+ finally:
50
+ os.chdir(old_cwd)
51
+
52
+
53
+ class WaldiezRunner:
54
+ """Waldiez runner class."""
55
+
56
+ def __init__(
57
+ self, waldiez: Waldiez, file_path: Optional[Union[str, Path]] = None
58
+ ) -> None:
59
+ """Initialize the Waldiez manager."""
60
+ self._waldiez = waldiez
61
+ self._running = False
62
+ self._file_path = file_path
63
+ self._stream: ContextVar[Optional[WaldiezIOStream]] = ContextVar(
64
+ "waldiez_stream", default=None
65
+ )
66
+ self._exporter = WaldiezExporter(waldiez)
67
+
68
+ @classmethod
69
+ def load(
70
+ cls,
71
+ waldiez_file: Union[str, Path],
72
+ name: Optional[str] = None,
73
+ description: Optional[str] = None,
74
+ tags: Optional[List[str]] = None,
75
+ requirements: Optional[List[str]] = None,
76
+ ) -> "WaldiezRunner":
77
+ """Create a WaldiezRunner instance from a file.
78
+
79
+ Parameters
80
+ ----------
81
+ waldiez_file : Union[str, Path]
82
+ The file path.
83
+ name : Optional[str], optional
84
+ The name of the Waldiez, by default None.
85
+ description : Optional[str], optional
86
+ The description of the Waldiez, by default None.
87
+ tags : Optional[List[str]], optional
88
+ The tags of the Waldiez, by default None.
89
+ requirements : Optional[List[str]], optional
90
+ The requirements of the Waldiez, by default None.
91
+
92
+ Returns
93
+ -------
94
+ WaldiezRunner
95
+ The Waldiez runner instance.
96
+
97
+ Raises
98
+ ------
99
+ FileNotFoundError
100
+ If the file is not found.
101
+ RuntimeError
102
+ If the file is not a valid Waldiez file.
103
+ """
104
+ waldiez = Waldiez.load(
105
+ waldiez_file,
106
+ name=name,
107
+ description=description,
108
+ tags=tags,
109
+ requirements=requirements,
110
+ )
111
+ return cls(waldiez, file_path=waldiez_file)
112
+
113
+ def __enter__(
114
+ self,
115
+ ) -> "WaldiezRunner":
116
+ """Enter the context manager."""
117
+ return self
118
+
119
+ def __exit__(
120
+ self,
121
+ exc_type: Type[BaseException],
122
+ exc_value: BaseException,
123
+ traceback: TracebackType,
124
+ ) -> None:
125
+ """Exit the context manager."""
126
+ if self._running:
127
+ self._running = False
128
+ token = self._stream.get()
129
+ if token is not None:
130
+ self._stream.reset(token)
131
+ del token
132
+
133
+ @property
134
+ def waldiez(self) -> Waldiez:
135
+ """Get the Waldiez instance."""
136
+ return self._waldiez
137
+
138
+ @property
139
+ def running(self) -> bool:
140
+ """Get the running status."""
141
+ return self._running
142
+
143
+ def _get_print_function(self) -> Callable[..., None]:
144
+ """Get the print function."""
145
+ token = self._stream.get()
146
+ if token is not None:
147
+ return token.print
148
+ return print
149
+
150
+ def _install_requirements(self) -> None:
151
+ """Install the requirements for the flow."""
152
+ extra_requirements = set(
153
+ req for req in self.waldiez.requirements if req not in sys.modules
154
+ )
155
+ if extra_requirements:
156
+ print_function = self._get_print_function()
157
+ # pylint: disable=inconsistent-quotes
158
+ print_function(
159
+ f"Installing requirements: {', '.join(extra_requirements)}"
160
+ )
161
+ with subprocess.Popen(
162
+ [sys.executable, "-m", "pip", "install", *extra_requirements],
163
+ stdout=subprocess.PIPE,
164
+ stderr=subprocess.PIPE,
165
+ ) as proc:
166
+ if proc.stdout:
167
+ for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
168
+ print_function(line.strip())
169
+ if proc.stderr:
170
+ for line in io.TextIOWrapper(proc.stderr, encoding="utf-8"):
171
+ print_function(line.strip())
172
+ print_function(
173
+ "Requirements installed.\n"
174
+ "NOTE: If new packages were added and you are using Jupyter, "
175
+ "you might need to restart the kernel."
176
+ )
177
+
178
+ def _after_run(
179
+ self, temp_dir: Path, output_path: Optional[Union[str, Path]]
180
+ ) -> None:
181
+ if output_path:
182
+ destination_dir = Path(output_path).parent
183
+ destination_dir = (
184
+ destination_dir
185
+ / "waldiez_out"
186
+ / datetime.datetime.now().strftime("%Y%m%d%H%M%S")
187
+ )
188
+ destination_dir.mkdir(parents=True, exist_ok=True)
189
+ # copy the contents of the temp dir to the destination dir
190
+ self._get_print_function()(
191
+ f"Copying the results to {destination_dir}"
192
+ )
193
+ for item in temp_dir.iterdir():
194
+ # skip cache files
195
+ if (
196
+ item.name.startswith("__pycache__")
197
+ or item.name.endswith(".pyc")
198
+ or item == ".cache"
199
+ ):
200
+ continue
201
+ if item.is_file():
202
+ shutil.copy(item, destination_dir)
203
+ else:
204
+ shutil.copytree(item, destination_dir / item.name)
205
+ shutil.rmtree(temp_dir)
206
+
207
+ def _set_env_vars(self) -> Dict[str, str]:
208
+ """Set environment variables and return the old ones (if any)."""
209
+ old_vars: Dict[str, str] = {}
210
+ for var_key, var_value in self.waldiez.get_flow_env_vars():
211
+ if var_key:
212
+ current = os.environ.get(var_key, "")
213
+ old_vars[var_key] = current
214
+ os.environ[var_key] = var_value
215
+ return old_vars
216
+
217
+ @staticmethod
218
+ def _reset_env_vars(old_vars: Dict[str, str]) -> None:
219
+ """Reset the environment variables."""
220
+ for var_key, var_value in old_vars.items():
221
+ if not var_value:
222
+ os.environ.pop(var_key, "")
223
+ else:
224
+ os.environ[var_key] = var_value
225
+
226
+ def _do_run(
227
+ self, output_path: Optional[Union[str, Path]]
228
+ ) -> Union[ChatResult, List[ChatResult]]:
229
+ """Run the Waldiez workflow."""
230
+ results: Union[ChatResult, List[ChatResult]] = []
231
+ temp_dir = Path(tempfile.mkdtemp())
232
+ file_name = "flow.py" if not output_path else Path(output_path).name
233
+ if file_name.endswith((".json", ".waldiez")):
234
+ file_name = file_name.replace(".json", ".py").replace(
235
+ ".waldiez", ".py"
236
+ )
237
+ if not file_name.endswith(".py"):
238
+ file_name += ".py"
239
+ module_name = file_name.replace(".py", "")
240
+ with _chdir(to=temp_dir):
241
+ self._exporter.export(Path(file_name))
242
+ spec = importlib.util.spec_from_file_location(
243
+ module_name, temp_dir / file_name
244
+ )
245
+ if not spec or not spec.loader:
246
+ raise ImportError("Could not import the flow")
247
+ sys.path.insert(0, str(temp_dir))
248
+ old_vars = self._set_env_vars()
249
+ module = importlib.util.module_from_spec(spec)
250
+ spec.loader.exec_module(module)
251
+ print_function = self._get_print_function()
252
+ print_function("Starting workflow...")
253
+ results = module.main()
254
+ sys.path.pop(0)
255
+ self._reset_env_vars(old_vars)
256
+ self._after_run(temp_dir, output_path)
257
+ return results
258
+
259
+ def _run(
260
+ self,
261
+ output_path: Optional[Union[str, Path]],
262
+ ) -> Union[ChatResult, List[ChatResult]]:
263
+ self._install_requirements()
264
+ token = self._stream.get()
265
+ if token is not None:
266
+ with WaldiezIOStream.set_default(token):
267
+ return self._do_run(output_path)
268
+ return self._do_run(output_path)
269
+
270
+ def run(
271
+ self,
272
+ stream: Optional[WaldiezIOStream] = None,
273
+ output_path: Optional[Union[str, Path]] = None,
274
+ ) -> Union[ChatResult, List[ChatResult]]:
275
+ """Run the Waldiez workflow.
276
+
277
+ Parameters
278
+ ----------
279
+ stream : Optional[WaldiezIOStream], optional
280
+ The stream to use, by default None.
281
+ output_path : Optional[Union[str, Path]], optional
282
+ The output path, by default None.
283
+
284
+ Returns
285
+ -------
286
+ Union[ChatResult, List[ChatResult]]
287
+ The result of the chat(s).
288
+
289
+ Raises
290
+ ------
291
+ RuntimeError
292
+ If the workflow is already running.
293
+ """
294
+ if self._running is True:
295
+ raise RuntimeError("Workflow already running")
296
+ self._running = True
297
+ token = self._stream.set(stream)
298
+ file_path = output_path or self._file_path
299
+ try:
300
+ return self._run(file_path)
301
+ finally:
302
+ self._running = False
303
+ self._stream.reset(token)
304
+ del token
@@ -0,0 +1,7 @@
1
+ """IO stream using twisted and sockets."""
2
+
3
+ from .consumer import TCPConsumer
4
+ from .provider import TCPProvider
5
+ from .server import TCPServer
6
+
7
+ __all__ = ["TCPServer", "TCPProvider", "TCPConsumer"]
@@ -0,0 +1,139 @@
1
+ """TCP socket input consumer.
2
+
3
+ It connects to a TCP server,
4
+ listens for `INPUT:` messages to get the user's input,
5
+ and sends `REQUEST:` messages to prompt the user,
6
+ """
7
+
8
+ import socket
9
+ import time
10
+ from types import TracebackType
11
+ from typing import Optional, Type
12
+
13
+ END_OF_MESSAGE = b"\r\n"
14
+
15
+
16
+ class TCPConsumer:
17
+ """TCP socket input consumer."""
18
+
19
+ def __init__(
20
+ self, host: str, port: int, timeout: Optional[float] = None
21
+ ) -> None:
22
+ """Create a new input consumer.
23
+
24
+ Parameters
25
+ ----------
26
+ host : str
27
+ The host to connect to.
28
+ port : int
29
+ The port to connect to.
30
+ timeout : float, optional
31
+ The timeout for the consumer, by default None (no timeout).
32
+ """
33
+ self.host = host
34
+ self.port = port
35
+ self.timeout = timeout
36
+ self._running = False
37
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
38
+
39
+ def __enter__(self) -> "TCPConsumer":
40
+ """Enter the context."""
41
+ self.start()
42
+ return self
43
+
44
+ def __exit__(
45
+ self,
46
+ exc_type: Optional[Type[BaseException]],
47
+ exc_value: Optional[BaseException],
48
+ traceback: Optional[TracebackType],
49
+ ) -> None:
50
+ """Exit the context."""
51
+ self.stop()
52
+
53
+ def is_running(self) -> bool:
54
+ """Check if the consumer is running.
55
+
56
+ Returns
57
+ -------
58
+ bool
59
+ True is the consumer is running, else False
60
+ """
61
+ return self._running
62
+
63
+ def start(self) -> None:
64
+ """Start the consumer."""
65
+ if self._running:
66
+ return
67
+ self._running = True
68
+ self.socket.connect((self.host, self.port))
69
+ self.socket.sendall("CONSUMER\r\n".encode("utf-8"))
70
+
71
+ def _get_response_no_timeout(self) -> Optional[str]:
72
+ """Get the response."""
73
+ data = self.socket.recv(1024)
74
+ while not data.endswith(END_OF_MESSAGE):
75
+ data += self.socket.recv(1024)
76
+ if data.startswith(b"INPUT:"):
77
+ response = data[len(b"INPUT:") :]
78
+ if response.endswith(END_OF_MESSAGE):
79
+ response = response[: -len(END_OF_MESSAGE)]
80
+ return response.decode("utf-8")
81
+ return None
82
+
83
+ def _get_response_with_timeout(self, timeout: float) -> Optional[str]:
84
+ """Get the response using a timeout."""
85
+ start_time = time.monotonic()
86
+ data = b""
87
+ self.socket.settimeout(timeout)
88
+ while time.monotonic() - start_time < timeout:
89
+ try:
90
+ data += self.socket.recv(1024)
91
+ except TimeoutError:
92
+ return None
93
+ if data.endswith(END_OF_MESSAGE):
94
+ break
95
+ if not data:
96
+ return None
97
+ if data.startswith(b"INPUT:"):
98
+ response = data[len(b"INPUT:") :]
99
+ if response.endswith(END_OF_MESSAGE):
100
+ response = response[: -len(END_OF_MESSAGE)]
101
+ return response.decode("utf-8")
102
+ return None
103
+
104
+ def get_response(self) -> Optional[str]:
105
+ """Get the response.
106
+
107
+ Returns
108
+ -------
109
+ Optional[str]
110
+ The response if available, None otherwise.
111
+ """
112
+ if not self._running:
113
+ self.start()
114
+ if self.timeout is None or self.timeout < 1:
115
+ return self._get_response_no_timeout()
116
+ return self._get_response_with_timeout(self.timeout)
117
+
118
+ def send_prompt(self, prompt: str) -> None:
119
+ """Send a prompt.
120
+
121
+ Parameters
122
+ ----------
123
+ prompt : str
124
+ The prompt to send.
125
+ """
126
+ if not self._running:
127
+ self.start()
128
+ message = f"REQUEST:{prompt}" + "\r\n"
129
+ self.socket.sendall(message.encode("utf-8"))
130
+
131
+ def stop(self) -> None:
132
+ """Close the consumer."""
133
+ try:
134
+ self.socket.close()
135
+ except OSError:
136
+ pass
137
+ del self.socket
138
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
139
+ self._running = False