waldiez 0.1.6__py3-none-any.whl → 0.1.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 CHANGED
@@ -2,14 +2,12 @@
2
2
 
3
3
  from ._version import __version__
4
4
  from .exporter import WaldiezExporter
5
- from .io_stream import WaldiezIOStream
6
5
  from .models import Waldiez
7
6
  from .runner import WaldiezRunner
8
7
 
9
8
  __all__ = [
10
9
  "Waldiez",
11
10
  "WaldiezExporter",
12
- "WaldiezIOStream",
13
11
  "WaldiezRunner",
14
12
  "__version__",
15
13
  ]
waldiez/_version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for Waldiez."""
2
2
 
3
- __version__ = "0.1.6"
3
+ __version__ = "0.1.8"
waldiez/cli.py CHANGED
@@ -6,14 +6,15 @@ import logging
6
6
  import os
7
7
  import sys
8
8
  from pathlib import Path
9
- from typing import Any, Dict, Optional
10
-
11
- from autogen import ChatResult # type: ignore[import-untyped]
9
+ from typing import TYPE_CHECKING, Any, Dict, Optional
12
10
 
13
11
  from . import Waldiez, __version__
14
12
  from .exporter import WaldiezExporter
15
13
  from .runner import WaldiezRunner
16
14
 
15
+ if TYPE_CHECKING:
16
+ from autogen import ChatResult # type: ignore[import-untyped]
17
+
17
18
 
18
19
  def get_parser() -> argparse.ArgumentParser:
19
20
  """Get the argument parser for the Waldiez package.
@@ -28,7 +29,7 @@ def get_parser() -> argparse.ArgumentParser:
28
29
  prog="waldiez",
29
30
  )
30
31
  parser.add_argument(
31
- "waldiez",
32
+ "file",
32
33
  type=str,
33
34
  help="Path to the Waldiez flow (*.waldiez) file.",
34
35
  )
@@ -66,7 +67,7 @@ def get_parser() -> argparse.ArgumentParser:
66
67
  return parser
67
68
 
68
69
 
69
- def _log_result(result: ChatResult) -> None:
70
+ def _log_result(result: "ChatResult") -> None:
70
71
  """Log the result of the Waldiez flow."""
71
72
  logger = logging.getLogger("waldiez::cli")
72
73
  logger.info("Chat History:\n")
@@ -94,9 +95,12 @@ def _run(data: Dict[str, Any], output_path: Optional[str]) -> None:
94
95
  def main() -> None:
95
96
  """Parse the command line arguments and run the Waldiez flow."""
96
97
  parser = get_parser()
98
+ if len(sys.argv) == 1:
99
+ parser.print_help()
100
+ sys.exit(0)
97
101
  args = parser.parse_args()
98
102
  logger = _get_logger()
99
- waldiez_file: str = args.waldiez
103
+ waldiez_file: str = args.file
100
104
  if not os.path.exists(waldiez_file):
101
105
  logger.error("File not found: %s", waldiez_file)
102
106
  sys.exit(1)
@@ -109,8 +109,7 @@ def get_chroma_db_args(
109
109
  f" embedding_function={embedding_function_arg},\n"
110
110
  )
111
111
  # The RAG example:
112
- # https://microsoft.github.io/autogen/docs/\
113
- # notebooks/agentchat_groupchat_RAG
112
+ # https://ag2ai.github.io/ag2/docs/notebooks/agentchat_groupchat_RAG/
114
113
  # raises `InvalidCollectionException`: Collection groupchat does not exist.
115
114
  # https://github.com/chroma-core/chroma/issues/861
116
115
  # https://github.com/microsoft/autogen/issues/3551#issuecomment-2366930994
@@ -14,7 +14,6 @@ from .helpers import escape_summary_args_quotes
14
14
 
15
15
 
16
16
  def get_nested_chat_trigger_agent_names(
17
- all_chats: List[WaldiezChat],
18
17
  nested_chat: WaldiezAgentNestedChat,
19
18
  agent_names: Dict[str, str],
20
19
  ) -> str:
