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.
- waldiez/__init__.py +15 -0
- waldiez/__main__.py +6 -0
- waldiez/_version.py +3 -0
- waldiez/cli.py +162 -0
- waldiez/exporter.py +293 -0
- waldiez/exporting/__init__.py +14 -0
- waldiez/exporting/agents/__init__.py +5 -0
- waldiez/exporting/agents/agent.py +229 -0
- waldiez/exporting/agents/agent_skills.py +67 -0
- waldiez/exporting/agents/code_execution.py +67 -0
- waldiez/exporting/agents/group_manager.py +209 -0
- waldiez/exporting/agents/llm_config.py +53 -0
- waldiez/exporting/agents/rag_user/__init__.py +5 -0
- waldiez/exporting/agents/rag_user/chroma_utils.py +134 -0
- waldiez/exporting/agents/rag_user/mongo_utils.py +83 -0
- waldiez/exporting/agents/rag_user/pgvector_utils.py +93 -0
- waldiez/exporting/agents/rag_user/qdrant_utils.py +112 -0
- waldiez/exporting/agents/rag_user/rag_user.py +165 -0
- waldiez/exporting/agents/rag_user/vector_db.py +119 -0
- waldiez/exporting/agents/teachability.py +37 -0
- waldiez/exporting/agents/termination_message.py +45 -0
- waldiez/exporting/chats/__init__.py +14 -0
- waldiez/exporting/chats/chats.py +46 -0
- waldiez/exporting/chats/helpers.py +395 -0
- waldiez/exporting/chats/nested.py +264 -0
- waldiez/exporting/flow/__init__.py +5 -0
- waldiez/exporting/flow/def_main.py +37 -0
- waldiez/exporting/flow/flow.py +185 -0
- waldiez/exporting/models/__init__.py +193 -0
- waldiez/exporting/skills/__init__.py +128 -0
- waldiez/exporting/utils/__init__.py +34 -0
- waldiez/exporting/utils/comments.py +136 -0
- waldiez/exporting/utils/importing.py +267 -0
- waldiez/exporting/utils/logging_utils.py +203 -0
- waldiez/exporting/utils/method_utils.py +35 -0
- waldiez/exporting/utils/naming.py +127 -0
- waldiez/exporting/utils/object_string.py +81 -0
- waldiez/io_stream.py +181 -0
- waldiez/models/__init__.py +107 -0
- waldiez/models/agents/__init__.py +65 -0
- waldiez/models/agents/agent/__init__.py +21 -0
- waldiez/models/agents/agent/agent.py +190 -0
- waldiez/models/agents/agent/agent_data.py +162 -0
- waldiez/models/agents/agent/code_execution.py +71 -0
- waldiez/models/agents/agent/linked_skill.py +30 -0
- waldiez/models/agents/agent/nested_chat.py +73 -0
- waldiez/models/agents/agent/teachability.py +68 -0
- waldiez/models/agents/agent/termination_message.py +167 -0
- waldiez/models/agents/agents.py +129 -0
- waldiez/models/agents/assistant/__init__.py +6 -0
- waldiez/models/agents/assistant/assistant.py +41 -0
- waldiez/models/agents/assistant/assistant_data.py +29 -0
- waldiez/models/agents/group_manager/__init__.py +19 -0
- waldiez/models/agents/group_manager/group_manager.py +87 -0
- waldiez/models/agents/group_manager/group_manager_data.py +91 -0
- waldiez/models/agents/group_manager/speakers.py +211 -0
- waldiez/models/agents/rag_user/__init__.py +26 -0
- waldiez/models/agents/rag_user/rag_user.py +58 -0
- waldiez/models/agents/rag_user/rag_user_data.py +32 -0
- waldiez/models/agents/rag_user/retrieve_config.py +592 -0
- waldiez/models/agents/rag_user/vector_db_config.py +162 -0
- waldiez/models/agents/user_proxy/__init__.py +6 -0
- waldiez/models/agents/user_proxy/user_proxy.py +41 -0
- waldiez/models/agents/user_proxy/user_proxy_data.py +30 -0
- waldiez/models/chat/__init__.py +22 -0
- waldiez/models/chat/chat.py +129 -0
- waldiez/models/chat/chat_data.py +326 -0
- waldiez/models/chat/chat_message.py +304 -0
- waldiez/models/chat/chat_nested.py +160 -0
- waldiez/models/chat/chat_summary.py +110 -0
- waldiez/models/common/__init__.py +38 -0
- waldiez/models/common/base.py +63 -0
- waldiez/models/common/method_utils.py +165 -0
- waldiez/models/flow/__init__.py +9 -0
- waldiez/models/flow/flow.py +302 -0
- waldiez/models/flow/flow_data.py +87 -0
- waldiez/models/model/__init__.py +11 -0
- waldiez/models/model/model.py +169 -0
- waldiez/models/model/model_data.py +86 -0
- waldiez/models/skill/__init__.py +9 -0
- waldiez/models/skill/skill.py +129 -0
- waldiez/models/skill/skill_data.py +37 -0
- waldiez/models/waldiez.py +301 -0
- waldiez/py.typed +0 -0
- waldiez/runner.py +304 -0
- waldiez/stream/__init__.py +7 -0
- waldiez/stream/consumer.py +139 -0
- waldiez/stream/provider.py +339 -0
- waldiez/stream/server.py +412 -0
- waldiez-0.1.0.dist-info/METADATA +181 -0
- waldiez-0.1.0.dist-info/RECORD +94 -0
- waldiez-0.1.0.dist-info/WHEEL +4 -0
- waldiez-0.1.0.dist-info/entry_points.txt +2 -0
- waldiez-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Logging related string generation functions.
|
|
2
|
+
|
|
3
|
+
Functions
|
|
4
|
+
---------
|
|
5
|
+
get_logging_start_string
|
|
6
|
+
Get the string to start logging.
|
|
7
|
+
get_logging_stop_string
|
|
8
|
+
Get the string to stop logging.
|
|
9
|
+
get_sqlite_to_csv_string
|
|
10
|
+
Get the sqlite to csv conversion code string.
|
|
11
|
+
get_sqlite_to_csv_call_string
|
|
12
|
+
Get the string to call the sqlite to csv conversion.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Check issue:
|
|
17
|
+
# https://github.com/microsoft/autogen/issues/2286
|
|
18
|
+
# we cannot log new agents if they have code_execution enabled
|
|
19
|
+
# we get `Path` is not JSON serializable (on code_executor)
|
|
20
|
+
# pylint: disable=inconsistent-quotes
|
|
21
|
+
def get_logging_start_string(tabs: int = 0) -> str:
|
|
22
|
+
"""Get the logging start string.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
tabs : int, optional
|
|
27
|
+
The number of tabs to use for indentation, by default 0
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
str
|
|
32
|
+
The logging start string.
|
|
33
|
+
|
|
34
|
+
Example
|
|
35
|
+
-------
|
|
36
|
+
```python
|
|
37
|
+
>>> get_logging_start_string()
|
|
38
|
+
runtime_logging.start(
|
|
39
|
+
logger_type="sqlite",
|
|
40
|
+
config={"dbname": "flow.db"},
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
"""
|
|
44
|
+
tab = " " * tabs
|
|
45
|
+
content = f"{tab}runtime_logging.start(\n"
|
|
46
|
+
content += f'{tab} logger_type="sqlite",\n'
|
|
47
|
+
content += f'{tab} config={{"dbname": "flow.db"}},\n'
|
|
48
|
+
content += f"{tab})\n"
|
|
49
|
+
return content
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_logging_stop_string(tabs: int = 0) -> str:
|
|
53
|
+
"""Get the logging stop string.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
tabs : int, optional
|
|
58
|
+
The number of tabs to use for indentation, by default 0
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
str
|
|
63
|
+
The logging stop string
|
|
64
|
+
|
|
65
|
+
Example
|
|
66
|
+
-------
|
|
67
|
+
```python
|
|
68
|
+
>>> get_logging_stop_string()
|
|
69
|
+
runtime_logging.stop()
|
|
70
|
+
```
|
|
71
|
+
"""
|
|
72
|
+
tab = " " * tabs
|
|
73
|
+
return f"{tab}runtime_logging.stop()\n"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# pylint: disable=differing-param-doc,differing-type-doc
|
|
77
|
+
def get_sqlite_to_csv_string() -> str:
|
|
78
|
+
"""Get the sqlite to csv conversion code string.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
str
|
|
83
|
+
The sqlite to csv conversion code string.
|
|
84
|
+
|
|
85
|
+
Example
|
|
86
|
+
-------
|
|
87
|
+
```python
|
|
88
|
+
>>> get_sqlite_to_csv_string()
|
|
89
|
+
def sqlite_to_csv(dbname: str, table: str, csv_file: str) -> None:
|
|
90
|
+
\"\"\"Convert a sqlite table to a csv file.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
dbname : str
|
|
95
|
+
The sqlite database name.
|
|
96
|
+
table : str
|
|
97
|
+
The table name.
|
|
98
|
+
csv_file : str
|
|
99
|
+
The csv file name.
|
|
100
|
+
\"\"\"
|
|
101
|
+
conn = sqlite3.connect(dbname)
|
|
102
|
+
query = f"SELECT * FROM {table}" # nosec
|
|
103
|
+
cursor = conn.execute(query)
|
|
104
|
+
rows = cursor.fetchall()
|
|
105
|
+
column_names = [description[0] for description in cursor.description]
|
|
106
|
+
data = [dict(zip(column_names, row)) for row in rows]
|
|
107
|
+
conn.close()
|
|
108
|
+
with open(csv_file, "w", newline="", encoding="utf-8") as file:
|
|
109
|
+
_csv_writer = csv.DictWriter(file, fieldnames=column_names)
|
|
110
|
+
_csv_writer.writeheader()
|
|
111
|
+
_csv_writer.writerows(data)
|
|
112
|
+
```
|
|
113
|
+
"""
|
|
114
|
+
content = "\n\n"
|
|
115
|
+
content += (
|
|
116
|
+
"def sqlite_to_csv(dbname: str, table: str, csv_file: str) -> None:\n"
|
|
117
|
+
)
|
|
118
|
+
content += ' """Convert a sqlite table to a csv file.\n\n'
|
|
119
|
+
content += " Parameters\n"
|
|
120
|
+
content += " ----------\n"
|
|
121
|
+
content += " dbname : str\n"
|
|
122
|
+
content += " The sqlite database name.\n"
|
|
123
|
+
content += " table : str\n"
|
|
124
|
+
content += " The table name.\n"
|
|
125
|
+
content += " csv_file : str\n"
|
|
126
|
+
content += " The csv file name.\n"
|
|
127
|
+
content += ' """\n'
|
|
128
|
+
content += " conn = sqlite3.connect(dbname)\n"
|
|
129
|
+
content += ' query = f"SELECT * FROM {table}" # nosec\n'
|
|
130
|
+
content += " try:\n"
|
|
131
|
+
content += " cursor = conn.execute(query)\n"
|
|
132
|
+
content += " except sqlite3.OperationalError:\n"
|
|
133
|
+
content += " conn.close()\n"
|
|
134
|
+
content += " return\n"
|
|
135
|
+
content += " rows = cursor.fetchall()\n"
|
|
136
|
+
content += " column_names = [description[0] for description "
|
|
137
|
+
content += "in cursor.description]\n"
|
|
138
|
+
content += " data = [dict(zip(column_names, row)) for row in rows]\n"
|
|
139
|
+
content += " conn.close()\n"
|
|
140
|
+
content += (
|
|
141
|
+
' with open(csv_file, "w", newline="", encoding="utf-8") as file:\n'
|
|
142
|
+
)
|
|
143
|
+
content += (
|
|
144
|
+
" _csv_writer = csv.DictWriter(file, fieldnames=column_names)\n"
|
|
145
|
+
)
|
|
146
|
+
content += " _csv_writer.writeheader()\n"
|
|
147
|
+
content += " _csv_writer.writerows(data)\n"
|
|
148
|
+
content += "\n\n"
|
|
149
|
+
return content
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_sqlite_to_csv_call_string(tabs: int = 0) -> str:
|
|
153
|
+
"""Get the sqlite to csv conversion call string.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
tabs : int, optional
|
|
158
|
+
The number of tabs to use for indentation, by default 0
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
str
|
|
163
|
+
The sqlite to csv conversion call string.
|
|
164
|
+
|
|
165
|
+
Example
|
|
166
|
+
-------
|
|
167
|
+
```python
|
|
168
|
+
>>> get_sqlite_to_csv_call_string()
|
|
169
|
+
if not os.path.exists("logs"):
|
|
170
|
+
os.makedirs("logs")
|
|
171
|
+
for table in [
|
|
172
|
+
"chat_completions",
|
|
173
|
+
"agents",
|
|
174
|
+
"oai_wrappers",
|
|
175
|
+
"oai_clients",
|
|
176
|
+
"version",
|
|
177
|
+
"events",
|
|
178
|
+
"function_calls",
|
|
179
|
+
]:
|
|
180
|
+
dest = os.path.join("logs", f"{table}.csv")
|
|
181
|
+
sqlite_to_csv("flow.db", table, dest)
|
|
182
|
+
```
|
|
183
|
+
"""
|
|
184
|
+
table_names = [
|
|
185
|
+
"chat_completions",
|
|
186
|
+
"agents",
|
|
187
|
+
"oai_wrappers",
|
|
188
|
+
"oai_clients",
|
|
189
|
+
"version",
|
|
190
|
+
"events",
|
|
191
|
+
"function_calls",
|
|
192
|
+
]
|
|
193
|
+
tab = " " * tabs
|
|
194
|
+
content = ""
|
|
195
|
+
content += tab + 'if not os.path.exists("logs"):\n'
|
|
196
|
+
content += tab + ' os.makedirs("logs")\n'
|
|
197
|
+
content += tab + "for table in [\n"
|
|
198
|
+
for table in table_names:
|
|
199
|
+
content += tab + f' "{table}",\n'
|
|
200
|
+
content += tab + "]:\n"
|
|
201
|
+
content += tab + ' dest = os.path.join("logs", f"{table}.csv")\n'
|
|
202
|
+
content += tab + ' sqlite_to_csv("flow.db", table, dest)\n'
|
|
203
|
+
return content
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Method related string generation utilities."""
|
|
2
|
+
|
|
3
|
+
from waldiez.models import METHOD_ARGS, WaldiezMethodName
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_method_string(
|
|
7
|
+
method_name: WaldiezMethodName, renamed_method_name: str, method_body: str
|
|
8
|
+
) -> str:
|
|
9
|
+
"""Get a function string.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
method_name : WaldiezMethodName
|
|
14
|
+
The method name.
|
|
15
|
+
renamed_method_name : str
|
|
16
|
+
The renamed method name.
|
|
17
|
+
method_body : str
|
|
18
|
+
The method body.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
str
|
|
23
|
+
The function string having the definition, type hints and body.
|
|
24
|
+
"""
|
|
25
|
+
method_args = METHOD_ARGS[method_name]
|
|
26
|
+
content = f"def {renamed_method_name}("
|
|
27
|
+
if len(method_args) == 0:
|
|
28
|
+
content += "):"
|
|
29
|
+
else:
|
|
30
|
+
content += "\n"
|
|
31
|
+
for arg in method_args:
|
|
32
|
+
content += f" {arg},\n"
|
|
33
|
+
content += "):"
|
|
34
|
+
content += f"\n{method_body}"
|
|
35
|
+
return content
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Naming related string generation functions.
|
|
2
|
+
|
|
3
|
+
Functions
|
|
4
|
+
---------
|
|
5
|
+
get_valid_python_variable_name
|
|
6
|
+
Make sure a string is a valid Python variable name.
|
|
7
|
+
get_valid_instance_name
|
|
8
|
+
Get a valid instance name.
|
|
9
|
+
get_escaped_string
|
|
10
|
+
Get a string with escaped quotes and newlines.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from typing import Dict, Tuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_valid_python_variable_name(
|
|
18
|
+
possible: str,
|
|
19
|
+
prefix: str = "w",
|
|
20
|
+
) -> str:
|
|
21
|
+
"""Get a valid Python variable name from a possible name.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
possible : str
|
|
26
|
+
The possible name.
|
|
27
|
+
|
|
28
|
+
prefix : str, optional
|
|
29
|
+
The prefix to use if the name starts with a digit or special character
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
str
|
|
34
|
+
The valid Python variable name.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def replacement(match: re.Match[str]) -> str:
|
|
38
|
+
"""Get the replacement for the match.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
match : re.Match[str]
|
|
43
|
+
The match.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
str
|
|
48
|
+
The replacement
|
|
49
|
+
"""
|
|
50
|
+
if match.group(0) in ["->", "=>"]:
|
|
51
|
+
return "to"
|
|
52
|
+
if match.group(0) in ["<-", "<="]:
|
|
53
|
+
return "from"
|
|
54
|
+
if re.match(r"\W|^(?=\d)", match.group(0)):
|
|
55
|
+
return "_"
|
|
56
|
+
return match.group(0)
|
|
57
|
+
|
|
58
|
+
possible = re.sub(r"->|=>|<-|<=|\W|^(?=\d)", replacement, possible)[
|
|
59
|
+
:64
|
|
60
|
+
].lower()
|
|
61
|
+
|
|
62
|
+
if not possible:
|
|
63
|
+
return prefix + "_"
|
|
64
|
+
if possible.startswith("_"):
|
|
65
|
+
return f"{prefix}{possible}"
|
|
66
|
+
if possible[0].isdigit():
|
|
67
|
+
return f"{prefix}_{possible}"
|
|
68
|
+
return possible
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_valid_instance_name(
|
|
72
|
+
instance: Tuple[str, str],
|
|
73
|
+
current_names: Dict[str, str],
|
|
74
|
+
prefix: str = "w",
|
|
75
|
+
) -> Dict[str, str]:
|
|
76
|
+
"""Get a valid instance name.
|
|
77
|
+
|
|
78
|
+
If the instance id is already in the current names nothing is done.
|
|
79
|
+
If the name already exists in the current names,
|
|
80
|
+
the name is updated (with an index suffix).
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
instance : Tuple[str, str]
|
|
85
|
+
The instance id and possible name.
|
|
86
|
+
current_names : Dict[str, str]
|
|
87
|
+
The current names.
|
|
88
|
+
prefix : str, optional
|
|
89
|
+
The prefix to use if the name starts with a digit,
|
|
90
|
+
if the name is already in the current names,
|
|
91
|
+
or if the name is already in the current names with an index suffix.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
Dict[str, str]
|
|
96
|
+
The updated names.
|
|
97
|
+
"""
|
|
98
|
+
instance_id, possible_name = instance
|
|
99
|
+
if instance_id in current_names:
|
|
100
|
+
return current_names
|
|
101
|
+
new_names = current_names.copy()
|
|
102
|
+
name = get_valid_python_variable_name(possible_name, prefix)
|
|
103
|
+
if name in current_names.values():
|
|
104
|
+
name = f"{prefix}_{name}"
|
|
105
|
+
if name in current_names.values():
|
|
106
|
+
index = 1
|
|
107
|
+
while f"{name}_{index}" in current_names.values():
|
|
108
|
+
index += 1
|
|
109
|
+
name = f"{name}_{index}"
|
|
110
|
+
new_names[instance_id] = name
|
|
111
|
+
return new_names
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_escaped_string(string: str) -> str:
|
|
115
|
+
"""Get a string with escaped quotes and newlines.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
string : str
|
|
120
|
+
The original string.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
str
|
|
125
|
+
The escaped string.
|
|
126
|
+
"""
|
|
127
|
+
return string.replace('"', '\\"').replace("\n", "\\n")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Function to convert an object to a formatted string with indentation.
|
|
2
|
+
|
|
3
|
+
To be used with dicts and/or lists.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_object_string(obj: Any, tabs: int = 1) -> str:
|
|
10
|
+
"""Convert an object to a formatted string with given indentation.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
obj : Any
|
|
15
|
+
The object to convert.
|
|
16
|
+
tabs : int, optional
|
|
17
|
+
The number of tabs, by default 1.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
str
|
|
22
|
+
The formatted string.
|
|
23
|
+
|
|
24
|
+
Example
|
|
25
|
+
-------
|
|
26
|
+
```python
|
|
27
|
+
>>> obj = {"a": 1, "b": [1, 2, 3]}
|
|
28
|
+
>>> get_object_string(obj)
|
|
29
|
+
{
|
|
30
|
+
"a": 1,
|
|
31
|
+
"b": [
|
|
32
|
+
1,
|
|
33
|
+
2,
|
|
34
|
+
3
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
>>> obj = {"a": 1, "b": [1, 2, 3], "c": {"d": 4}}
|
|
38
|
+
>>> get_object_string(obj, 2)
|
|
39
|
+
{
|
|
40
|
+
"a": 1,
|
|
41
|
+
"b": [
|
|
42
|
+
1,
|
|
43
|
+
2,
|
|
44
|
+
3
|
|
45
|
+
],
|
|
46
|
+
"c": {
|
|
47
|
+
"d": 4
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
indent = " " * 4 * tabs # Number of spaces corresponding to the tabs
|
|
53
|
+
next_indent = (
|
|
54
|
+
" " * 4 * (tabs + 1)
|
|
55
|
+
) # Number of spaces corresponding to the next tab level
|
|
56
|
+
if isinstance(obj, dict):
|
|
57
|
+
items = []
|
|
58
|
+
for key, value in obj.items():
|
|
59
|
+
items.append(
|
|
60
|
+
f'{next_indent}"{key}": {get_object_string(value, tabs + 1)}'
|
|
61
|
+
)
|
|
62
|
+
# python3.10? f-string expression part cannot include a backslash
|
|
63
|
+
items_string = ",\n".join(items)
|
|
64
|
+
to_return = "\n" + items_string + "\n" + indent
|
|
65
|
+
return f"{{{to_return}}}"
|
|
66
|
+
# return f'{{\n{",\n".join(items)}\n{indent}}}'
|
|
67
|
+
if isinstance(obj, list):
|
|
68
|
+
items = []
|
|
69
|
+
for item in obj:
|
|
70
|
+
items.append(f"{next_indent}{get_object_string(item, tabs + 1)}")
|
|
71
|
+
# python3.10? f-string expression part cannot include a backslash
|
|
72
|
+
items_string = ",\n".join(items)
|
|
73
|
+
to_return = "\n" + items_string + "\n" + indent
|
|
74
|
+
return f"[{to_return}]"
|
|
75
|
+
|
|
76
|
+
if isinstance(obj, str):
|
|
77
|
+
return f'"{obj}"'
|
|
78
|
+
|
|
79
|
+
if obj is None:
|
|
80
|
+
return "None"
|
|
81
|
+
return str(obj)
|
waldiez/io_stream.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
We use:
|
|
11
|
+
|
|
12
|
+
- one tcp server to handle messaging between the clients
|
|
13
|
+
- one tcp client (provider) to set and forward the user's input
|
|
14
|
+
that we got elsewhere (e.g. from a websocket connection)
|
|
15
|
+
- one tcp client (consumer) to ask and get the input from the provider
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import socket
|
|
19
|
+
from contextlib import closing
|
|
20
|
+
from typing import Any, Callable, Optional
|
|
21
|
+
|
|
22
|
+
from autogen.io import IOStream # type: ignore[import-untyped]
|
|
23
|
+
|
|
24
|
+
from .stream import TCPConsumer, TCPProvider, TCPServer
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WaldiezIOStream(IOStream):
|
|
28
|
+
"""Custom IOStream class to handle the `print` and `input` functions."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
port: int = 0,
|
|
33
|
+
input_timeout: float = 60,
|
|
34
|
+
print_function: Optional[Callable[..., None]] = None,
|
|
35
|
+
on_prompt_input: Optional[Callable[[str], None]] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Initialize the IOStream.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
port : int, optional
|
|
42
|
+
The port to use, by default 0 (auto-assign).
|
|
43
|
+
input_timeout : float, optional
|
|
44
|
+
The input timeout, by default 60.
|
|
45
|
+
print_function : Optional[Callable[..., None]], optional
|
|
46
|
+
The print function to use, by default None.
|
|
47
|
+
on_prompt_input : Optional[Callable[[str], None]], optional
|
|
48
|
+
The function to call for getting an input, by default None.
|
|
49
|
+
"""
|
|
50
|
+
self._print_function = print_function
|
|
51
|
+
if port == 0:
|
|
52
|
+
port = get_available_port()
|
|
53
|
+
self._port = port
|
|
54
|
+
self._input_timeout = input_timeout
|
|
55
|
+
self._server = TCPServer(port)
|
|
56
|
+
self._server.start()
|
|
57
|
+
self._provider = TCPProvider("localhost", port, response=None)
|
|
58
|
+
self._on_prompt_input = on_prompt_input
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def print_function(self) -> Optional[Callable[..., None]]:
|
|
62
|
+
"""Get the print function."""
|
|
63
|
+
return self._print_function
|
|
64
|
+
|
|
65
|
+
def open(self) -> None:
|
|
66
|
+
"""Start the server."""
|
|
67
|
+
if not self._server.is_running():
|
|
68
|
+
self._server.start()
|
|
69
|
+
|
|
70
|
+
def close(self) -> None:
|
|
71
|
+
"""Stop the server and the provider."""
|
|
72
|
+
# pylint: disable=broad-except
|
|
73
|
+
if self._server.is_running():
|
|
74
|
+
try:
|
|
75
|
+
self._server.stop()
|
|
76
|
+
except BaseException: # pragma: no cover
|
|
77
|
+
pass
|
|
78
|
+
try:
|
|
79
|
+
self._provider.stop()
|
|
80
|
+
except BaseException: # pragma: no cover
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
def __del__(self) -> None: # pragma: no cover
|
|
84
|
+
"""Delete the instance."""
|
|
85
|
+
self.close()
|
|
86
|
+
|
|
87
|
+
def forward_input(self, input_data: str) -> None:
|
|
88
|
+
"""Forward the user's input to the provider.
|
|
89
|
+
|
|
90
|
+
When we have the input data
|
|
91
|
+
e.g. from 'input(..)' or from a websocket connection,
|
|
92
|
+
we can forward it to the provider (the tcp client)
|
|
93
|
+
to make it available to the consumer (the other tcp client).
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
input_data : str
|
|
97
|
+
The input data to forward.
|
|
98
|
+
"""
|
|
99
|
+
if not self._provider.is_running():
|
|
100
|
+
self._provider.start()
|
|
101
|
+
self._provider.set_response(input_data)
|
|
102
|
+
|
|
103
|
+
def print(
|
|
104
|
+
self,
|
|
105
|
+
*objects: Any,
|
|
106
|
+
sep: str = " ",
|
|
107
|
+
end: str = "\n",
|
|
108
|
+
flush: bool = False,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Mock the print function.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
objects : Any
|
|
115
|
+
The objects to print.
|
|
116
|
+
sep : str, optional
|
|
117
|
+
The separator, by default " ".
|
|
118
|
+
end : str, optional
|
|
119
|
+
The end, by default a new line.
|
|
120
|
+
flush : bool, optional
|
|
121
|
+
Whether to flush, by default False.
|
|
122
|
+
"""
|
|
123
|
+
print_function: Callable[..., None] = self.print_function or print
|
|
124
|
+
print_function(*objects, sep=sep, end=end, flush=flush)
|
|
125
|
+
|
|
126
|
+
def input(self, prompt: str = "", *, password: bool = False) -> str:
|
|
127
|
+
"""Mock the input function.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
prompt : str, optional
|
|
132
|
+
The prompt to show, by default "".
|
|
133
|
+
password : bool, optional (not used)
|
|
134
|
+
Whether to show the input as password, by default False.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
str
|
|
139
|
+
The user's input.
|
|
140
|
+
"""
|
|
141
|
+
_prompt = prompt or "Your input:"
|
|
142
|
+
if _prompt in (">", "> "):
|
|
143
|
+
_prompt = "Your input:"
|
|
144
|
+
if prompt:
|
|
145
|
+
if self._on_prompt_input:
|
|
146
|
+
self._on_prompt_input(_prompt)
|
|
147
|
+
self.print(_prompt, end="")
|
|
148
|
+
if not self._provider.is_running():
|
|
149
|
+
self._provider.start()
|
|
150
|
+
# wait for the provider to start
|
|
151
|
+
self._provider.wait(timeout=self._input_timeout)
|
|
152
|
+
if not self._provider.is_running(): # pragma: no cover
|
|
153
|
+
self.print(
|
|
154
|
+
"WARNING: Provider is not running. Was an input expected?"
|
|
155
|
+
)
|
|
156
|
+
return "\n"
|
|
157
|
+
consumer = TCPConsumer(
|
|
158
|
+
"localhost", self._port, timeout=self._input_timeout
|
|
159
|
+
)
|
|
160
|
+
consumer.start()
|
|
161
|
+
# send the prompt and wait for the response
|
|
162
|
+
consumer.send_prompt(_prompt)
|
|
163
|
+
response = consumer.get_response()
|
|
164
|
+
consumer.stop()
|
|
165
|
+
self._provider.stop()
|
|
166
|
+
# return the response or a line break (i.e. no input)
|
|
167
|
+
return response or "\n"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_available_port() -> int:
|
|
171
|
+
"""Get an available port.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
int
|
|
176
|
+
Available port.
|
|
177
|
+
"""
|
|
178
|
+
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as soc:
|
|
179
|
+
soc.bind(("", 0))
|
|
180
|
+
soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
181
|
+
return soc.getsockname()[1]
|