waldiez 0.3.12__py3-none-any.whl → 0.4.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.

Files changed (50) hide show
  1. waldiez/_version.py +1 -1
  2. waldiez/cli.py +1 -3
  3. waldiez/exporting/agent/agent_exporter.py +5 -1
  4. waldiez/exporting/agent/utils/captain_agent.py +9 -12
  5. waldiez/exporting/agent/utils/swarm_agent.py +12 -7
  6. waldiez/exporting/base/utils/comments.py +1 -0
  7. waldiez/exporting/chats/utils/swarm.py +1 -1
  8. waldiez/exporting/flow/flow_exporter.py +3 -1
  9. waldiez/exporting/flow/utils/__init__.py +3 -6
  10. waldiez/exporting/flow/utils/flow_content.py +38 -0
  11. waldiez/exporting/flow/utils/importing_utils.py +64 -30
  12. waldiez/exporting/flow/utils/logging_utils.py +25 -4
  13. waldiez/exporting/skills/skills_exporter.py +13 -6
  14. waldiez/exporting/skills/utils.py +92 -6
  15. waldiez/models/agents/agent/__init__.py +2 -1
  16. waldiez/models/agents/agent/agent.py +5 -8
  17. waldiez/models/agents/agent/agent_type.py +11 -0
  18. waldiez/models/agents/captain_agent/captain_agent.py +1 -1
  19. waldiez/models/agents/group_manager/speakers.py +3 -0
  20. waldiez/models/agents/rag_user/retrieve_config.py +3 -0
  21. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +1 -0
  22. waldiez/models/agents/swarm_agent/after_work.py +13 -11
  23. waldiez/models/agents/swarm_agent/on_condition.py +3 -2
  24. waldiez/models/agents/swarm_agent/on_condition_available.py +1 -0
  25. waldiez/models/agents/swarm_agent/swarm_agent_data.py +3 -3
  26. waldiez/models/agents/swarm_agent/update_system_message.py +1 -0
  27. waldiez/models/chat/chat_message.py +1 -0
  28. waldiez/models/chat/chat_summary.py +1 -0
  29. waldiez/models/common/__init__.py +2 -0
  30. waldiez/models/common/method_utils.py +98 -0
  31. waldiez/models/model/__init__.py +2 -1
  32. waldiez/models/model/extra_requirements.py +2 -0
  33. waldiez/models/model/model.py +25 -6
  34. waldiez/models/model/model_data.py +1 -0
  35. waldiez/models/skill/__init__.py +4 -0
  36. waldiez/models/skill/extra_requirements.py +39 -0
  37. waldiez/models/skill/skill.py +157 -13
  38. waldiez/models/skill/skill_data.py +14 -0
  39. waldiez/models/skill/skill_type.py +8 -0
  40. waldiez/models/waldiez.py +36 -6
  41. waldiez/runner.py +19 -7
  42. waldiez/running/environment.py +30 -1
  43. waldiez/running/running.py +0 -6
  44. waldiez/utils/pysqlite3_checker.py +18 -5
  45. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/METADATA +25 -22
  46. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/RECORD +50 -47
  47. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/WHEEL +0 -0
  48. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/entry_points.txt +0 -0
  49. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/licenses/LICENSE +0 -0
  50. {waldiez-0.3.12.dist-info → waldiez-0.4.1.dist-info}/licenses/NOTICE.md +0 -0
waldiez/_version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
  """Version information for Waldiez."""
4
4
 
5
- __version__ = "0.3.12"
5
+ __version__ = "0.4.1"
waldiez/cli.py CHANGED
@@ -86,10 +86,8 @@ def run(
86
86
  ),
87
87
  ) -> None:
88
88
  """Run a Waldiez flow."""
89
- # a swarm chat without a user agent
90
- # creates a new user (this has a default code execution with docker)
91
- # temp (until we handle/detect docker setup)
92
89
  os.environ["AUTOGEN_USE_DOCKER"] = "0"
90
+ os.environ["NEP50_DISABLE_WARNING"] = "1"
93
91
  output_path = _get_output_path(output, force)
94
92
  with file.open("r", encoding="utf-8") as _file:
95
93
  try:
