ag2 0.9.4__py3-none-any.whl → 0.9.6__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 ag2 might be problematic. Click here for more details.
- {ag2-0.9.4.dist-info → ag2-0.9.6.dist-info}/METADATA +1 -1
- {ag2-0.9.4.dist-info → ag2-0.9.6.dist-info}/RECORD +22 -13
- autogen/agentchat/realtime/experimental/realtime_swarm.py +2 -0
- autogen/code_utils.py +8 -6
- autogen/coding/docker_commandline_code_executor.py +29 -9
- autogen/environments/__init__.py +10 -0
- autogen/environments/docker_python_environment.py +375 -0
- autogen/environments/python_environment.py +134 -0
- autogen/environments/system_python_environment.py +86 -0
- autogen/environments/venv_python_environment.py +224 -0
- autogen/environments/working_directory.py +75 -0
- autogen/llm_config.py +6 -3
- autogen/mcp/mcp_proxy/mcp_proxy.py +4 -4
- autogen/oai/client.py +72 -8
- autogen/oai/openai_responses.py +426 -0
- autogen/tools/experimental/__init__.py +2 -0
- autogen/tools/experimental/code_execution/__init__.py +7 -0
- autogen/tools/experimental/code_execution/python_code_execution.py +88 -0
- autogen/version.py +1 -1
- {ag2-0.9.4.dist-info → ag2-0.9.6.dist-info}/WHEEL +0 -0
- {ag2-0.9.4.dist-info → ag2-0.9.6.dist-info}/licenses/LICENSE +0 -0
- {ag2-0.9.4.dist-info → ag2-0.9.6.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
autogen/__init__.py,sha256=8JVF68yM6D3qsY2ZXLOwX0C7QK2td45Veka3d5zAGHA,2108
|
|
2
2
|
autogen/browser_utils.py,sha256=tkNiADQuO27htXvj1FcQfGvw02Qjn-L-l5A0MJhc8lY,13264
|
|
3
|
-
autogen/code_utils.py,sha256=
|
|
3
|
+
autogen/code_utils.py,sha256=7-u4a0O24c0P3vfag5R7lCPze7uFjFDVCktOxESD87A,24118
|
|
4
4
|
autogen/doc_utils.py,sha256=RwKfLUKAnRLYLFI_ffiko6Y7NWG0fxEzpBQjJJOc4D0,1046
|
|
5
5
|
autogen/exception_utils.py,sha256=YpuaczyoZ4wHFhyv1a_UA9CpUN2KKIU4johIRm_mOaQ,2486
|
|
6
6
|
autogen/formatting_utils.py,sha256=JfENmuHOmDQEQSUPOMvc8e4jAaJ2CJyppp4Fw9ePPDc,2122
|
|
@@ -8,13 +8,13 @@ autogen/function_utils.py,sha256=YnzwNFA49Jbbe4riAY1sinYcKphg5lrHFCXx0POdYbw,481
|
|
|
8
8
|
autogen/graph_utils.py,sha256=2dfGUHZCCF629vh0vMK9WMXIX-Zi-if9NbdC0KFcuw4,7904
|
|
9
9
|
autogen/import_utils.py,sha256=byPlexRvu1bkGzEAtwwtJ2SQIm1IzModK9B5ONGkqJw,17634
|
|
10
10
|
autogen/json_utils.py,sha256=_61k__3SS1-ffE2K5Mm9ZGHQJBRUbOL9kHGQGTAWRZk,1378
|
|
11
|
-
autogen/llm_config.py,sha256=
|
|
11
|
+
autogen/llm_config.py,sha256=lQPV3zXbLKL_7rJHi1fJAJn1NpNK7wZtqGUxHeapIZQ,14437
|
|
12
12
|
autogen/math_utils.py,sha256=Ew9-I5chny9eAOnphGxKf6XCeW6Pepz5S-5touWrbYU,9546
|
|
13
13
|
autogen/retrieve_utils.py,sha256=R3Yp5d8dH4o9ayLZrGn4rCjIaY4glOHIiyQjwClmdi8,20087
|
|
14
14
|
autogen/runtime_logging.py,sha256=yCmZODvwqYR91m8lX3Q4SoPcY-DK48NF4m56CP6Om3c,4692
|
|
15
15
|
autogen/token_count_utils.py,sha256=n4wTFVNHwrfjZkrErFr8kNig2K-YCGgMLWsjDRS9D6g,10797
|
|
16
16
|
autogen/types.py,sha256=qu-7eywhakW2AxQ5lYisLLeIg45UoOW-b3ErIuyRTuw,1000
|
|
17
|
-
autogen/version.py,sha256=
|
|
17
|
+
autogen/version.py,sha256=Gx2IBExx_OQsL98JzXZ9G56t2UljGjVfvrmaV4UzVp4,193
|
|
18
18
|
autogen/_website/__init__.py,sha256=c8B9TpO07x9neD0zsJWj6AaEdlcP-WvxrvVOGWLtamk,143
|
|
19
19
|
autogen/_website/generate_api_references.py,sha256=yKqyeSP_NE27wwLYWsZbTYRceEoxzNxPXqn6vsIzEvk,14789
|
|
20
20
|
autogen/_website/generate_mkdocs.py,sha256=TkmLnUDv1Ms5cGClXPmenA8nxmwg4kR0E-FHCVjw_og,43246
|
|
@@ -159,7 +159,7 @@ autogen/agentchat/realtime/experimental/function_observer.py,sha256=M0cXXJNoBQ8s
|
|
|
159
159
|
autogen/agentchat/realtime/experimental/realtime_agent.py,sha256=i8rxU-Tjk2Pz-WOZJ5PuRymaMvVLH66lqH2LJ85PxLM,5713
|
|
160
160
|
autogen/agentchat/realtime/experimental/realtime_events.py,sha256=zmRr3pwPJpme5VZEADIz5vg9IZoT3Z1NAc3vt1RdWLk,1083
|
|
161
161
|
autogen/agentchat/realtime/experimental/realtime_observer.py,sha256=nTouVj5-il0q2_P2LTpyb4pnHqyfwP5MJh_QmMJF3e8,3061
|
|
162
|
-
autogen/agentchat/realtime/experimental/realtime_swarm.py,sha256=
|
|
162
|
+
autogen/agentchat/realtime/experimental/realtime_swarm.py,sha256=sy7Vwt7Ztuu4LMnZQ_RjASY0KWUNARJev41INXqY7pc,17880
|
|
163
163
|
autogen/agentchat/realtime/experimental/websockets.py,sha256=bj9b5eq80L3KlGWPP6nn7uyfT_Z47kQqtIRbQkeE5SI,667
|
|
164
164
|
autogen/agentchat/realtime/experimental/audio_adapters/__init__.py,sha256=rd0pEy91LYq0JMvIk8Fv7ZKIQLK7oZbVdgVAwNZDCmQ,315
|
|
165
165
|
autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py,sha256=-O10rpqPKZKxZO58rQOxPnwECe-RQJoSUTU_K8i0A98,6110
|
|
@@ -212,7 +212,7 @@ autogen/cache/in_memory_cache.py,sha256=qeECs4UnN5FzuCSgiF-Ma11eLGSjJBgGlqLraWhD
|
|
|
212
212
|
autogen/cache/redis_cache.py,sha256=XqUVObZRbD_kDL5vDD6H6xu_U-xcL8qKmsUq2Ix8xVo,4248
|
|
213
213
|
autogen/coding/__init__.py,sha256=fN8UCm3RzJZ4OFMwlp3a0jic0ZzOege6TNVAk5t6J0U,825
|
|
214
214
|
autogen/coding/base.py,sha256=hj70EwB-b0MVzHXACw-Oj3CiHo-GUC5kGZGniDTMOnw,3801
|
|
215
|
-
autogen/coding/docker_commandline_code_executor.py,sha256=
|
|
215
|
+
autogen/coding/docker_commandline_code_executor.py,sha256=8sr6bBpk1JtWEb1YQNgmj5z_n2mL_-cAP3EzSrn6SLU,10872
|
|
216
216
|
autogen/coding/factory.py,sha256=bC3kkIBNExAPt8KZvCQ1PsLpyJWLq1jpw1JcvIbKR04,1978
|
|
217
217
|
autogen/coding/func_with_reqs.py,sha256=dPcjRs4gOjoVO3m5cQYWtFm7FKO94gBYPwr3O7gVj7g,6372
|
|
218
218
|
autogen/coding/local_commandline_code_executor.py,sha256=XKkT141IYZf_HngNJjMcJ7uZXe3k056wMdXZC2hfAts,16671
|
|
@@ -226,6 +226,12 @@ autogen/coding/jupyter/import_utils.py,sha256=7yzEAYmNmJsODsqz3XCNOORKfvp0t6xnaf
|
|
|
226
226
|
autogen/coding/jupyter/jupyter_client.py,sha256=ROXAWOKG_EJ_oFNuyqUd_3uOBPUTRoTh6_-8bSpXBdU,8674
|
|
227
227
|
autogen/coding/jupyter/jupyter_code_executor.py,sha256=Z2vZvou6QzpMBg0IgOzVRoCADswd15mvfkktIjGhUMY,6374
|
|
228
228
|
autogen/coding/jupyter/local_jupyter_server.py,sha256=7b8yi5qK8ms2e5-PRCrzmXKGp1iC5KgpMU8xiqQ9u8o,6589
|
|
229
|
+
autogen/environments/__init__.py,sha256=SNh4NFcBgySNW--H1YUZPEf7F7QLpBQA3Qc8EvyM-gk,488
|
|
230
|
+
autogen/environments/docker_python_environment.py,sha256=oKdtr2IN2UDoYi8V_Qjvut5VGfwUNzNoj8J1aMRP9GU,15312
|
|
231
|
+
autogen/environments/python_environment.py,sha256=CXY0h3SWJPYSZtcR_nqY5VmVvGItIJwAnBqSx7YvECg,4265
|
|
232
|
+
autogen/environments/system_python_environment.py,sha256=jk1U4CSDPsF1igqdv-GQOKsH_NTxitRzN35-UWng2e8,3081
|
|
233
|
+
autogen/environments/venv_python_environment.py,sha256=i7dgd8lFuxfZwkysnFXuOn_B26CFYCzNHHBfCtExLkk,10076
|
|
234
|
+
autogen/environments/working_directory.py,sha256=MevUpN1lzmJLEKM_o9JHZod9cy9pQgI5AsE0ic6fvrE,2505
|
|
229
235
|
autogen/events/__init__.py,sha256=XwCA6Rsq9AyjgeecGuiwHcAvDQMmKXgGomw5iLDIU5Q,358
|
|
230
236
|
autogen/events/agent_events.py,sha256=LSCOMwA-nlBSboE5jOh-Da2Pm7hghiwjfzYTz_bS30k,31112
|
|
231
237
|
autogen/events/base_event.py,sha256=5K1wzDBAio9wLxahErSvE0-UbFfMuSTxBp6EI8SbPGU,3475
|
|
@@ -281,7 +287,7 @@ autogen/mcp/helpers.py,sha256=J5_J6n3jMJUEJH5K8k9BeUb6ymQgRUI0hC1gbY7rlwM,1533
|
|
|
281
287
|
autogen/mcp/mcp_client.py,sha256=7c_lHgBJEs77TFYjLcTlVrEu_0z4EafPPY3PgteY87c,7400
|
|
282
288
|
autogen/mcp/mcp_proxy/__init__.py,sha256=3HTU-TqHLk4XSXeBV1UFd9XkQ1B0yOuXXyGseXvDVec,518
|
|
283
289
|
autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py,sha256=dx-w2tGVMnh8pzY2NuXlMD7oIF7_5Gvc5oSbHIySEpc,2110
|
|
284
|
-
autogen/mcp/mcp_proxy/mcp_proxy.py,sha256=
|
|
290
|
+
autogen/mcp/mcp_proxy/mcp_proxy.py,sha256=1eWwZatPgPxqkhcbEFCW-ErmkrWUF9F_cLkFWVtLkug,22171
|
|
285
291
|
autogen/mcp/mcp_proxy/operation_grouping.py,sha256=1n4o5qhkQd2hVr7OaOMsRhInDveDGkCozLudsBVusC4,6349
|
|
286
292
|
autogen/mcp/mcp_proxy/operation_renaming.py,sha256=G5J4VdxUAwSvOo-DsqIUbCfV9TnH3riaHLI6IpctDF8,3956
|
|
287
293
|
autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py,sha256=vBr8P890nsFvK4iuFicrdNskooHlBQeTQ6jbb5kcPAk,3490
|
|
@@ -296,7 +302,7 @@ autogen/oai/__init__.py,sha256=BIwnV6wtHmKgIM4IUdymfPfpdNqos5P7BfRv-7_QL9A,1680
|
|
|
296
302
|
autogen/oai/anthropic.py,sha256=AK_Bbcc5I-PqAvoiwB0LaO1ZLugh_wQNAOIFcj7eTtk,29174
|
|
297
303
|
autogen/oai/bedrock.py,sha256=8AYWZVsDkJS2HmQ0ggoUqlKV_a4H_ON8rks4VW2Jrxk,25719
|
|
298
304
|
autogen/oai/cerebras.py,sha256=8hiSBq88l2yTXUJPV7AvGXRCtwvW0Y9hIYUnYK2S2os,12462
|
|
299
|
-
autogen/oai/client.py,sha256=
|
|
305
|
+
autogen/oai/client.py,sha256=ZJfax-XdV92xfmIvn-qmJhSwjP-MusU4oDc9_88o-1k,68206
|
|
300
306
|
autogen/oai/client_utils.py,sha256=lVbHyff7OnpdM-tXskC23xLdFccj2AalTdWA4DxzxS4,7543
|
|
301
307
|
autogen/oai/cohere.py,sha256=pRcQWjbzKbZ1RfC1vk9WGjgndwjHbIaOVoKEYdV2L6c,19421
|
|
302
308
|
autogen/oai/gemini.py,sha256=bc_RQwtoGi7DnwQwPie7ZdvsqpD1AjYwzjVwnIKP39U,43168
|
|
@@ -304,6 +310,7 @@ autogen/oai/gemini_types.py,sha256=i4wT8ytD2cO9nCUWm2FKLKVVxyd31wMhOpnrvnIiKIc,5
|
|
|
304
310
|
autogen/oai/groq.py,sha256=pQWtaAY_AjT30XKbZNHXDzWsawBys3yFWlfy6K4Nqr8,12431
|
|
305
311
|
autogen/oai/mistral.py,sha256=SlOYPdnNLHuTEHBGCzsemG9sLEgphdUukRurERdMsvI,12677
|
|
306
312
|
autogen/oai/ollama.py,sha256=t0fIgDCoIfsQZ3hhpseam5N-fbpI7-fw82bG55mA8nU,29103
|
|
313
|
+
autogen/oai/openai_responses.py,sha256=W847OTGzlDbII8sYWIXTOc9NEPAM59XmLWvcYbC6bRo,17811
|
|
307
314
|
autogen/oai/openai_utils.py,sha256=4kEu50WeTGGG2uh1fOeMxRIZkEoov-YkkTgx2n5DhkM,38131
|
|
308
315
|
autogen/oai/together.py,sha256=Sj4LOk9RrBG3Bb5IVsrjBYz-hDECCyCgofsCdtk6PSM,14867
|
|
309
316
|
autogen/oai/oai_models/__init__.py,sha256=cILDaaCCvSC3aAX85iLwE1RCpNEokA9925Zse5hX2K4,549
|
|
@@ -322,9 +329,11 @@ autogen/tools/toolkit.py,sha256=1tOmTGJ96RhkJrrtAViKUyEcwacA6ztoIbYbnf8NgTU,2558
|
|
|
322
329
|
autogen/tools/contrib/__init__.py,sha256=DWEjPK6xCR2ihAXXdquQZmiuqRLA3Pqb8QV8W1RtS3k,202
|
|
323
330
|
autogen/tools/contrib/time/__init__.py,sha256=dplie5aBJZ8VoKy6EKcQMLTtSgcCkNDYzpdsC2I0YWk,195
|
|
324
331
|
autogen/tools/contrib/time/time.py,sha256=tPi49vOUwfvujbYA-zS00CWcLW-y18CPyQ1gnJG6iRg,1271
|
|
325
|
-
autogen/tools/experimental/__init__.py,sha256=
|
|
332
|
+
autogen/tools/experimental/__init__.py,sha256=OPJlrK_tJCPbeBT-qsIWwJBdLuaCMskpshduA8Haj7E,1671
|
|
326
333
|
autogen/tools/experimental/browser_use/__init__.py,sha256=kfxCajXcVMDH6CZq-lWh2p8PKxOwT9yjC_Za0jr4zUg,290
|
|
327
334
|
autogen/tools/experimental/browser_use/browser_use.py,sha256=KfU4MI_BWaHepv0bDMf9HTDUaHTJThuBJI8R_BPpjmg,5561
|
|
335
|
+
autogen/tools/experimental/code_execution/__init__.py,sha256=KFUku2awEgsnkSa48t2iMMPC-QCxpqAG7lb8fB4Fr-c,242
|
|
336
|
+
autogen/tools/experimental/code_execution/python_code_execution.py,sha256=Fj1Wxsn6z0HuXWpqa-ahedkCtyPYBShUnbiyvSw_mus,3705
|
|
328
337
|
autogen/tools/experimental/crawl4ai/__init__.py,sha256=UjFJLSZ9P5xT6WCV0RDPtwt4MHuwPdK90TU7ByXhLWs,207
|
|
329
338
|
autogen/tools/experimental/crawl4ai/crawl4ai.py,sha256=MsOLtbPHRpRrCnRJPQVVVNwmsBcgsWLSHNXDOF-47ws,6088
|
|
330
339
|
autogen/tools/experimental/deep_research/__init__.py,sha256=9SFcDEj2OHxNSlXP11lf1uHENlfUeO47ROcOSD9GCDs,220
|
|
@@ -405,8 +414,8 @@ autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py,sha256=
|
|
|
405
414
|
autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py,sha256=iqgpFJdyBHPPNCqkehSIbeuV8Rabr2eDMilT23Wx7PI,1687
|
|
406
415
|
autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py,sha256=-6T5r6Er4mONPldRxv3F9tLoE7Og3qmeSeTC7Du_tTg,596
|
|
407
416
|
autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py,sha256=Xig7K3A3DRnbv-UXfyo5bybGZUQYAQsltthfTYW5eV8,509
|
|
408
|
-
ag2-0.9.
|
|
409
|
-
ag2-0.9.
|
|
410
|
-
ag2-0.9.
|
|
411
|
-
ag2-0.9.
|
|
412
|
-
ag2-0.9.
|
|
417
|
+
ag2-0.9.6.dist-info/METADATA,sha256=Syw0Wb_vvJEdWJ52b0UjycmcDQj0PJtCt8ZRUa-lLeM,35268
|
|
418
|
+
ag2-0.9.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
419
|
+
ag2-0.9.6.dist-info/licenses/LICENSE,sha256=GEFQVNayAR-S_rQD5l8hPdgvgyktVdy4Bx5-v90IfRI,11384
|
|
420
|
+
ag2-0.9.6.dist-info/licenses/NOTICE.md,sha256=07iCPQGbth4pQrgkSgZinJGT5nXddkZ6_MGYcBd2oiY,1134
|
|
421
|
+
ag2-0.9.6.dist-info/RECORD,,
|
autogen/code_utils.py
CHANGED
|
@@ -72,18 +72,20 @@ def content_str(content: Union[str, list[Union[UserMessageTextContentPart, UserM
|
|
|
72
72
|
if not isinstance(content, list):
|
|
73
73
|
raise TypeError(f"content must be None, str, or list, but got {type(content)}")
|
|
74
74
|
|
|
75
|
-
rst =
|
|
75
|
+
rst = []
|
|
76
76
|
for item in content:
|
|
77
77
|
if not isinstance(item, dict):
|
|
78
78
|
raise TypeError("Wrong content format: every element should be dict if the content is a list.")
|
|
79
79
|
assert "type" in item, "Wrong content format. Missing 'type' key in content's dict."
|
|
80
|
-
if item["type"]
|
|
81
|
-
rst
|
|
82
|
-
elif item["type"]
|
|
83
|
-
rst
|
|
80
|
+
if item["type"] in ["text", "input_text"]:
|
|
81
|
+
rst.append(item["text"])
|
|
82
|
+
elif item["type"] in ["image_url", "input_image"]:
|
|
83
|
+
rst.append("<image>")
|
|
84
|
+
elif item["type"] in ["function", "tool_call", "tool_calls"]:
|
|
85
|
+
rst.append("<function>" if "name" not in item else f"<function: {item['name']}>")
|
|
84
86
|
else:
|
|
85
87
|
raise ValueError(f"Wrong content format: unknown type {item['type']} within the content")
|
|
86
|
-
return rst
|
|
88
|
+
return "\n".join(rst)
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
def infer_lang(code: str) -> str:
|
|
@@ -71,6 +71,8 @@ class DockerCommandLineCodeExecutor(CodeExecutor):
|
|
|
71
71
|
auto_remove: bool = True,
|
|
72
72
|
stop_container: bool = True,
|
|
73
73
|
execution_policies: Optional[dict[str, bool]] = None,
|
|
74
|
+
*,
|
|
75
|
+
container_create_kwargs: Optional[dict[str, Any]] = None,
|
|
74
76
|
):
|
|
75
77
|
"""(Experimental) A code executor class that executes code through
|
|
76
78
|
a command line environment in a Docker container.
|
|
@@ -98,6 +100,11 @@ class DockerCommandLineCodeExecutor(CodeExecutor):
|
|
|
98
100
|
whether code in that language should be executed. True means code in that language
|
|
99
101
|
will be executed, False means it will only be saved to a file. This overrides the
|
|
100
102
|
default execution policies. Defaults to None.
|
|
103
|
+
container_create_kwargs: Optional dict forwarded verbatim to
|
|
104
|
+
"docker.client.containers.create". Use it to set advanced Docker
|
|
105
|
+
options (environment variables, GPU device_requests, port mappings, etc.).
|
|
106
|
+
Values here override the class defaults when keys collide. Defaults to None.
|
|
107
|
+
|
|
101
108
|
|
|
102
109
|
Raises:
|
|
103
110
|
ValueError: On argument error, or if the container fails to start.
|
|
@@ -128,16 +135,29 @@ class DockerCommandLineCodeExecutor(CodeExecutor):
|
|
|
128
135
|
if container_name is None:
|
|
129
136
|
container_name = f"autogen-code-exec-{uuid.uuid4()}"
|
|
130
137
|
|
|
138
|
+
# build kwargs for docker.create
|
|
139
|
+
base_kwargs: dict[str, Any] = {
|
|
140
|
+
"image": image,
|
|
141
|
+
"name": container_name,
|
|
142
|
+
"entrypoint": "/bin/sh",
|
|
143
|
+
"tty": True,
|
|
144
|
+
"auto_remove": auto_remove,
|
|
145
|
+
"volumes": {str(bind_dir.resolve()): {"bind": "/workspace", "mode": "rw"}},
|
|
146
|
+
"working_dir": "/workspace",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if container_create_kwargs:
|
|
150
|
+
for k in ("entrypoint", "volumes", "working_dir", "tty"):
|
|
151
|
+
if k in container_create_kwargs:
|
|
152
|
+
logging.warning(
|
|
153
|
+
"DockerCommandLineCodeExecutor: overriding default %s=%s",
|
|
154
|
+
k,
|
|
155
|
+
container_create_kwargs[k],
|
|
156
|
+
)
|
|
157
|
+
base_kwargs.update(container_create_kwargs)
|
|
158
|
+
|
|
131
159
|
# Start a container from the image, read to exec commands later
|
|
132
|
-
self._container = client.containers.create(
|
|
133
|
-
image,
|
|
134
|
-
name=container_name,
|
|
135
|
-
entrypoint="/bin/sh",
|
|
136
|
-
tty=True,
|
|
137
|
-
auto_remove=auto_remove,
|
|
138
|
-
volumes={str(bind_dir.resolve()): {"bind": "/workspace", "mode": "rw"}},
|
|
139
|
-
working_dir="/workspace",
|
|
140
|
-
)
|
|
160
|
+
self._container = client.containers.create(**base_kwargs)
|
|
141
161
|
self._container.start()
|
|
142
162
|
|
|
143
163
|
_wait_for_ready(self._container)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from .docker_python_environment import DockerPythonEnvironment
|
|
6
|
+
from .system_python_environment import SystemPythonEnvironment
|
|
7
|
+
from .venv_python_environment import VenvPythonEnvironment
|
|
8
|
+
from .working_directory import WorkingDirectory
|
|
9
|
+
|
|
10
|
+
__all__ = ["DockerPythonEnvironment", "SystemPythonEnvironment", "VenvPythonEnvironment", "WorkingDirectory"]
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
from asyncer import asyncify
|
|
14
|
+
|
|
15
|
+
from .python_environment import PythonEnvironment
|
|
16
|
+
|
|
17
|
+
__all__ = ["DockerPythonEnvironment"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DockerPythonEnvironment(PythonEnvironment):
|
|
21
|
+
"""A Python environment using Docker containers for isolated execution."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
image: str = "python:3.11-slim",
|
|
26
|
+
container_name_prefix: str = "ag2_docker_env_",
|
|
27
|
+
volumes: Optional[dict[str, str]] = None,
|
|
28
|
+
environment: Optional[dict[str, str]] = None,
|
|
29
|
+
network: Optional[str] = None,
|
|
30
|
+
pip_packages: Optional[list[str]] = None,
|
|
31
|
+
requirements_file: Optional[str] = None,
|
|
32
|
+
dockerfile: Optional[str] = None,
|
|
33
|
+
build_args: Optional[dict[str, str]] = None,
|
|
34
|
+
cleanup_container: bool = True,
|
|
35
|
+
keep_container_running: bool = False,
|
|
36
|
+
container_startup_timeout: int = 30,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Initialize a Docker Python environment.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
image: Docker image to use (ignored if dockerfile is provided)
|
|
43
|
+
container_name_prefix: Prefix for container names
|
|
44
|
+
volumes: Dictionary mapping host paths to container paths for mounting
|
|
45
|
+
environment: Dictionary of environment variables to set in the container
|
|
46
|
+
network: Docker network to attach the container to
|
|
47
|
+
pip_packages: List of pip packages to install in the container
|
|
48
|
+
requirements_file: Path to requirements.txt file to install in the container
|
|
49
|
+
dockerfile: Optional path to a Dockerfile to build and use instead of pulling an image
|
|
50
|
+
build_args: Optional build arguments for the Dockerfile
|
|
51
|
+
cleanup_container: Whether to remove the container after use
|
|
52
|
+
keep_container_running: Whether to keep the container running after execution
|
|
53
|
+
container_startup_timeout: Timeout in seconds for container startup
|
|
54
|
+
"""
|
|
55
|
+
self.image = image
|
|
56
|
+
self.container_name_prefix = container_name_prefix
|
|
57
|
+
self.volumes = volumes or {}
|
|
58
|
+
self.environment = environment or {}
|
|
59
|
+
self.network = network
|
|
60
|
+
self.pip_packages = pip_packages or []
|
|
61
|
+
self.requirements_file = requirements_file
|
|
62
|
+
self.dockerfile = dockerfile
|
|
63
|
+
self.build_args = build_args or {}
|
|
64
|
+
self.cleanup_container = cleanup_container
|
|
65
|
+
self.keep_container_running = keep_container_running
|
|
66
|
+
self.container_startup_timeout = container_startup_timeout
|
|
67
|
+
|
|
68
|
+
# Internal state
|
|
69
|
+
self._container_id = None
|
|
70
|
+
self._container_name = None
|
|
71
|
+
self._custom_image_name = None
|
|
72
|
+
self._temp_dir = None
|
|
73
|
+
|
|
74
|
+
super().__init__()
|
|
75
|
+
|
|
76
|
+
def _setup_environment(self) -> None:
|
|
77
|
+
"""Set up the Docker environment."""
|
|
78
|
+
# Verify Docker is installed and accessible
|
|
79
|
+
try:
|
|
80
|
+
result = subprocess.run(["docker", "--version"], capture_output=True, text=True, check=True)
|
|
81
|
+
logging.info(f"Docker version: {result.stdout.strip()}")
|
|
82
|
+
except (subprocess.SubprocessError, FileNotFoundError) as e:
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
"Docker not found or not accessible. Please ensure Docker is installed and running."
|
|
85
|
+
) from e
|
|
86
|
+
|
|
87
|
+
# Create a temporary directory for file operations
|
|
88
|
+
self._temp_dir = tempfile.mkdtemp(prefix="ag2_docker_")
|
|
89
|
+
|
|
90
|
+
# Generate a unique container name
|
|
91
|
+
self._container_name = f"{self.container_name_prefix}{uuid.uuid4().hex[:8]}"
|
|
92
|
+
|
|
93
|
+
# Build custom image if Dockerfile is provided
|
|
94
|
+
if self.dockerfile:
|
|
95
|
+
self._build_custom_image()
|
|
96
|
+
else:
|
|
97
|
+
# Pull the specified image
|
|
98
|
+
try:
|
|
99
|
+
subprocess.run(
|
|
100
|
+
["docker", "pull", self.image],
|
|
101
|
+
check=True,
|
|
102
|
+
stdout=subprocess.PIPE,
|
|
103
|
+
stderr=subprocess.PIPE,
|
|
104
|
+
text=True,
|
|
105
|
+
)
|
|
106
|
+
logging.info(f"Pulled Docker image: {self.image}")
|
|
107
|
+
except subprocess.CalledProcessError as e:
|
|
108
|
+
raise RuntimeError(f"Failed to pull Docker image: {e.stderr}") from e
|
|
109
|
+
|
|
110
|
+
# Start the container
|
|
111
|
+
self._start_container()
|
|
112
|
+
|
|
113
|
+
def _build_custom_image(self) -> None:
|
|
114
|
+
"""Build a custom Docker image from the provided Dockerfile."""
|
|
115
|
+
if not os.path.exists(self.dockerfile):
|
|
116
|
+
raise RuntimeError(f"Dockerfile not found at: {self.dockerfile}")
|
|
117
|
+
|
|
118
|
+
# Create a unique image name
|
|
119
|
+
self._custom_image_name = f"ag2-custom-python-{uuid.uuid4().hex[:8]}"
|
|
120
|
+
|
|
121
|
+
# Build command
|
|
122
|
+
build_cmd = ["docker", "build", "-t", self._custom_image_name]
|
|
123
|
+
|
|
124
|
+
# Add build args
|
|
125
|
+
for arg_name, arg_value in self.build_args.items():
|
|
126
|
+
build_cmd.extend(["--build-arg", f"{arg_name}={arg_value}"])
|
|
127
|
+
|
|
128
|
+
# Add Dockerfile path
|
|
129
|
+
build_cmd.extend(["-f", self.dockerfile, os.path.dirname(self.dockerfile)])
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
logging.info(f"Building custom Docker image: {self._custom_image_name}")
|
|
133
|
+
_ = subprocess.run(
|
|
134
|
+
build_cmd,
|
|
135
|
+
check=True,
|
|
136
|
+
stdout=subprocess.PIPE,
|
|
137
|
+
stderr=subprocess.PIPE,
|
|
138
|
+
text=True,
|
|
139
|
+
)
|
|
140
|
+
logging.info(f"Built custom Docker image: {self._custom_image_name}")
|
|
141
|
+
except subprocess.CalledProcessError as e:
|
|
142
|
+
raise RuntimeError(f"Failed to build Docker image: {e.stderr}") from e
|
|
143
|
+
|
|
144
|
+
# Use the custom image
|
|
145
|
+
self.image = self._custom_image_name
|
|
146
|
+
|
|
147
|
+
def _start_container(self) -> None:
|
|
148
|
+
"""Start the Docker container."""
|
|
149
|
+
# Basic container run command
|
|
150
|
+
run_cmd = ["docker", "run", "--name", self._container_name]
|
|
151
|
+
|
|
152
|
+
# Add detached mode flag to run container in background
|
|
153
|
+
run_cmd.append("-d")
|
|
154
|
+
|
|
155
|
+
# Add network if specified
|
|
156
|
+
if self.network:
|
|
157
|
+
run_cmd.extend(["--network", self.network])
|
|
158
|
+
|
|
159
|
+
# Add environment variables
|
|
160
|
+
for env_name, env_value in self.environment.items():
|
|
161
|
+
run_cmd.extend(["-e", f"{env_name}={env_value}"])
|
|
162
|
+
|
|
163
|
+
# Add volume mounts including temp directory
|
|
164
|
+
work_dir_mount = f"{self._temp_dir}:/workspace"
|
|
165
|
+
run_cmd.extend(["-v", work_dir_mount])
|
|
166
|
+
|
|
167
|
+
for host_path, container_path in self.volumes.items():
|
|
168
|
+
run_cmd.extend(["-v", f"{host_path}:{container_path}"])
|
|
169
|
+
|
|
170
|
+
# Set workspace as working directory
|
|
171
|
+
run_cmd.extend(["-w", "/workspace"])
|
|
172
|
+
|
|
173
|
+
# Add tty to keep container running
|
|
174
|
+
run_cmd.append("-t")
|
|
175
|
+
|
|
176
|
+
# Add image name
|
|
177
|
+
run_cmd.append(self.image)
|
|
178
|
+
|
|
179
|
+
# Initial command to keep container running
|
|
180
|
+
run_cmd.extend(["tail", "-f", "/dev/null"])
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Start the container
|
|
184
|
+
logging.info(f"Starting Docker container: {self._container_name}")
|
|
185
|
+
result = subprocess.run(
|
|
186
|
+
run_cmd,
|
|
187
|
+
check=True,
|
|
188
|
+
stdout=subprocess.PIPE,
|
|
189
|
+
stderr=subprocess.PIPE,
|
|
190
|
+
text=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Get container ID
|
|
194
|
+
self._container_id = result.stdout.strip()
|
|
195
|
+
logging.info(f"Started Docker container: {self._container_name} ({self._container_id})")
|
|
196
|
+
|
|
197
|
+
# Install pip packages if specified
|
|
198
|
+
if self.pip_packages or self.requirements_file:
|
|
199
|
+
self._install_packages()
|
|
200
|
+
|
|
201
|
+
except subprocess.CalledProcessError as e:
|
|
202
|
+
raise RuntimeError(f"Failed to start Docker container: {e.stderr}") from e
|
|
203
|
+
|
|
204
|
+
def _install_packages(self) -> None:
|
|
205
|
+
"""Install Python packages in the running container."""
|
|
206
|
+
# Install pip packages
|
|
207
|
+
if self.pip_packages:
|
|
208
|
+
packages_str = " ".join(self.pip_packages)
|
|
209
|
+
try:
|
|
210
|
+
logging.info(f"Installing pip packages: {packages_str}")
|
|
211
|
+
_ = subprocess.run(
|
|
212
|
+
["docker", "exec", self._container_name, "pip", "install", "--no-cache-dir"] + self.pip_packages,
|
|
213
|
+
check=True,
|
|
214
|
+
stdout=subprocess.PIPE,
|
|
215
|
+
stderr=subprocess.PIPE,
|
|
216
|
+
text=True,
|
|
217
|
+
)
|
|
218
|
+
logging.info("Successfully installed pip packages")
|
|
219
|
+
except subprocess.CalledProcessError as e:
|
|
220
|
+
logging.warning(f"Failed to install pip packages: {e.stderr}")
|
|
221
|
+
|
|
222
|
+
# Install from requirements file
|
|
223
|
+
if self.requirements_file:
|
|
224
|
+
if os.path.exists(self.requirements_file):
|
|
225
|
+
# Copy requirements file to temp directory
|
|
226
|
+
req_filename = os.path.basename(self.requirements_file)
|
|
227
|
+
temp_req_path = os.path.join(self._temp_dir, req_filename)
|
|
228
|
+
shutil.copy(self.requirements_file, temp_req_path)
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
logging.info(f"Installing requirements from: {req_filename}")
|
|
232
|
+
_ = subprocess.run(
|
|
233
|
+
[
|
|
234
|
+
"docker",
|
|
235
|
+
"exec",
|
|
236
|
+
self._container_name,
|
|
237
|
+
"pip",
|
|
238
|
+
"install",
|
|
239
|
+
"--no-cache-dir",
|
|
240
|
+
"-r",
|
|
241
|
+
f"/workspace/{req_filename}",
|
|
242
|
+
],
|
|
243
|
+
check=True,
|
|
244
|
+
stdout=subprocess.PIPE,
|
|
245
|
+
stderr=subprocess.PIPE,
|
|
246
|
+
text=True,
|
|
247
|
+
)
|
|
248
|
+
logging.info("Successfully installed requirements")
|
|
249
|
+
except subprocess.CalledProcessError as e:
|
|
250
|
+
logging.warning(f"Failed to install requirements: {e.stderr}")
|
|
251
|
+
else:
|
|
252
|
+
logging.warning(f"Requirements file not found: {self.requirements_file}")
|
|
253
|
+
|
|
254
|
+
def _cleanup_environment(self) -> None:
|
|
255
|
+
"""Clean up the Docker environment."""
|
|
256
|
+
if self._container_id:
|
|
257
|
+
# Stop the container if it's running and we want to clean it up
|
|
258
|
+
if not self.keep_container_running:
|
|
259
|
+
try:
|
|
260
|
+
logging.info(f"Stopping Docker container: {self._container_name}")
|
|
261
|
+
subprocess.run(
|
|
262
|
+
["docker", "stop", self._container_name],
|
|
263
|
+
check=True,
|
|
264
|
+
stdout=subprocess.PIPE,
|
|
265
|
+
stderr=subprocess.PIPE,
|
|
266
|
+
text=True,
|
|
267
|
+
)
|
|
268
|
+
except subprocess.CalledProcessError:
|
|
269
|
+
logging.warning(f"Failed to stop Docker container: {self._container_name}")
|
|
270
|
+
|
|
271
|
+
# Remove the container if cleanup is enabled
|
|
272
|
+
if self.cleanup_container and not self.keep_container_running:
|
|
273
|
+
try:
|
|
274
|
+
logging.info(f"Removing Docker container: {self._container_name}")
|
|
275
|
+
subprocess.run(
|
|
276
|
+
["docker", "rm", "-f", self._container_name],
|
|
277
|
+
check=True,
|
|
278
|
+
stdout=subprocess.PIPE,
|
|
279
|
+
stderr=subprocess.PIPE,
|
|
280
|
+
text=True,
|
|
281
|
+
)
|
|
282
|
+
except subprocess.CalledProcessError:
|
|
283
|
+
logging.warning(f"Failed to remove Docker container: {self._container_name}")
|
|
284
|
+
|
|
285
|
+
# Remove the custom image if it was created
|
|
286
|
+
if self._custom_image_name and self.cleanup_container:
|
|
287
|
+
try:
|
|
288
|
+
logging.info(f"Removing custom Docker image: {self._custom_image_name}")
|
|
289
|
+
subprocess.run(
|
|
290
|
+
["docker", "rmi", self._custom_image_name],
|
|
291
|
+
check=True,
|
|
292
|
+
stdout=subprocess.PIPE,
|
|
293
|
+
stderr=subprocess.PIPE,
|
|
294
|
+
text=True,
|
|
295
|
+
)
|
|
296
|
+
except subprocess.CalledProcessError:
|
|
297
|
+
logging.warning(f"Failed to remove custom Docker image: {self._custom_image_name}")
|
|
298
|
+
|
|
299
|
+
# Clean up the temporary directory
|
|
300
|
+
if self._temp_dir and os.path.exists(self._temp_dir):
|
|
301
|
+
try:
|
|
302
|
+
shutil.rmtree(self._temp_dir)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logging.warning(f"Failed to remove temporary directory: {e}")
|
|
305
|
+
|
|
306
|
+
def get_executable(self) -> str:
|
|
307
|
+
"""Get the path to the Python executable in the Docker container."""
|
|
308
|
+
# This is a virtual path in the container
|
|
309
|
+
return "python"
|
|
310
|
+
|
|
311
|
+
async def execute_code(self, code: str, script_path: str, timeout: int = 30) -> dict[str, Any]:
|
|
312
|
+
"""Execute code in the Docker container."""
|
|
313
|
+
# Ensure the container is running
|
|
314
|
+
if not self._container_id:
|
|
315
|
+
return {"success": False, "error": "Docker container not started"}
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
# Calculate the relative path within the temp directory
|
|
319
|
+
if os.path.isabs(script_path):
|
|
320
|
+
rel_path = os.path.basename(script_path)
|
|
321
|
+
host_script_path = os.path.join(self._temp_dir, rel_path)
|
|
322
|
+
else:
|
|
323
|
+
rel_path = script_path
|
|
324
|
+
host_script_path = os.path.join(self._temp_dir, rel_path)
|
|
325
|
+
|
|
326
|
+
# Ensure the directory for the script exists
|
|
327
|
+
script_dir = os.path.dirname(host_script_path)
|
|
328
|
+
if script_dir:
|
|
329
|
+
os.makedirs(script_dir, exist_ok=True)
|
|
330
|
+
|
|
331
|
+
# Write the code to the script file on the host
|
|
332
|
+
await asyncify(self._write_to_file)(host_script_path, code)
|
|
333
|
+
|
|
334
|
+
# Path to the script in the container
|
|
335
|
+
container_script_path = f"/workspace/{rel_path}"
|
|
336
|
+
|
|
337
|
+
# Execute the script in the container
|
|
338
|
+
exec_cmd = ["docker", "exec", self._container_name, "python", container_script_path]
|
|
339
|
+
|
|
340
|
+
# Run the command with a timeout
|
|
341
|
+
result = await asyncify(self._run_subprocess_with_timeout)(exec_cmd, timeout)
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
"success": result[0],
|
|
345
|
+
"stdout": result[1],
|
|
346
|
+
"stderr": result[2],
|
|
347
|
+
"returncode": result[3] if result[0] else 1,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
return {"success": False, "error": f"Execution error: {str(e)}"}
|
|
352
|
+
|
|
353
|
+
def _run_subprocess_with_timeout(self, cmd: list[str], timeout: int) -> Tuple[bool, str, str, int]:
|
|
354
|
+
"""
|
|
355
|
+
Run a subprocess with timeout and return status, stdout, stderr, and return code.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
cmd: Command to run as a list of strings
|
|
359
|
+
timeout: Maximum execution time in seconds
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Tuple of (success, stdout, stderr, return_code)
|
|
363
|
+
"""
|
|
364
|
+
try:
|
|
365
|
+
result = subprocess.run(
|
|
366
|
+
cmd,
|
|
367
|
+
capture_output=True,
|
|
368
|
+
text=True,
|
|
369
|
+
timeout=timeout,
|
|
370
|
+
)
|
|
371
|
+
return (result.returncode == 0, result.stdout, result.stderr, result.returncode)
|
|
372
|
+
except subprocess.TimeoutExpired:
|
|
373
|
+
return (False, "", f"Execution timed out after {timeout} seconds", -1)
|
|
374
|
+
except Exception as e:
|
|
375
|
+
return (False, "", str(e), -1)
|