ag2 0.9.5__py3-none-any.whl → 0.9.7__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.5.dist-info → ag2-0.9.7.dist-info}/METADATA +1 -1
- {ag2-0.9.5.dist-info → ag2-0.9.7.dist-info}/RECORD +25 -17
- autogen/agentchat/conversable_agent.py +19 -5
- 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/logger/sqlite_logger.py +1 -1
- autogen/mcp/mcp_proxy/mcp_proxy.py +4 -4
- autogen/oai/client.py +34 -7
- autogen/oai/gemini.py +1 -0
- autogen/oai/ollama.py +3 -1
- autogen/oai/openai_utils.py +111 -47
- 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/tools/tool.py +1 -2
- autogen/version.py +1 -1
- {ag2-0.9.5.dist-info → ag2-0.9.7.dist-info}/WHEEL +0 -0
- {ag2-0.9.5.dist-info → ag2-0.9.7.dist-info}/licenses/LICENSE +0 -0
- {ag2-0.9.5.dist-info → ag2-0.9.7.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -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=aw3BBOt8HZBovklvFoq90ok-Yb3gkEvbuckRDabaBTs,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
|
|
@@ -25,7 +25,7 @@ autogen/agentchat/__init__.py,sha256=d5jPpXeavynP9eh5uMIgJKbK3-3tywJBa6l3L9X4l34
|
|
|
25
25
|
autogen/agentchat/agent.py,sha256=HePNJ5BXJTcZtaD2a8CoTeHAoLUUy3scM6Ihm-NsSWk,5828
|
|
26
26
|
autogen/agentchat/assistant_agent.py,sha256=XTJvD66r4qYkdNAJJLr1CC-wTYFJWvhmD5_G0WbbX2I,5741
|
|
27
27
|
autogen/agentchat/chat.py,sha256=6Gx2t1-Xa8kP6ZoUihHBNGOqNlrGhhqLPKrckL0n-RI,14003
|
|
28
|
-
autogen/agentchat/conversable_agent.py,sha256
|
|
28
|
+
autogen/agentchat/conversable_agent.py,sha256=v-vX9HkzJzrOXqzLCxXCow2aND0sjcbqR7h4ECKgNAM,193375
|
|
29
29
|
autogen/agentchat/groupchat.py,sha256=vdK8zUX2CG8fR5pkPGWX7Zn7DFwLvld0bpaRv3vOBEo,89471
|
|
30
30
|
autogen/agentchat/user_proxy_agent.py,sha256=-gbDblRvE09FGuVB6-y5ZT9Cpvv--rM3FMi8PnPIFBA,7445
|
|
31
31
|
autogen/agentchat/utils.py,sha256=2ZweUqe4ynxji0jnvd0r9Uxg3n0elif4a-jZOyeeqcg,8238
|
|
@@ -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
|
|
@@ -274,14 +280,14 @@ autogen/logger/base_logger.py,sha256=nMR6IqELN8b-TzqL0-pJyJ6CpIejjp45IYh5tsMQHAE
|
|
|
274
280
|
autogen/logger/file_logger.py,sha256=EbjDhvPUSP2JX-xAZRplc8JRm6au8ofcjHz-KfXboh4,9796
|
|
275
281
|
autogen/logger/logger_factory.py,sha256=CeLbW3gN0J5zgvQSsRLCSnaiMYusrDWLWovo_Bk-mK8,1391
|
|
276
282
|
autogen/logger/logger_utils.py,sha256=H9hcsRyEcUcfxTYWf5cRjtNghF4h3FT8sr4IIuqQumY,2053
|
|
277
|
-
autogen/logger/sqlite_logger.py,sha256=
|
|
283
|
+
autogen/logger/sqlite_logger.py,sha256=rP1He7CJB839Qhn1AvW5C0kQ1c45KJs1lgV16y4LxpQ,18694
|
|
278
284
|
autogen/mcp/__init__.py,sha256=6BDDmw0sjLZRjyHnd-Gfh9BE-pTKTv5bkow9W6odHtQ,213
|
|
279
285
|
autogen/mcp/__main__.py,sha256=C7nXbWxG3yGsWKRgFnhSsbQOmshLnz6ZOcCxK2TWWio,2487
|
|
280
286
|
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,16 +302,16 @@ 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=icTgPuHiO6Q9l3iu34j2JYXLCSzYVuculfMBEm1TeDY,68897
|
|
300
306
|
autogen/oai/client_utils.py,sha256=lVbHyff7OnpdM-tXskC23xLdFccj2AalTdWA4DxzxS4,7543
|
|
301
307
|
autogen/oai/cohere.py,sha256=pRcQWjbzKbZ1RfC1vk9WGjgndwjHbIaOVoKEYdV2L6c,19421
|
|
302
|
-
autogen/oai/gemini.py,sha256=
|
|
308
|
+
autogen/oai/gemini.py,sha256=PXHLyXJgB35YuRFGnM_8fxg2FDRcQj3szh0PZc9cOKA,43192
|
|
303
309
|
autogen/oai/gemini_types.py,sha256=i4wT8ytD2cO9nCUWm2FKLKVVxyd31wMhOpnrvnIiKIc,5888
|
|
304
310
|
autogen/oai/groq.py,sha256=pQWtaAY_AjT30XKbZNHXDzWsawBys3yFWlfy6K4Nqr8,12431
|
|
305
311
|
autogen/oai/mistral.py,sha256=SlOYPdnNLHuTEHBGCzsemG9sLEgphdUukRurERdMsvI,12677
|
|
306
|
-
autogen/oai/ollama.py,sha256=
|
|
312
|
+
autogen/oai/ollama.py,sha256=39r_dA9Uq_ZcVxxMmaSJ9WgjxspQty7PX6cdx8lOEpQ,29162
|
|
307
313
|
autogen/oai/openai_responses.py,sha256=W847OTGzlDbII8sYWIXTOc9NEPAM59XmLWvcYbC6bRo,17811
|
|
308
|
-
autogen/oai/openai_utils.py,sha256=
|
|
314
|
+
autogen/oai/openai_utils.py,sha256=lVWSJsTT_Kpto00423mf0zkDLCVXbiYLxLeiG9QdjQ4,41401
|
|
309
315
|
autogen/oai/together.py,sha256=Sj4LOk9RrBG3Bb5IVsrjBYz-hDECCyCgofsCdtk6PSM,14867
|
|
310
316
|
autogen/oai/oai_models/__init__.py,sha256=cILDaaCCvSC3aAX85iLwE1RCpNEokA9925Zse5hX2K4,549
|
|
311
317
|
autogen/oai/oai_models/_models.py,sha256=jr5nlvk7Be4W7wDVnwyjDL6m2CSj0RY1nOL1W3Kq0xI,478
|
|
@@ -318,14 +324,16 @@ autogen/oai/oai_models/completion_usage.py,sha256=WAuvTPlONP3wN1iiWXztp7Zv6e6-ba
|
|
|
318
324
|
autogen/tools/__init__.py,sha256=guevGJut7uN2pGpPn9mODaVsTSfeHrGpjjjIEWRBqVo,554
|
|
319
325
|
autogen/tools/dependency_injection.py,sha256=UaLO9QdaVxPLjbKa5ZRuizPagMfg91pa7ohc54mUkDU,8468
|
|
320
326
|
autogen/tools/function_utils.py,sha256=J-3u-Ao_kK7PxqiJMea2P-HGfSTeJTXlWTFSI19IndM,13756
|
|
321
|
-
autogen/tools/tool.py,sha256=
|
|
327
|
+
autogen/tools/tool.py,sha256=VtSYf363Z9vIsSZWgNnYs9REdRuPWJMDZkcklIq9Yh8,7225
|
|
322
328
|
autogen/tools/toolkit.py,sha256=1tOmTGJ96RhkJrrtAViKUyEcwacA6ztoIbYbnf8NgTU,2558
|
|
323
329
|
autogen/tools/contrib/__init__.py,sha256=DWEjPK6xCR2ihAXXdquQZmiuqRLA3Pqb8QV8W1RtS3k,202
|
|
324
330
|
autogen/tools/contrib/time/__init__.py,sha256=dplie5aBJZ8VoKy6EKcQMLTtSgcCkNDYzpdsC2I0YWk,195
|
|
325
331
|
autogen/tools/contrib/time/time.py,sha256=tPi49vOUwfvujbYA-zS00CWcLW-y18CPyQ1gnJG6iRg,1271
|
|
326
|
-
autogen/tools/experimental/__init__.py,sha256=
|
|
332
|
+
autogen/tools/experimental/__init__.py,sha256=OPJlrK_tJCPbeBT-qsIWwJBdLuaCMskpshduA8Haj7E,1671
|
|
327
333
|
autogen/tools/experimental/browser_use/__init__.py,sha256=kfxCajXcVMDH6CZq-lWh2p8PKxOwT9yjC_Za0jr4zUg,290
|
|
328
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
|
|
329
337
|
autogen/tools/experimental/crawl4ai/__init__.py,sha256=UjFJLSZ9P5xT6WCV0RDPtwt4MHuwPdK90TU7ByXhLWs,207
|
|
330
338
|
autogen/tools/experimental/crawl4ai/crawl4ai.py,sha256=MsOLtbPHRpRrCnRJPQVVVNwmsBcgsWLSHNXDOF-47ws,6088
|
|
331
339
|
autogen/tools/experimental/deep_research/__init__.py,sha256=9SFcDEj2OHxNSlXP11lf1uHENlfUeO47ROcOSD9GCDs,220
|
|
@@ -406,8 +414,8 @@ autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py,sha256=
|
|
|
406
414
|
autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py,sha256=iqgpFJdyBHPPNCqkehSIbeuV8Rabr2eDMilT23Wx7PI,1687
|
|
407
415
|
autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py,sha256=-6T5r6Er4mONPldRxv3F9tLoE7Og3qmeSeTC7Du_tTg,596
|
|
408
416
|
autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py,sha256=Xig7K3A3DRnbv-UXfyo5bybGZUQYAQsltthfTYW5eV8,509
|
|
409
|
-
ag2-0.9.
|
|
410
|
-
ag2-0.9.
|
|
411
|
-
ag2-0.9.
|
|
412
|
-
ag2-0.9.
|
|
413
|
-
ag2-0.9.
|
|
417
|
+
ag2-0.9.7.dist-info/METADATA,sha256=eFWnorPlwQwD0pmvgJvzwflsiWFZh69HHZq4MgxNxjg,35268
|
|
418
|
+
ag2-0.9.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
419
|
+
ag2-0.9.7.dist-info/licenses/LICENSE,sha256=GEFQVNayAR-S_rQD5l8hPdgvgyktVdy4Bx5-v90IfRI,11384
|
|
420
|
+
ag2-0.9.7.dist-info/licenses/NOTICE.md,sha256=07iCPQGbth4pQrgkSgZinJGT5nXddkZ6_MGYcBd2oiY,1134
|
|
421
|
+
ag2-0.9.7.dist-info/RECORD,,
|
|
@@ -3873,15 +3873,29 @@ class ConversableAgent(LLMAgent):
|
|
|
3873
3873
|
**executor_kwargs,
|
|
3874
3874
|
)
|
|
3875
3875
|
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3876
|
+
# Combine agent's existing tools with passed tools
|
|
3877
|
+
agent_tools = self._tools.copy() # Get agent's pre-registered tools
|
|
3878
|
+
passed_tools = [] if tools is None else tools
|
|
3879
|
+
passed_tools = [passed_tools] if isinstance(passed_tools, Tool) else passed_tools
|
|
3880
|
+
|
|
3881
|
+
# Combine both sets of tools (avoid duplicates)
|
|
3882
|
+
all_tools = agent_tools.copy()
|
|
3883
|
+
for tool in passed_tools:
|
|
3884
|
+
if tool not in all_tools:
|
|
3885
|
+
all_tools.append(tool)
|
|
3886
|
+
|
|
3887
|
+
# Register all tools with the executor
|
|
3888
|
+
for tool in all_tools:
|
|
3879
3889
|
tool.register_for_execution(self.run_executor)
|
|
3890
|
+
|
|
3891
|
+
# Register only newly passed tools for LLM (agent's pre-existing tools are already registered)
|
|
3892
|
+
for tool in passed_tools:
|
|
3880
3893
|
tool.register_for_llm(self)
|
|
3881
3894
|
yield self.run_executor
|
|
3882
3895
|
finally:
|
|
3883
|
-
|
|
3884
|
-
|
|
3896
|
+
# Clean up only newly passed tools (not agent's pre-existing tools)
|
|
3897
|
+
if "passed_tools" in locals():
|
|
3898
|
+
for tool in passed_tools:
|
|
3885
3899
|
self.update_tool_signature(tool_sig=tool.tool_schema["function"]["name"], is_remove=True)
|
|
3886
3900
|
|
|
3887
3901
|
def _deprecated_run(
|
|
@@ -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)
|