@@ -239,7 +239,11 @@ class AgentExporter(BaseExporter, ExporterMixin):
239
239
  extras = (
240
240
  f"{group_chat_arg}{retrieve_arg}{self._reasoning}{self._captain}"
241
241
  )
242
- agent_str = f"""{agent_name} = {self.agent.ag2_class}(
242
+ ag2_class = self.agent.ag2_class
243
+ if agent.agent_type == "swarm":
244
+ # SwarmAgent is deprecated.
245
+ ag2_class = "ConversableAgent"
246
+ agent_str = f"""{agent_name} = {ag2_class}(
243
247
  name="{agent_name}",
244
248
  description="{agent.description}"{system_message_arg},
245
249
  human_input_mode="{agent.data.human_input_mode}",
@@ -47,7 +47,7 @@ def get_captain_agent_extras(
47
47
  return ""
48
48
  agent_name = agent_names[agent.id]
49
49
  save_path = str(output_dir) if output_dir else "."
50
- extra_args_content = "\n" + f' agent_config_save_path=r"{save_path}",'
50
+ extra_args_content = "\n" + " agent_config_save_path=os.getcwd(),"
51
51
  if agent.data.agent_lib:
52
52
  lib_dict = [
53
53
  lib.model_dump(by_alias=False) for lib in agent.data.agent_lib
@@ -56,7 +56,7 @@ def get_captain_agent_extras(
56
56
  agent_lib_path = os.path.join(save_path, lib_json_name)
57
57
  with open(agent_lib_path, "w", encoding="utf-8", newline="\n") as f:
58
58
  json.dump(lib_dict, f, ensure_ascii=False, indent=4)
59
- extra_args_content += "\n" + f' agent_lib=r"{agent_lib_path}",'
59
+ extra_args_content += "\n" + f' agent_lib="{lib_json_name}",'
60
60
  if agent.data.tool_lib:
61
61
  extra_args_content += "\n" + f' tool_lib="{agent.data.tool_lib}",'
62
62
  nested_config = generate_nested_config(
@@ -97,19 +97,15 @@ def generate_nested_config(
97
97
  """
98
98
  config_file_or_env_name = f"{agent_name}_llm_config.json"
99
99
  llm_config = get_llm_config(agent, all_models)
100
- to_serialize = {
101
- "config_list": [llm_config],
102
- }
103
100
  os.makedirs(save_path, exist_ok=True)
104
101
  config_file_or_env_path = os.path.join(save_path, config_file_or_env_name)
105
102
  with open(
106
103
  config_file_or_env_path, "w", encoding="utf-8", newline="\n"
107
104
  ) as f:
108
- json.dump(to_serialize, f, ensure_ascii=False, indent=4)
109
- config_file_or_env = f'r"{config_file_or_env_path}"'
105
+ json.dump([llm_config], f, ensure_ascii=False, indent=4)
110
106
  nested_config = {
111
107
  "autobuild_init_config": {
112
- "config_file_or_env": config_file_or_env,
108
+ "config_file_or_env": config_file_or_env_name,
113
109
  "builder_model": llm_config["model"],
114
110
  "agent_model": llm_config["model"],
115
111
  },
@@ -146,10 +142,11 @@ def get_llm_config(
146
142
  max_tokens: Optional[int] = 2048
147
143
  if agent.data.model_ids:
148
144
  waldiez_model = get_waldiez_model(agent.data.model_ids[0], all_models)
149
- model_name = waldiez_model.name
150
- temperature = waldiez_model.data.temperature
151
- top_p = waldiez_model.data.top_p
152
- max_tokens = waldiez_model.data.max_tokens
145
+ llm_config = waldiez_model.get_llm_config(skip_price=True)
146
+ for key in ["temperature", "top_p", "max_tokens"]:
147
+ if key not in llm_config:
148
+ llm_config[key] = None
149
+ return llm_config
153
150
  config_dict = {
154
151
  "model": model_name,
155
152
  "temperature": temperature,
@@ -21,11 +21,11 @@ from waldiez.models import (
21
21
  # functions (List[Callable]):
22
22
  # -A list of functions to register with the agent.
23
23
  # update_agent_state_before_reply (List[Callable]):
24
- # - A list of functions, including UPDATE_SYSTEM_MESSAGEs,
24
+ # - A list of functions, including UpdateSystemMessage,
25
25
  # called to update the agent before it replies.
26
26
 
27
27
  # Additional methods:
28
- # register_hand_off(hand_offs: List[AfterWork|OnCondition]):
28
+ # register_hand_off(agent, hand_offs: List[AfterWork|OnCondition]):
29
29
 
30
30
 
31
31
  def get_swarm_extras(
@@ -173,14 +173,14 @@ def get_update_agent_state_before_reply_arg(
173
173
  name_suffix=agent_names[agent.id],
174
174
  )
175
175
  arg_string += (
176
- "\n" + f"{tab}{tab}UPDATE_SYSTEM_MESSAGE({function_name}),"
176
+ "\n" + f"{tab}{tab}UpdateSystemMessage({function_name}),"
177
177
  )
178
178
  before_agent += "\n" + function_content + "\n"
179
179
  else:
180
180
  escaped_function = string_escape(function.update_function)
181
181
  arg_string += (
182
182
  "\n"
183
- + f'{tab}{tab}UPDATE_SYSTEM_MESSAGE("{escaped_function}"),'
183
+ + f'{tab}{tab}UpdateSystemMessage("{escaped_function}"),'
184
184
  )
185
185
  else:
186
186
  skill_name = skill_names.get(function, "")
@@ -233,7 +233,12 @@ def get_agent_handoff_registrations(
233
233
  if not agent.handoffs:
234
234
  return before_agent, after_agent
235
235
  tab = " "
236
- after_agent = f"{agent_name}.register_hand_off(" + "\n" + f"{tab}[" + "\n"
236
+ # change {agent}.register_hand_off([...
237
+ # to register_hand_off({agent}, [...
238
+ # after_agent = f"{agent_name}.register_hand_off(" + "\n" + f"{tab}[" + "\n"
239
+ after_agent = (
240
+ "register_hand_off(\n" + f"{tab}{agent_name}," + "\n" + f"{tab}[" + "\n"
241
+ )
237
242
  for hand_off in agent.handoffs:
238
243
  if isinstance(hand_off, WaldiezSwarmOnCondition):
239
244
  registration, before_handoff = get_agent_on_condition_handoff(
@@ -385,7 +390,7 @@ def _get_agent_on_condition_handoff_to_agent(
385
390
  before_agent = ""
386
391
  tab = " "
387
392
  on_condition = (
388
- f"{tab}{tab}ON_CONDITION(" + "\n"
393
+ f"{tab}{tab}OnCondition(" + "\n"
389
394
  f"{tab}{tab}{tab}target={recipient}," + "\n"
390
395
  f'{tab}{tab}{tab}condition="{condition}",' + "\n"
391
396
  )
@@ -431,7 +436,7 @@ def _get_agent_on_condition_handoff_to_nested_chat(
431
436
  before_agent += f"{chat_queue_var_name} = {chat_queue} " + "\n"
432
437
  condition_string = string_escape(condition)
433
438
  on_condition = (
434
- f"{tab}{tab}ON_CONDITION(" + "\n"
439
+ f"{tab}{tab}OnCondition(" + "\n"
435
440
  f"{tab}{tab}{tab}target=" + "{\n"
436
441
  f'{tab}{tab}{tab}{tab}"chat_queue": {chat_queue_var_name},' + "\n"
437
442
  f'{tab}{tab}{tab}{tab}"config": None,' + "\n"
@@ -13,6 +13,7 @@ from typing_extensions import Literal
13
13
  CommentKey = Literal[
14
14
  "agents", "imports", "skills", "models", "nested", "run", "logging"
15
15
  ]
16
+ """Possible keys for comments."""
16
17
 
17
18
 
18
19
  def comment(for_notebook: bool, hashtags: int = 1) -> str:
@@ -196,7 +196,7 @@ def get_swarm_after_work_string(
196
196
  The after work string and the additional methods string.
197
197
  """
198
198
  if not chat.after_work:
199
- return "AFTER_WORK(AfterWorkOption.TERMINATE)", ""
199
+ return "AfterWork(AfterWorkOption.TERMINATE)", ""
200
200
  additional_methods = ""
201
201
  after_work_string, function_content = chat.after_work.get_recipient(
202
202
  agent_names=agent_names,
@@ -52,6 +52,7 @@ from .utils import (
52
52
  get_after_run_content,
53
53
  get_def_main,
54
54
  get_ipynb_content_start,
55
+ get_np_no_nep50_handle,
55
56
  get_py_content_start,
56
57
  get_sqlite_out,
57
58
  get_start_logging,
@@ -210,8 +211,9 @@ class FlowExporter(BaseExporter, ExporterMixin):
210
211
  )
211
212
  content += self.get_comment("imports", self.for_notebook) + "\n"
212
213
  content += imports[0] + "\n"
214
+ content += get_np_no_nep50_handle() + "\n"
213
215
  content += self.get_comment("logging", self.for_notebook) + "\n"
214
- content += get_start_logging(tabs=0) + "\n"
216
+ content += get_start_logging(is_async=is_async, tabs=0) + "\n"
215
217
  content += "start_logging()\n\n"
216
218
  if models_output:
217
219
  content += self.get_comment("models", self.for_notebook) + "\n"
@@ -14,14 +14,11 @@ from .def_main import get_def_main
14
14
  from .flow_content import (
15
15
  get_after_run_content,
16
16
  get_ipynb_content_start,
17
+ get_np_no_nep50_handle,
17
18
  get_py_content_start,
18
19
  )
19
20
  from .flow_names import ensure_unique_names
20
- from .importing_utils import (
21
- gather_imports,
22
- get_standard_imports,
23
- get_the_imports_string,
24
- )
21
+ from .importing_utils import gather_imports, get_the_imports_string
25
22
  from .logging_utils import (
26
23
  get_sqlite_out,
27
24
  get_sqlite_out_call,
@@ -41,12 +38,12 @@ __all__ = [
41
38
  "gather_imports",
42
39
  "get_after_run_content",
43
40
  "get_def_main",
41
+ "get_np_no_nep50_handle",
44
42
  "get_py_content_start",
45
43
  "get_ipynb_content_start",
46
44
  "get_start_logging",
47
45
  "get_stop_logging",
48
46
  "get_sqlite_out",
49
47
  "get_sqlite_out_call",
50
- "get_standard_imports",
51
48
  "get_the_imports_string",
52
49
  ]
@@ -71,12 +71,14 @@ PYLINT_RULES = [
71
71
  "unknown-option-value",
72
72
  "unused-argument",
73
73
  "unused-import",
74
+ "unused-variable",
74
75
  "invalid-name",
75
76
  "import-error",
76
77
  "inconsistent-quotes",
77
78
  "missing-function-docstring",
78
79
  "missing-param-doc",
79
80
  "missing-return-doc",
81
+ "ungrouped-imports",
80
82
  ]
81
83
 
82
84
 
@@ -159,3 +161,39 @@ def get_after_run_content(
159
161
  {space}{tab}pass
160
162
  """
161
163
  return content
164
+
165
+
166
+ def get_np_no_nep50_handle() -> str:
167
+ """Handle catching the "module numpy has no attribute _no_pep50_warning" error.
168
+
169
+ Returns
170
+ -------
171
+ str
172
+ The content to handle the error.
173
+ """
174
+ # https://github.com/numpy/numpy/blob/v2.2.2/\
175
+ # doc/source/release/2.2.0-notes.rst#nep-50-promotion-state-option-removed
176
+ content = '''
177
+ # try to make sure we don't get:
178
+ # module 'numpy' has no attribute '_no_nep50_warning'"
179
+ os.environ["NEP50_DEPRECATION_WARNING"] = "0"
180
+ os.environ["NEP50_DISABLE_WARNING"] = "1"
181
+ os.environ["NPY_PROMOTION_STATE"] = "weak"
182
+ if not hasattr(np, "_no_pep50_warning"):
183
+
184
+ import contextlib
185
+ from typing import Generator
186
+
187
+ @contextlib.contextmanager
188
+ def _np_no_nep50_warning() -> Generator[None, None, None]:
189
+ """Dummy function to avoid the warning.
190
+
191
+ Yields
192
+ ------
193
+ None
194
+ Nothing.
195
+ """
196
+ yield
197
+ setattr(np, "_no_pep50_warning", _np_no_nep50_warning) # noqa
198
+ '''
199
+ return content
@@ -36,21 +36,6 @@ COMMON_AUTOGEN_IMPORTS = [
36
36
  ]
37
37
 
38
38
 
39
- def get_standard_imports() -> str:
40
- """Get the standard imports.
41
-
42
- Returns
43
- -------
44
- str
45
- The standard imports.
46
- """
47
- builtin_imports = BUILTIN_IMPORTS.copy()
48
- imports_string = "\n".join(builtin_imports) + "\n"
49
- typing_imports = "from typing import " + ", ".join(TYPING_IMPORTS)
50
- imports_string += typing_imports
51
- return imports_string
52
-
53
-
54
39
  def sort_imports(
55
40
  all_imports: List[Tuple[str, ImportPosition]],
56
41
  ) -> Tuple[List[str], List[str], List[str], List[str], bool]:
@@ -66,10 +51,10 @@ def sort_imports(
66
51
  Tuple[List[str], List[str], List[str], List[str], bool]
67
52
  The sorted imports and a flag if we got `import autogen`.
68
53
  """
69
- builtin_imports = []
70
- third_party_imports = []
71
- local_imports = []
72
- autogen_imports = COMMON_AUTOGEN_IMPORTS.copy()
54
+ builtin_imports: List[str] = []
55
+ third_party_imports: List[str] = []
56
+ local_imports: List[str] = []
57
+ autogen_imports: List[str] = COMMON_AUTOGEN_IMPORTS.copy()
73
58
  got_import_autogen = False
74
59
  for import_string, position in all_imports:
75
60
  if "import autogen" in import_string:
@@ -85,11 +70,22 @@ def sort_imports(
85
70
  elif position == ImportPosition.LOCAL:
86
71
  local_imports.append(import_string)
87
72
  autogen_imports = list(set(autogen_imports))
73
+ third_party_imports = ensure_np_import(third_party_imports)
74
+ sorted_builtins = sorted(
75
+ [imp for imp in builtin_imports if imp.startswith("import ")]
76
+ ) + sorted([imp for imp in builtin_imports if imp.startswith("from ")])
77
+ sorted_third_party = sorted(
78
+ [imp for imp in third_party_imports if imp.startswith("import ")]
79
+ ) + sorted([imp for imp in third_party_imports if imp.startswith("from ")])
80
+ sorted_locals = sorted(
81
+ [imp for imp in local_imports if imp.startswith("import ")]
82
+ ) + sorted([imp for imp in local_imports if imp.startswith("from ")])
83
+
88
84
  return (
89
- sorted(builtin_imports),
85
+ sorted_builtins,
90
86
  sorted(autogen_imports),
91
- sorted(third_party_imports),
92
- sorted(local_imports),
87
+ sorted_third_party,
88
+ sorted_locals,
93
89
  got_import_autogen,
94
90
  )
95
91
 
@@ -131,7 +127,6 @@ def get_the_imports_string(
131
127
  "\nimport anyio"
132
128
  "\nimport nest_asyncio"
133
129
  "\nfrom aiocsv import AsyncDictWriter"
134
- "\nfrom asyncer import asyncify"
135
130
  )
136
131
  if got_import_autogen:
137
132
  final_string += "\nimport autogen # type: ignore\n"
@@ -150,6 +145,27 @@ def get_the_imports_string(
150
145
  return final_string.replace("\n\n\n", "\n\n") # avoid too many newlines
151
146
 
152
147
 
148
+ def ensure_np_import(third_party_imports: List[str]) -> List[str]:
149
+ """Ensure numpy is imported.
150
+
151
+ Parameters
152
+ ----------
153
+ third_party_imports : List[str]
154
+ The third party imports.
155
+
156
+ Returns
157
+ -------
158
+ List[str]
159
+ The third party imports with numpy.
160
+ """
161
+ if (
162
+ not third_party_imports
163
+ or "import numpy as np" not in third_party_imports
164
+ ):
165
+ third_party_imports.append("import numpy as np")
166
+ return third_party_imports
167
+
168
+
153
169
  def gather_imports(
154
170
  model_imports: Optional[List[Tuple[str, ImportPosition]]],
155
171
  skill_imports: Optional[List[Tuple[str, ImportPosition]]],
@@ -174,13 +190,14 @@ def gather_imports(
174
190
  Tuple[str, ImportPosition]
175
191
  The gathered imports.
176
192
  """
177
- imports_string = get_standard_imports()
178
- all_imports: List[Tuple[str, ImportPosition]] = [
179
- (
180
- imports_string,
181
- ImportPosition.BUILTINS,
193
+ all_imports: List[Tuple[str, ImportPosition]] = []
194
+ for import_statement in BUILTIN_IMPORTS:
195
+ all_imports.append(
196
+ (
197
+ import_statement,
198
+ ImportPosition.BUILTINS,
199
+ )
182
200
  )
183
- ]
184
201
  if model_imports:
185
202
  all_imports.extend(model_imports)
186
203
  if skill_imports:
@@ -189,4 +206,21 @@ def gather_imports(
189
206
  all_imports.extend(chat_imports)
190
207
  if agent_imports:
191
208
  all_imports.extend(agent_imports)
192
- return list(set(all_imports))
209
+ # let's try to avoid this:
210
+ # from typing import Annotated
211
+ # from typing import Annotated, Any, Callable, Dict, ...Union
212
+ all_typing_imports = TYPING_IMPORTS.copy()
213
+ final_imports: List[Tuple[str, ImportPosition]] = []
214
+ for import_statement, import_position in all_imports:
215
+ if import_statement.startswith("from typing"):
216
+ to_import = import_statement.split("import")[1].strip()
217
+ if to_import:
218
+ all_typing_imports.append(to_import)
219
+ else:
220
+ final_imports.append((import_statement, import_position))
221
+ unique_typing_imports = list(set(all_typing_imports))
222
+ one_typing_import = "from typing import " + ", ".join(
223
+ sorted(unique_typing_imports)
224
+ )
225
+ final_imports.insert(1, (one_typing_import, ImportPosition.BUILTINS))
226
+ return list(set(final_imports))
@@ -16,11 +16,13 @@ get_sqlite_to_csv_call_string
16
16
  """
17
17
 
18
18
 
19
- def get_start_logging(tabs: int = 0) -> str:
19
+ def get_start_logging(is_async: bool, tabs: int = 0) -> str:
20
20
  """Get the logging start call string.
21
21
 
22
22
  Parameters
23
23
  ----------
24
+ is_async : bool
25
+ Whether to use async mode.
24
26
  tabs : int, optional
25
27
  The number of tabs to use for indentation, by default 0
26
28
 
@@ -41,7 +43,8 @@ def get_start_logging(tabs: int = 0) -> str:
41
43
  )
42
44
  """
43
45
  tab = " " * tabs
44
- content = f'''
46
+ if is_async is False:
47
+ return f'''
45
48
  {tab}def start_logging() -> None:
46
49
  {tab} """Start logging."""
47
50
  {tab} runtime_logging.start(
@@ -49,7 +52,20 @@ def get_start_logging(tabs: int = 0) -> str:
49
52
  {tab} config={{"dbname": "flow.db"}},
50
53
  {tab} )
51
54
  '''
52
- return content
55
+ return f'''
56
+ {tab}def start_logging() -> None:
57
+ {tab} """Start logging."""
58
+ {tab} # pylint: disable=import-outside-toplevel
59
+ {tab} from anyio.from_thread import start_blocking_portal
60
+
61
+ {tab} with start_blocking_portal(backend="asyncio") as portal:
62
+ {tab} portal.call(
63
+ {tab} runtime_logging.start,
64
+ {tab} None,
65
+ {tab} "sqlite",
66
+ {tab} {{"dbname": "flow.db"}},
67
+ {tab} )
68
+ '''
53
69
 
54
70
 
55
71
  # pylint: disable=differing-param-doc,differing-type-doc
@@ -336,6 +352,11 @@ def get_stop_logging(tabs: int, is_async: bool) -> str:
336
352
  content += "async "
337
353
  content += "def stop_logging() -> None:\n"
338
354
  content += ' """Stop logging."""\n'
339
- content += f"{tab} runtime_logging.stop()\n"
355
+ if is_async:
356
+ content += f"{tab} # pylint: disable=import-outside-toplevel\n"
357
+ content += f"{tab} from asyncer import asyncify\n\n"
358
+ content += f"{tab} await asyncify(runtime_logging.stop)()\n"
359
+ else:
360
+ content += f"{tab} runtime_logging.stop()\n"
340
361
  content += get_sqlite_out_call(tabs + 1, is_async)
341
362
  return content
@@ -89,12 +89,18 @@ class SkillsExporter(BaseExporter, ExporterMixin):
89
89
  Tuple[str, int]
90
90
  The exported imports and the position of the imports.
91
91
  """
92
- if not self.skill_imports:
93
- return []
94
92
  imports: List[Tuple[str, ImportPosition]] = []
95
- for skill_import in self.skill_imports:
96
- if (skill_import, ImportPosition.LOCAL) not in imports:
97
- imports.append((skill_import, ImportPosition.LOCAL))
93
+ if not self.skill_imports:
94
+ return imports
95
+ # standard imports
96
+ for import_statement in self.skill_imports[0]:
97
+ imports.append((import_statement, ImportPosition.BUILTINS))
98
+ # third party imports
99
+ for import_statement in self.skill_imports[1]:
100
+ imports.append((import_statement, ImportPosition.THIRD_PARTY))
101
+ # secrets/local imports
102
+ for import_statement in self.skill_imports[2]:
103
+ imports.append((import_statement, ImportPosition.LOCAL))
98
104
  return imports
99
105
 
100
106
  def get_before_export(
@@ -156,11 +162,12 @@ class SkillsExporter(BaseExporter, ExporterMixin):
156
162
  the before export strings, the after export strings,
157
163
  and the environment variables.
158
164
  """
165
+ content = self.generate()
159
166
  imports = self.get_imports()
160
167
  after_export = self.get_after_export()
161
168
  environment_variables = self.get_environment_variables()
162
169
  result: ExporterReturnType = {
163
- "content": self.generate(),
170
+ "content": content,
164
171
  "imports": imports,
165
172
  "before_export": None,
166
173
  "after_export": after_export,
@@ -94,6 +94,7 @@ def _write_skill_secrets(
94
94
  return
95
95
  if not isinstance(output_dir, Path):
96
96
  output_dir = Path(output_dir)
97
+ output_dir.mkdir(parents=True, exist_ok=True)
97
98
  secrets_file = output_dir / f"{flow_name}_{skill_name}_secrets.py"
98
99
  first_line = f'"""Secrets for the skill: {skill_name}."""' + "\n"
99
100
  with secrets_file.open("w", encoding="utf-8", newline="\n") as f:
@@ -108,7 +109,7 @@ def export_skills(
108
109
  skills: List[WaldiezSkill],
109
110
  skill_names: Dict[str, str],
110
111
  output_dir: Optional[Union[str, Path]] = None,
111
- ) -> Tuple[List[str], List[Tuple[str, str]], str]:
112
+ ) -> Tuple[Tuple[List[str], List[str], List[str]], List[Tuple[str, str]], str]:
112
113
  """Get the skills' contents and secrets.
113
114
 
114
115
  If `output_dir` is provided, the contents are saved to that directory.
@@ -126,19 +127,26 @@ def export_skills(
126
127
 
127
128
  Returns
128
129
  -------
129
- Tuple[Set[str], Set[Tuple[str, str]], str]
130
+ Tuple[Tuple[List[str], List[str], List[str]], List[Tuple[str, str]], str]
130
131
  - The skill imports to use in the main file.
131
132
  - The skill secrets to set as environment variables.
132
133
  - The skills contents.
133
134
  """
134
- skill_imports: List[str] = []
135
+ skill_imports: Tuple[List[str], List[str], List[str]] = ([], [], [])
135
136
  skill_secrets: List[Tuple[str, str]] = []
136
137
  skill_contents: str = ""
137
138
  # if the skill.is_shared,
138
139
  # its contents must be first (before the other skills)
139
140
  shared_skill_contents = ""
140
141
  for skill in skills:
141
- skill_imports.append(get_skill_imports(flow_name, skill))
142
+ standard_skill_imports, third_party_skill_imports = skill.get_imports()
143
+ if standard_skill_imports:
144
+ skill_imports[0].extend(standard_skill_imports)
145
+ if third_party_skill_imports:
146
+ skill_imports[1].extend(third_party_skill_imports)
147
+ secrets_import = get_skill_secrets_import(flow_name, skill)
148
+ if secrets_import:
149
+ skill_imports[2].append(secrets_import)
142
150
  for key, value in skill.secrets.items():
143
151
  skill_secrets.append((key, value))
144
152
  _write_skill_secrets(
@@ -153,8 +161,14 @@ def export_skills(
153
161
  if skill.is_shared:
154
162
  shared_skill_contents += skill_content + "\n\n"
155
163
  else:
164
+ if skill.is_interop:
165
+ skill_content += _add_interop_extras(
166
+ skill=skill, skill_names=skill_names
167
+ )
156
168
  skill_contents += skill_content + "\n\n"
157
169
  skill_contents = shared_skill_contents + skill_contents
170
+ # remove dupes from imports if any and sort them
171
+ skill_imports = _sort_imports(skill_imports)
158
172
  return (
159
173
  skill_imports,
160
174
  skill_secrets,
@@ -162,8 +176,77 @@ def export_skills(
162
176
  )
163
177
 
164
178
 
165
- def get_skill_imports(flow_name: str, skill: WaldiezSkill) -> str:
166
- """Get the skill imports string.
179
+ def _add_interop_extras(
180
+ skill: WaldiezSkill,
181
+ skill_names: Dict[str, str],
182
+ ) -> str:
183
+ """Add the interop conversion.
184
+
185
+ Parameters
186
+ ----------
187
+ skill : WaldiezSkill
188
+ The skill
189
+ skill_names : Dict[str, str]
190
+ The skill names.
191
+
192
+ Returns
193
+ -------
194
+ str
195
+ The extra content to convert the tool.
196
+ """
197
+ skill_name = skill_names[skill.id]
198
+ interop_instance = f"ag2_{skill_name}_interop = Interoperability()" + "\n"
199
+ extra_content = (
200
+ f"ag2_{skill_name} = "
201
+ f"ag2_{skill_name}_interop.convert_tool("
202
+ f"tool={skill_name}, "
203
+ f'type="{skill.skill_type}")'
204
+ )
205
+ return "\n" + interop_instance + extra_content
206
+
207
+
208
+ def _sort_imports(
209
+ skill_imports: Tuple[List[str], List[str], List[str]],
210
+ ) -> Tuple[List[str], List[str], List[str]]:
211
+ """Sort the imports.
212
+
213
+ Parameters
214
+ ----------
215
+ skill_imports : Tuple[List[str], List[str], List[str]]
216
+ The skill imports.
217
+
218
+ Returns
219
+ -------
220
+ Tuple[List[str], List[str], List[str]]
221
+ The sorted skill imports.
222
+ """
223
+
224
+ # "from x import y" and "import z"
225
+ # the "import a" should be first (and sorted)
226
+ # then the "from b import c" (and sorted)
227
+ standard_lib_imports = skill_imports[0]
228
+ third_party_imports = skill_imports[1]
229
+ secrets_imports = skill_imports[2]
230
+
231
+ sorted_standard_lib_imports = sorted(
232
+ [imp for imp in standard_lib_imports if imp.startswith("import ")]
233
+ ) + sorted([imp for imp in standard_lib_imports if imp.startswith("from ")])
234
+
235
+ sorted_third_party_imports = sorted(
236
+ [imp for imp in third_party_imports if imp.startswith("import ")]
237
+ ) + sorted([imp for imp in third_party_imports if imp.startswith("from ")])
238
+
239
+ sorted_secrets_imports = sorted(secrets_imports)
240
+
241
+ return (
242
+ sorted_standard_lib_imports,
243
+ sorted_third_party_imports,
244
+ sorted_secrets_imports,
245
+ )
246
+
247
+
248
+ def get_skill_secrets_import(flow_name: str, skill: WaldiezSkill) -> str:
249
+ """Get the skill secrets import string.
167
250
 
168
251
  Parameters
169
252
  ----------
@@ -263,6 +346,9 @@ def get_agent_skill_registrations(
263
346
  skill for skill in all_skills if skill.id == linked_skill.id
264
347
  )
265
348
  skill_name = skill_names[linked_skill.id]
349
+ if waldiez_skill.is_interop:
350
+ # the name of the the converted to ag2 tool
351
+ skill_name = f"ag2_{skill_name}"
266
352
  skill_description = (
267
353
  waldiez_skill.description or f"Description of {skill_name}"
268
354
  )