@@ -22,8 +21,6 @@ def get_nested_chat_trigger_agent_names(
22
21
 
23
22
  Parameters
24
23
  ----------
25
- all_chats : List[WaldiezChat]
26
- All the chats in the flow.
27
24
  nested_chat : WaldiezAgentNestedChat
28
25
  The nested chat.
29
26
  agent_names : Dict[str, str]
@@ -34,14 +31,7 @@ def get_nested_chat_trigger_agent_names(
34
31
  str
35
32
  The trigger agent names.
36
33
  """
37
- trigger_agent_ids: List[str] = []
38
- for message in nested_chat.triggered_by:
39
- waldiez_chat = next(chat for chat in all_chats if chat.id == message.id)
40
- if message.is_reply:
41
- trigger_agent_ids.append(waldiez_chat.target)
42
- else:
43
- trigger_agent_ids.append(waldiez_chat.source)
44
- agents = [agent_names[agent_id] for agent_id in trigger_agent_ids]
34
+ agents = [agent_names[agent_id] for agent_id in nested_chat.triggered_by]
45
35
  trigger_string = f'{[", ".join(agents)]}'
46
36
  return trigger_string.replace("'", '"')
47
37
 
@@ -228,7 +218,7 @@ def export_nested_chat(
228
218
  use_suffix = len(agent.data.nested_chats) > 1
229
219
  for index, entry in enumerate(agent.data.nested_chats):
230
220
  trigger_names = get_nested_chat_trigger_agent_names(
231
- all_chats=all_chats, nested_chat=entry, agent_names=agent_names
221
+ nested_chat=entry, agent_names=agent_names
232
222
  )
233
223
  chat_queue, extra_methods = get_nested_chat_queue(
234
224
  nested_chat=entry,
@@ -14,6 +14,7 @@ get_sqlite_to_csv_call_string
14
14
 
15
15
 
16
16
  # Check issue:
17
+ # Also check if in ag2 this still applies
17
18
  # https://github.com/microsoft/autogen/issues/2286
18
19
  # we cannot log new agents if they have code_execution enabled
19
20
  # we get `Path` is not JSON serializable (on code_executor)
waldiez/io/__init__.py ADDED
@@ -0,0 +1,142 @@
1
+ """Custom IOStream class to use with autogen.
2
+
3
+ It is meant to be used when we want to use custom
4
+ `print` and `input`. For example, when a websocket
5
+ is used to trigger a UI element that requires user input.
6
+ and sends back the user's input to the websocket. In the same
7
+ way, we can use it to forward what is meant to be printed.
8
+ """
9
+
10
+ import threading
11
+ from typing import Any, Callable, Optional
12
+
13
+ from autogen.io import IOStream # type: ignore[import-untyped]
14
+
15
+
16
+ class WaldiezIOStream(IOStream):
17
+ """Custom IOStream class to handle `print` and `input` functions."""
18
+
19
+ def __init__(
20
+ self,
21
+ input_timeout: float = 60.0,
22
+ print_function: Optional[Callable[..., None]] = None,
23
+ on_prompt_input: Optional[Callable[[str], None]] = None,
24
+ ) -> None:
25
+ """
26
+ Initialize the IOStream.
27
+
28
+ Parameters
29
+ ----------
30
+ input_timeout : float, optional
31
+ The input timeout in seconds, by default 60.0.
32
+ print_function : Optional[Callable[..., None]], optional
33
+ The function to handle print operations, by default None.
34
+ on_prompt_input : Optional[Callable[[str], None]], optional
35
+ The function to call for processing input prompts, by default None.
36
+
37
+ Notes
38
+ -----
39
+ - on_prompt_input: It does not return a string (like 'input' does).
40
+ Instead, it is meant to be used to forward the prompt somewhere else
41
+ (e.g., a websocket). When we get the input, we can call
42
+ `waldiez_io_stream.set_input(input_data)` with the input data.
43
+ """
44
+ self.input_timeout = input_timeout # Timeout for input
45
+ self.print_function = print_function # Custom print handler
46
+ self._on_prompt_input = on_prompt_input # Custom input prompt handler
47
+ self.current_input: Optional[str] = None # Store the current input
48
+ self._input_event = threading.Event() # Event to signal input readiness
49
+ self.allow_input = True # Flag to allow or block input setting
50
+
51
+ def print(
52
+ self,
53
+ *objects: Any,
54
+ sep: str = " ",
55
+ end: str = "\n",
56
+ flush: bool = False,
57
+ ) -> None:
58
+ """
59
+ Mock the `print` function.
60
+
61
+ Parameters
62
+ ----------
63
+ objects : Any
64
+ The objects to print.
65
+ sep : str, optional
66
+ The separator, by default " ".
67
+ end : str, optional
68
+ The ending string, by default a new line.
69
+ flush : bool, optional
70
+ Whether to flush the output, by default False.
71
+ """
72
+ print_function: Callable[..., None] = self.print_function or print
73
+ print_function(*objects, sep=sep, end=end, flush=flush)
74
+
75
+ def input(self, prompt: str = "", *, password: bool = False) -> str:
76
+ """
77
+ Mock the `input` function with optional timeout handling.
78
+
79
+ Parameters
80
+ ----------
81
+ prompt : str, optional
82
+ The prompt to show, by default "".
83
+ password : bool, optional
84
+ Whether to hide the input as password (not used), by default False.
85
+
86
+ Returns
87
+ -------
88
+ str
89
+ The user's input or '\n' if timeout occurs.
90
+ """
91
+ _prompt = prompt or "Your input:"
92
+ if _prompt in (">", "> "): # pragma: no cover
93
+ _prompt = "Your input:"
94
+ if prompt:
95
+ if self._on_prompt_input:
96
+ self._on_prompt_input(_prompt)
97
+ self.print(_prompt, end="")
98
+
99
+ # Only reset if no input is currently set
100
+ # e.g. handle the case when we call set_input(..) before input(..)
101
+ already_set = self._input_event.is_set()
102
+ if not self._input_event.is_set():
103
+ self.current_input = None # Reset previous input
104
+ self._input_event.clear() # Clear the event before waiting
105
+
106
+ # Wait for input or timeout
107
+ if not self._input_event.wait(self.input_timeout):
108
+ # Timeout occurred, return what we have so far
109
+ to_return = (
110
+ self.current_input if self.current_input is not None else "\n"
111
+ )
112
+ self.current_input = None
113
+ self._input_event.clear()
114
+ # if we had already set the input, return it
115
+ return to_return if already_set else "\n"
116
+
117
+ # Input is ready, return it
118
+ to_return = (
119
+ self.current_input if self.current_input is not None else "\n"
120
+ )
121
+ self.current_input = None
122
+ self._input_event.clear() # Clear the event after waiting
123
+ return to_return
124
+
125
+ def set_input(self, value: str) -> None:
126
+ """
127
+ Set the input value and signal that input is ready.
128
+
129
+ Parameters
130
+ ----------
131
+ value : str
132
+ The value to set as input.
133
+ """
134
+ if self.allow_input: # Respect the allow_input flag
135
+ self.current_input = value
136
+ self._input_event.set() # Signal that input is ready
137
+
138
+ def reset_state(self) -> None:
139
+ """Reset the IOStream state for testing."""
140
+ self.current_input = None
141
+ self._input_event.clear()
142
+ self.allow_input = True # Re-enable input setting
@@ -40,22 +40,18 @@ class WaldiezAgentNestedChat(WaldiezBase):
40
40
 
41
41
  Attributes
42
42
  ----------
43
- triggered_by : List[WaldiezAgentNestedChatMessage]
44
- A list of chats (id and is_reply) to determine
45
- the triggering of the nested chat.
43
+ triggered_by : List[str]
44
+ A list of agent ids that trigger the nested chat.
46
45
  messages : List[WaldiezAgentNestedChatMessage]
47
46
  The list of messages (chat ids and 'is_reply'z)
48
47
  to include the in the nested chat registration.
49
48
  """
50
49
 
51
50
  triggered_by: Annotated[
52
- List[WaldiezAgentNestedChatMessage],
51
+ List[str],
53
52
  Field(
54
53
  title="Triggered By",
55
- description=(
56
- "A list of chats (id and is_reply) to determine"
57
- "the triggering of the nested chat."
58
- ),
54
+ description=("A list of agent ids that trigger the nested chat."),
59
55
  alias="triggeredBy",
60
56
  default_factory=list,
61
57
  ),
waldiez/models/waldiez.py CHANGED
@@ -7,12 +7,10 @@ definitions and their optional additional skills to be used.
7
7
 
8
8
  import json
9
9
  from dataclasses import dataclass
10
+ from functools import cache
10
11
  from pathlib import Path
11
12
  from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
12
13
 
13
- # let's be strict with autogen version
14
- from autogen.version import __version__ as autogen_version # type: ignore
15
-
16
14
  from .agents import WaldiezAgent
17
15
  from .chat import WaldiezChat
18
16
  from .flow import WaldiezFlow
@@ -203,21 +201,20 @@ class Waldiez:
203
201
  @property
204
202
  def requirements(self) -> List[str]:
205
203
  """Get the flow requirements."""
204
+ autogen_version = _get_autogen_version()
206
205
  requirements_list = filter(
207
206
  lambda requirement: not (
208
207
  requirement.startswith("pyautogen")
209
- or requirement.startswith("autogen-agentchat")
208
+ or requirement.startswith("ag2")
210
209
  ),
211
210
  self.flow.requirements,
212
211
  )
213
212
  requirements = set(requirements_list)
214
213
  if self.has_rag_agents:
215
- requirements.add(
216
- f"autogen-agentchat[retrievechat]=={autogen_version}"
217
- )
214
+ requirements.add(f"ag2[retrievechat]=={autogen_version}")
218
215
  else:
219
- requirements.add(f"autogen-agentchat=={autogen_version}")
220
- # ref: https://github.com/microsoft/autogen/blob/main/setup.py
216
+ requirements.add(f"ag2=={autogen_version}")
217
+ # ref: https://github.com/ag2ai/ag2/blob/main/setup.py
221
218
  models_with_additional_requirements = [
222
219
  "together",
223
220
  "gemini",
@@ -230,8 +227,7 @@ class Waldiez:
230
227
  for model in self.models:
231
228
  if model.data.api_type in models_with_additional_requirements:
232
229
  requirements.add(
233
- f"autogen-agentchat[{model.data.api_type}]=="
234
- f"{autogen_version}"
230
+ f"ag2[{model.data.api_type}]==" f"{autogen_version}"
235
231
  )
236
232
  return list(requirements)
237
233
 
@@ -299,3 +295,14 @@ def _get_flow(
299
295
  if "requirements" not in data:
300
296
  data["requirements"] = []
301
297
  return data
298
+
299
+
300
+ @cache
301
+ def _get_autogen_version() -> str:
302
+ """Get the autogen version."""
303
+ # pylint: disable=import-outside-toplevel
304
+ try:
305
+ from autogen.version import __version__ as atg_version # type: ignore
306
+ except ImportError: # pragma: no cover
307
+ atg_version = "0.0.0"
308
+ return atg_version
waldiez/runner.py CHANGED
@@ -19,14 +19,25 @@ from contextlib import contextmanager
19
19
  from contextvars import ContextVar
20
20
  from pathlib import Path
21
21
  from types import TracebackType
22
- from typing import Callable, Dict, Iterator, List, Optional, Type, Union
23
-
24
- from autogen import ChatResult # type: ignore
22
+ from typing import (
23
+ TYPE_CHECKING,
24
+ Callable,
25
+ Dict,
26
+ Iterator,
27
+ List,
28
+ Optional,
29
+ Type,
30
+ Union,
31
+ )
25
32
 
26
33
  from .exporter import WaldiezExporter
27
- from .io_stream import WaldiezIOStream
28
34
  from .models.waldiez import Waldiez
29
35
 
36
+ if TYPE_CHECKING:
37
+ from autogen import ChatResult # type: ignore
38
+
39
+ from .io import WaldiezIOStream
40
+
30
41
 
31
42
  @contextmanager
32
43
  def _chdir(to: Union[str, Path]) -> Iterator[None]:
@@ -60,7 +71,7 @@ class WaldiezRunner:
60
71
  self._waldiez = waldiez
61
72
  self._running = False
62
73
  self._file_path = file_path
63
- self._stream: ContextVar[Optional[WaldiezIOStream]] = ContextVar(
74
+ self._stream: ContextVar[Optional["WaldiezIOStream"]] = ContextVar(
64
75
  "waldiez_stream", default=None
65
76
  )
66
77
  self._exporter = WaldiezExporter(waldiez)
@@ -225,9 +236,9 @@ class WaldiezRunner:
225
236
 
226
237
  def _do_run(
227
238
  self, output_path: Optional[Union[str, Path]]
228
- ) -> Union[ChatResult, List[ChatResult]]:
239
+ ) -> Union["ChatResult", List["ChatResult"]]:
229
240
  """Run the Waldiez workflow."""
230
- results: Union[ChatResult, List[ChatResult]] = []
241
+ results: Union["ChatResult", List["ChatResult"]] = []
231
242
  temp_dir = Path(tempfile.mkdtemp())
232
243
  file_name = "flow.py" if not output_path else Path(output_path).name
233
244
  if file_name.endswith((".json", ".waldiez")):
@@ -259,19 +270,22 @@ class WaldiezRunner:
259
270
  def _run(
260
271
  self,
261
272
  output_path: Optional[Union[str, Path]],
262
- ) -> Union[ChatResult, List[ChatResult]]:
273
+ ) -> Union["ChatResult", List["ChatResult"]]:
263
274
  self._install_requirements()
264
275
  token = self._stream.get()
265
276
  if token is not None:
277
+ # pylint: disable=import-outside-toplevel
278
+ from .io import WaldiezIOStream
279
+
266
280
  with WaldiezIOStream.set_default(token):
267
281
  return self._do_run(output_path)
268
282
  return self._do_run(output_path)
269
283
 
270
284
  def run(
271
285
  self,
272
- stream: Optional[WaldiezIOStream] = None,
286
+ stream: Optional["WaldiezIOStream"] = None,
273
287
  output_path: Optional[Union[str, Path]] = None,
274
- ) -> Union[ChatResult, List[ChatResult]]:
288
+ ) -> Union["ChatResult", List["ChatResult"]]:
275
289
  """Run the Waldiez workflow.
276
290
 
277
291
  Parameters
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.3
2
+ Name: waldiez
3
+ Version: 0.1.8
4
+ Summary: waldiez
5
+ Project-URL: homepage, https://waldiez.github.io/waldiez/
6
+ Project-URL: repository, https://github.com/waldiez/waldiez.git
7
+ Author-email: Panagiotis Kasnesis <pkasnesis@thingenious.io>, Lazaros Toumanidis <laztoum@protonmail.com>
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: <3.13,>=3.10
19
+ Requires-Dist: ag2==0.3.2
20
+ Requires-Dist: jupytext
21
+ Provides-Extra: ag2-extras
22
+ Requires-Dist: ag2[anthropic]==0.3.2; extra == 'ag2-extras'
23
+ Requires-Dist: ag2[bedrock]==0.3.2; extra == 'ag2-extras'
24
+ Requires-Dist: ag2[gemini]==0.3.2; extra == 'ag2-extras'
25
+ Requires-Dist: ag2[groq]==0.3.2; extra == 'ag2-extras'
26
+ Requires-Dist: ag2[mistral]==0.3.2; extra == 'ag2-extras'
27
+ Requires-Dist: ag2[retrievechat-mongodb]==0.3.2; extra == 'ag2-extras'
28
+ Requires-Dist: ag2[retrievechat-pgvector]==0.3.2; extra == 'ag2-extras'
29
+ Requires-Dist: ag2[retrievechat-qdrant]==0.3.2; extra == 'ag2-extras'
30
+ Requires-Dist: ag2[retrievechat]==0.3.2; extra == 'ag2-extras'
31
+ Requires-Dist: ag2[together]==0.3.2; extra == 'ag2-extras'
32
+ Requires-Dist: ag2[websurfer]==0.3.2; extra == 'ag2-extras'
33
+ Requires-Dist: chromadb==0.5.3; extra == 'ag2-extras'
34
+ Requires-Dist: fastembed==0.4.2; extra == 'ag2-extras'
35
+ Requires-Dist: pgvector>=0.2.5; extra == 'ag2-extras'
36
+ Requires-Dist: psycopg[binary]>=3.1.18; extra == 'ag2-extras'
37
+ Requires-Dist: pymongo==4.10.1; extra == 'ag2-extras'
38
+ Requires-Dist: qdrant-client==1.12.1; extra == 'ag2-extras'
39
+ Provides-Extra: dev
40
+ Requires-Dist: autoflake==2.3.1; extra == 'dev'
41
+ Requires-Dist: bandit==1.7.10; extra == 'dev'
42
+ Requires-Dist: black[jupyter]==24.10.0; extra == 'dev'
43
+ Requires-Dist: flake8==7.1.1; extra == 'dev'
44
+ Requires-Dist: isort==5.13.2; extra == 'dev'
45
+ Requires-Dist: mypy==1.13.0; extra == 'dev'
46
+ Requires-Dist: pre-commit==4.0.1; extra == 'dev'
47
+ Requires-Dist: pydocstyle==6.3.0; extra == 'dev'
48
+ Requires-Dist: pylint==3.3.1; extra == 'dev'
49
+ Requires-Dist: python-dotenv==1.0.1; extra == 'dev'
50
+ Requires-Dist: ruff==0.7.4; extra == 'dev'
51
+ Requires-Dist: types-pyyaml==6.0.12; extra == 'dev'
52
+ Requires-Dist: yamllint==1.35.1; extra == 'dev'
53
+ Provides-Extra: docs
54
+ Requires-Dist: mdx-include==1.4.2; extra == 'docs'
55
+ Requires-Dist: mdx-truly-sane-lists==1.3; extra == 'docs'
56
+ Requires-Dist: mkdocs-jupyter==0.25.1; extra == 'docs'
57
+ Requires-Dist: mkdocs-macros-plugin==1.3.7; extra == 'docs'
58
+ Requires-Dist: mkdocs-material==9.5.44; extra == 'docs'
59
+ Requires-Dist: mkdocs-minify-html-plugin==0.2.3; extra == 'docs'
60
+ Requires-Dist: mkdocs==1.6.1; extra == 'docs'
61
+ Requires-Dist: mkdocstrings-python==1.12.2; extra == 'docs'
62
+ Requires-Dist: mkdocstrings[crystal,python]==0.27.0; extra == 'docs'
63
+ Provides-Extra: test
64
+ Requires-Dist: pytest-cov==6.0.0; extra == 'test'
65
+ Requires-Dist: pytest-html==4.1.1; extra == 'test'
66
+ Requires-Dist: pytest-sugar==1.0.0; extra == 'test'
67
+ Requires-Dist: pytest-timeout==2.3.1; extra == 'test'
68
+ Requires-Dist: pytest-xdist==3.6.1; extra == 'test'
69
+ Requires-Dist: pytest==8.3.3; extra == 'test'
70
+ Description-Content-Type: text/markdown
71
+
72
+ # Waldiez
73
+
74
+ ![CI Build](https://github.com/waldiez/waldiez/actions/workflows/main.yaml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/waldiez/waldiez/badge.svg)](https://coveralls.io/github/waldiez/waldiez) [![PyPI version](https://badge.fury.io/waldiez/waldiez.svg)](https://badge.fury.io/py/waldiez)
75
+
76
+ Translate a Waldiez flow:
77
+
78
+ ![Flow](https://raw.githubusercontent.com/waldiez/waldiez/refs/heads/main/docs/static/images/overview.webp)
79
+
80
+ To a python script or a jupyter notebook with the corresponding [ag2](https://github.com/ag2ai/ag2/) agents and chats.
81
+
82
+ ## Features
83
+
84
+ - Export .waldiez flows to .py or .ipynb
85
+ - Run a .waldiez flow
86
+ - Include a `logs` folder with the logs of the flow in csv format
87
+ - Provide a custom [IOSStream](https://ag2ai.github.io/ag2/docs/reference/io/base#iostream) to handle input and output.
88
+
89
+ ## Installation
90
+
91
+ On PyPI:
92
+
93
+ ```bash
94
+ python -m pip install waldiez
95
+ ```
96
+
97
+ From the repository:
98
+
99
+ ```bash
100
+ python -m pip install git+https://github.com/waldiez/waldiez.git
101
+ ```
102
+
103
+ ## Usage
104
+
105
+ ### CLI
106
+
107
+ ```bash
108
+ # Export a Waldiez flow to a python script or a jupyter notebook
109
+ waldiez --export /path/to/a/flow.waldiez --output /path/to/an/output/flow[.py|.ipynb]
110
+ # Export and run the script, optionally force generation if the output file already exists
111
+ waldiez /path/to/a/flow.waldiez --output /path/to/an/output/flow[.py] [--force]
112
+ ```
113
+
114
+ ### Using docker/podman
115
+
116
+ ```shell
117
+ CONTAINER_COMMAND=docker # or podman
118
+ # pull the image
119
+ $CONTAINER_COMMAND pull waldiez/waldiez
120
+ # Export a Waldiez flow to a python script or a jupyter notebook
121
+ $CONTAINER_COMMAND run \
122
+ --rm \
123
+ -v /path/to/a/flow.waldiez:/flow.waldiez \
124
+ -v /path/to/an/output:/output \
125
+ waldiez/waldiez --export /flow.waldiez --output /output/flow[.py|.ipynb]
126
+
127
+ # with selinux and/or podman, you might get permission (or file not found) errors, so you can try:
128
+ $CONTAINER_COMMAND run \
129
+ --rm \
130
+ -v /path/to/a/flow.waldiez:/flow.waldiez \
131
+ -v /path/to/an/output:/output \
132
+ --userns=keep-id \
133
+ --security-opt label=disable \
134
+ waldiez/waldiez --export /flow.waldiez --output /output/flow[.py|.ipynb]
135
+ ```
136
+
137
+ ```shell
138
+ # Export and run the script
139
+ $CONTAINER_COMMAND run --rm -v /path/to/a/flow.waldiez:/flow.waldiez -v /path/to/an/output:/output waldiez/waldiez /flow.waldiez --output /output/output[.py]
140
+ ```
141
+
142
+ ### As a library
143
+
144
+ #### Export a flow
145
+
146
+ ```python
147
+ # Export a Waldiez flow to a python script or a jupyter notebook
148
+ from waldiez import WaldiezExporter
149
+ flow_path = "/path/to/a/flow.waldiez"
150
+ output_path = "/path/to/an/output.py" # or .ipynb
151
+ exporter = WaldiezExporter.load(flow_path)
152
+ exporter.export(output_path)
153
+ ```
154
+
155
+ #### Run a flow
156
+
157
+ ```python
158
+ # Run a flow
159
+ from waldiez import WaldiezRunner
160
+ flow_path = "/path/to/a/flow.waldiez"
161
+ output_path = "/path/to/an/output.py"
162
+ runner = WaldiezRunner.load(flow_path)
163
+ runner.run(output_path=output_path)
164
+ ```
165
+
166
+ #### Run a flow with a custom IOStream
167
+
168
+ ```python
169
+ # Run the flow with a custom IOStream
170
+ # In case the standard 'input' and 'print' functions cannot be used
171
+ import time
172
+ import threading
173
+
174
+ from typing import Any
175
+
176
+ from waldiez import WaldiezRunner
177
+ from waldiez.io import WaldiezIOStream
178
+
179
+ flow_path = "/path/to/a/flow.waldiez"
180
+ output_path = "/path/to/an/output.py"
181
+
182
+
183
+ def custom_print_function(*args: Any, sep: str = " ", **kwargs: Any) -> None:
184
+ """Custom print function."""
185
+ print(*args, sep=sep, **kwargs)
186
+
187
+
188
+ # Custom input handler
189
+ class InputProcessorWrapper:
190
+ """Wrapper input processor.
191
+
192
+ To manage the interaction between the custom input processor and IOStream.
193
+ """
194
+
195
+ def __init__(self):
196
+ self.stream = None # Placeholder for the WaldiezIOStream instance
197
+ self.lock = threading.Lock() # Ensure thread-safe operations
198
+
199
+ def custom_input_processor(self, prompt: str) -> None:
200
+ """Simulate external input and send it back to the IOStream."""
201
+ def external_input_simulation():
202
+ with self.lock: # Ensure thread-safe access
203
+ time.sleep(2) # Simulate delay for network input
204
+ if self.stream:
205
+ self.stream.set_input("Simulated external input")
206
+ else:
207
+ raise RuntimeError("Stream reference not set!")
208
+
209
+ threading.Thread(target=external_input_simulation, daemon=True).start()
210
+
211
+ def set_stream(self, stream: "WaldiezIOStream"):
212
+ """Set the WaldiezIOStream instance."""
213
+ with self.lock: # Ensure thread-safe setting of the stream
214
+ self.stream = stream
215
+
216
+ processor_wrapper = InputProcessorWrapper()
217
+
218
+ stream = WaldiezIOStream(
219
+ input_timeout=30,
220
+ print_function=
221
+ on_prompt_input=processor_wrapper.custom_input_processor,
222
+ )
223
+
224
+ # Link the processor wrapper to the WaldiezIOStream instance
225
+ processor_wrapper.set_stream(custom_stream)
226
+
227
+ with WaldiezIOStream.set_default(io_stream):
228
+ runner = WaldiezRunner.load(flow_path)
229
+ runner.run(stream=io_stream, output_path=output_path)
230
+
231
+ ```
232
+
233
+ ### Tools
234
+
235
+ - [ag2 (formerly AutoGen)](https://github.com/ag2ai/ag2)
236
+ - [juptytext](https://github.com/mwouts/jupytext)
237
+ - [pydantic](https://github.com/pydantic/pydantic)
238
+
239
+ ## License
240
+
241
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/waldiez/waldiez/blob/main/LICENSE) file for details.