ag2 0.9.8.post1__py3-none-any.whl → 0.9.10__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.

Files changed (88) hide show
  1. {ag2-0.9.8.post1.dist-info → ag2-0.9.10.dist-info}/METADATA +232 -210
  2. {ag2-0.9.8.post1.dist-info → ag2-0.9.10.dist-info}/RECORD +88 -80
  3. autogen/_website/generate_mkdocs.py +3 -3
  4. autogen/_website/notebook_processor.py +1 -1
  5. autogen/_website/utils.py +1 -1
  6. autogen/agentchat/assistant_agent.py +15 -15
  7. autogen/agentchat/chat.py +52 -40
  8. autogen/agentchat/contrib/agent_eval/criterion.py +1 -1
  9. autogen/agentchat/contrib/capabilities/text_compressors.py +5 -5
  10. autogen/agentchat/contrib/capabilities/tools_capability.py +1 -1
  11. autogen/agentchat/contrib/capabilities/transforms.py +1 -1
  12. autogen/agentchat/contrib/captainagent/agent_builder.py +1 -1
  13. autogen/agentchat/contrib/captainagent/captainagent.py +20 -19
  14. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +2 -5
  15. autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +5 -5
  16. autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +18 -17
  17. autogen/agentchat/contrib/rag/mongodb_query_engine.py +2 -2
  18. autogen/agentchat/contrib/rag/query_engine.py +11 -11
  19. autogen/agentchat/contrib/retrieve_assistant_agent.py +3 -0
  20. autogen/agentchat/contrib/swarm_agent.py +3 -2
  21. autogen/agentchat/contrib/vectordb/couchbase.py +1 -1
  22. autogen/agentchat/contrib/vectordb/mongodb.py +1 -1
  23. autogen/agentchat/contrib/web_surfer.py +1 -1
  24. autogen/agentchat/conversable_agent.py +184 -80
  25. autogen/agentchat/group/context_expression.py +21 -21
  26. autogen/agentchat/group/handoffs.py +11 -11
  27. autogen/agentchat/group/multi_agent_chat.py +3 -2
  28. autogen/agentchat/group/on_condition.py +11 -11
  29. autogen/agentchat/group/safeguards/__init__.py +21 -0
  30. autogen/agentchat/group/safeguards/api.py +224 -0
  31. autogen/agentchat/group/safeguards/enforcer.py +1064 -0
  32. autogen/agentchat/group/safeguards/events.py +119 -0
  33. autogen/agentchat/group/safeguards/validator.py +435 -0
  34. autogen/agentchat/groupchat.py +60 -19
  35. autogen/agentchat/realtime/experimental/clients/realtime_client.py +2 -2
  36. autogen/agentchat/realtime/experimental/function_observer.py +2 -3
  37. autogen/agentchat/realtime/experimental/realtime_agent.py +2 -3
  38. autogen/agentchat/realtime/experimental/realtime_swarm.py +21 -10
  39. autogen/agentchat/user_proxy_agent.py +55 -53
  40. autogen/agents/experimental/document_agent/document_agent.py +1 -10
  41. autogen/agents/experimental/document_agent/parser_utils.py +5 -1
  42. autogen/browser_utils.py +4 -4
  43. autogen/cache/abstract_cache_base.py +2 -6
  44. autogen/cache/disk_cache.py +1 -6
  45. autogen/cache/in_memory_cache.py +2 -6
  46. autogen/cache/redis_cache.py +1 -5
  47. autogen/coding/__init__.py +10 -2
  48. autogen/coding/base.py +2 -1
  49. autogen/coding/docker_commandline_code_executor.py +1 -6
  50. autogen/coding/factory.py +9 -0
  51. autogen/coding/jupyter/docker_jupyter_server.py +1 -7
  52. autogen/coding/jupyter/jupyter_client.py +2 -9
  53. autogen/coding/jupyter/jupyter_code_executor.py +2 -7
  54. autogen/coding/jupyter/local_jupyter_server.py +2 -6
  55. autogen/coding/local_commandline_code_executor.py +0 -65
  56. autogen/coding/yepcode_code_executor.py +197 -0
  57. autogen/environments/docker_python_environment.py +3 -3
  58. autogen/environments/system_python_environment.py +5 -5
  59. autogen/environments/venv_python_environment.py +5 -5
  60. autogen/events/agent_events.py +1 -1
  61. autogen/events/client_events.py +1 -1
  62. autogen/fast_depends/utils.py +10 -0
  63. autogen/graph_utils.py +5 -7
  64. autogen/import_utils.py +28 -15
  65. autogen/interop/pydantic_ai/pydantic_ai.py +8 -5
  66. autogen/io/processors/console_event_processor.py +8 -3
  67. autogen/llm_config/config.py +168 -91
  68. autogen/llm_config/entry.py +38 -26
  69. autogen/llm_config/types.py +35 -0
  70. autogen/llm_config/utils.py +223 -0
  71. autogen/mcp/mcp_proxy/operation_grouping.py +48 -39
  72. autogen/messages/agent_messages.py +1 -1
  73. autogen/messages/client_messages.py +1 -1
  74. autogen/oai/__init__.py +8 -1
  75. autogen/oai/client.py +10 -3
  76. autogen/oai/client_utils.py +1 -1
  77. autogen/oai/cohere.py +4 -4
  78. autogen/oai/gemini.py +4 -6
  79. autogen/oai/gemini_types.py +1 -0
  80. autogen/oai/openai_utils.py +44 -115
  81. autogen/tools/dependency_injection.py +4 -8
  82. autogen/tools/experimental/reliable/reliable.py +3 -2
  83. autogen/tools/experimental/web_search_preview/web_search_preview.py +1 -1
  84. autogen/tools/function_utils.py +2 -1
  85. autogen/version.py +1 -1
  86. {ag2-0.9.8.post1.dist-info → ag2-0.9.10.dist-info}/WHEEL +0 -0
  87. {ag2-0.9.8.post1.dist-info → ag2-0.9.10.dist-info}/licenses/LICENSE +0 -0
  88. {ag2-0.9.8.post1.dist-info → ag2-0.9.10.dist-info}/licenses/NOTICE.md +0 -0
@@ -8,7 +8,6 @@ from __future__ import annotations
8
8
 
9
9
  import atexit
10
10
  import logging
11
- import sys
12
11
  import uuid
13
12
  from hashlib import md5
14
13
  from pathlib import Path
@@ -18,6 +17,7 @@ from typing import Any, ClassVar
18
17
 
19
18
  import docker
20
19
  from docker.errors import ImageNotFound
20
+ from typing_extensions import Self
21
21
 
22
22
  from ..code_utils import TIMEOUT_MSG, _cmd
23
23
  from ..doc_utils import export_module
@@ -25,11 +25,6 @@ from .base import CodeBlock, CodeExecutor, CodeExtractor, CommandLineCodeResult
25
25
  from .markdown_code_extractor import MarkdownCodeExtractor
26
26
  from .utils import _get_file_name_from_content, silence_pip
27
27
 
28
- if sys.version_info >= (3, 11):
29
- from typing import Self
30
- else:
31
- from typing_extensions import Self
32
-
33
28
 
34
29
  def _wait_for_ready(container: Any, timeout: int = 60, stop_time: float = 0.1) -> None:
35
30
  elapsed_time = 0.0
autogen/coding/factory.py CHANGED
@@ -43,5 +43,14 @@ class CodeExecutorFactory:
43
43
  from .local_commandline_code_executor import LocalCommandLineCodeExecutor
44
44
 
45
45
  return LocalCommandLineCodeExecutor(**code_execution_config.get("commandline-local", {}))
46
+ elif executor == "yepcode":
47
+ try:
48
+ from .yepcode_code_executor import YepCodeCodeExecutor
49
+ except ImportError as e:
50
+ raise ImportError(
51
+ "Missing dependencies for YepCodeCodeExecutor. Please install with: pip install ag2[yepcode]"
52
+ ) from e
53
+
54
+ return YepCodeCodeExecutor(**code_execution_config.get("yepcode", {}))
46
55
  else:
47
56
  raise ValueError(f"Unknown code executor {executor}")
@@ -10,20 +10,14 @@ import atexit
10
10
  import io
11
11
  import logging
12
12
  import secrets
13
- import sys
14
13
  import uuid
15
14
  from pathlib import Path
16
15
  from types import TracebackType
17
16
 
18
17
  import docker
18
+ from typing_extensions import Self
19
19
 
20
20
  from ...doc_utils import export_module
21
-
22
- if sys.version_info >= (3, 11):
23
- from typing import Self
24
- else:
25
- from typing_extensions import Self
26
-
27
21
  from ..docker_commandline_code_executor import _wait_for_ready
28
22
  from .base import JupyterConnectable, JupyterConnectionInfo
29
23
  from .import_utils import require_jupyter_kernel_gateway_installed
@@ -8,23 +8,16 @@ from __future__ import annotations
8
8
 
9
9
  import datetime
10
10
  import json
11
- import sys
12
11
  import uuid
13
12
  from dataclasses import dataclass
14
13
  from types import TracebackType
15
14
  from typing import Any, cast
16
15
 
17
- from ...doc_utils import export_module
18
-
19
- if sys.version_info >= (3, 11):
20
- from typing import Self
21
- else:
22
- from typing_extensions import Self
23
-
24
-
25
16
  import requests
26
17
  from requests.adapters import HTTPAdapter, Retry
18
+ from typing_extensions import Self
27
19
 
20
+ from ...doc_utils import export_module
28
21
  from ...import_utils import optional_import_block, require_optional_import
29
22
  from .base import JupyterConnectionInfo
30
23
 
@@ -7,18 +7,13 @@
7
7
  import base64
8
8
  import json
9
9
  import os
10
- import sys
11
10
  import uuid
12
11
  from pathlib import Path
13
12
  from types import TracebackType
14
13
 
15
- from ...doc_utils import export_module
16
-
17
- if sys.version_info >= (3, 11):
18
- from typing import Self
19
- else:
20
- from typing_extensions import Self
14
+ from typing_extensions import Self
21
15
 
16
+ from ...doc_utils import export_module
22
17
  from ..base import CodeBlock, CodeExecutor, CodeExtractor, IPythonCodeResult
23
18
  from ..markdown_code_extractor import MarkdownCodeExtractor
24
19
  from ..utils import silence_pip
@@ -14,13 +14,9 @@ import subprocess
14
14
  import sys
15
15
  from types import TracebackType
16
16
 
17
- from ...doc_utils import export_module
18
-
19
- if sys.version_info >= (3, 11):
20
- from typing import Self
21
- else:
22
- from typing_extensions import Self
17
+ from typing_extensions import Self
23
18
 
19
+ from ...doc_utils import export_module
24
20
  from .base import JupyterConnectable, JupyterConnectionInfo
25
21
  from .import_utils import require_jupyter_kernel_gateway_installed
26
22
  from .jupyter_client import JupyterClient
@@ -339,68 +339,3 @@ $functions"""
339
339
  def restart(self) -> None:
340
340
  """(Experimental) Restart the code executor."""
341
341
  warnings.warn("Restarting local command line code executor is not supported. No action is taken.")
342
-
343
-
344
- # From stack overflow: https://stackoverflow.com/a/52087847/2214524
345
- class _DeprecatedClassMeta(type):
346
- def __new__(cls, name, bases, classdict, *args, **kwargs): # type: ignore[no-untyped-def]
347
- alias = classdict.get("_DeprecatedClassMeta__alias")
348
-
349
- if alias is not None:
350
-
351
- def new(cls, *args, **kwargs): # type: ignore[no-untyped-def]
352
- alias = cls._DeprecatedClassMeta__alias
353
-
354
- if alias is not None:
355
- warnings.warn(
356
- f"{cls.__name__} has been renamed to {alias.__name__}, the alias will be removed in the future",
357
- DeprecationWarning,
358
- stacklevel=2,
359
- )
360
-
361
- return alias(*args, **kwargs)
362
-
363
- classdict["__new__"] = new
364
- classdict["_DeprecatedClassMeta__alias"] = alias
365
-
366
- fixed_bases = []
367
-
368
- for b in bases:
369
- alias = getattr(b, "_DeprecatedClassMeta__alias", None)
370
-
371
- if alias is not None:
372
- warnings.warn(
373
- f"{b.__name__} has been renamed to {alias.__name__}, the alias will be removed in the future",
374
- DeprecationWarning,
375
- stacklevel=2,
376
- )
377
-
378
- # Avoid duplicate base classes.
379
- b = alias or b
380
- if b not in fixed_bases:
381
- fixed_bases.append(b)
382
-
383
- fixed_bases = tuple(fixed_bases) # type: ignore[assignment]
384
-
385
- return super().__new__(cls, name, fixed_bases, classdict, *args, **kwargs) # type: ignore[call-overload]
386
-
387
- def __instancecheck__(cls, instance): # type: ignore[no-untyped-def]
388
- return any(cls.__subclasscheck__(c) for c in {type(instance), instance.__class__}) # type: ignore[no-untyped-call]
389
-
390
- def __subclasscheck__(cls, subclass): # type: ignore[no-untyped-def]
391
- if subclass is cls:
392
- return True
393
- else:
394
- return issubclass(subclass, cls._DeprecatedClassMeta__alias) # type: ignore[attr-defined]
395
-
396
-
397
- class LocalCommandlineCodeExecutor(metaclass=_DeprecatedClassMeta):
398
- """LocalCommandlineCodeExecutor renamed to LocalCommandLineCodeExecutor"""
399
-
400
- _DeprecatedClassMeta__alias = LocalCommandLineCodeExecutor
401
-
402
-
403
- class CommandlineCodeResult(metaclass=_DeprecatedClassMeta):
404
- """CommandlineCodeResult renamed to CommandLineCodeResult"""
405
-
406
- _DeprecatedClassMeta__alias = CommandLineCodeResult
@@ -0,0 +1,197 @@
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
+ # Portions derived from https://github.com/microsoft/autogen are under the MIT License.
6
+ # SPDX-License-Identifier: MIT
7
+ """YepCode code executor implementation."""
8
+
9
+ import os
10
+ from collections.abc import Callable
11
+ from typing import ClassVar
12
+
13
+ from pydantic import Field
14
+
15
+ from ..doc_utils import export_module
16
+ from .base import CodeBlock, CodeExecutor, CodeExtractor, CodeResult
17
+ from .markdown_code_extractor import MarkdownCodeExtractor
18
+
19
+ try:
20
+ from dotenv import load_dotenv
21
+
22
+ _load_dotenv: Callable[[], bool] | None = load_dotenv
23
+ except ImportError:
24
+ _load_dotenv = None
25
+
26
+ try:
27
+ from yepcode_run import YepCodeApiConfig, YepCodeRun
28
+ except ImportError:
29
+ YepCodeRun = None
30
+ YepCodeApiConfig = None
31
+
32
+
33
+ @export_module("autogen.coding")
34
+ class YepCodeCodeResult(CodeResult):
35
+ """A code result class for YepCode executor."""
36
+
37
+ execution_id: str | None = Field(default=None, description="The YepCode execution ID for this result.")
38
+
39
+
40
+ @export_module("autogen.coding")
41
+ class YepCodeCodeExecutor(CodeExecutor):
42
+ """A code executor class that executes code using YepCode's serverless runtime.
43
+
44
+ This executor runs code in YepCode's secure, production-grade sandboxes.
45
+ It supports Python and JavaScript execution with access to any external library with automatic discovery and installation.
46
+
47
+ The executor executes code blocks serially in the order they are received.
48
+ Each code block is executed in a separate YepCode execution environment.
49
+ Currently supports Python and JavaScript languages.
50
+
51
+ Args:
52
+ api_token (Optional[str]): YepCode API token. If None, will try to get from YEPCODE_API_TOKEN environment variable.
53
+ timeout (int): The timeout for code execution in seconds. Default is 60.
54
+ remove_on_done (bool): Whether to remove the execution after completion. Default is False.
55
+ sync_execution (bool): Whether to wait for execution to complete. Default is True.
56
+
57
+ Raises:
58
+ ImportError: If yepcode-run package is not installed.
59
+ ValueError: If YepCode API token is not provided or timeout is invalid.
60
+ RuntimeError: If YepCode runner initialization fails.
61
+ """
62
+
63
+ SUPPORTED_LANGUAGES: ClassVar[list[str]] = ["python", "javascript"]
64
+
65
+ def __init__(
66
+ self,
67
+ api_token: str | None = None,
68
+ timeout: int = 60,
69
+ remove_on_done: bool = False,
70
+ sync_execution: bool = True,
71
+ ):
72
+ if YepCodeRun is None or YepCodeApiConfig is None:
73
+ raise ImportError(
74
+ "Missing dependencies for YepCodeCodeExecutor. Please install with: pip install ag2[yepcode]"
75
+ )
76
+
77
+ if timeout < 1:
78
+ raise ValueError("Timeout must be greater than or equal to 1.")
79
+
80
+ # Load environment variables from .env file if dotenv is available
81
+ if _load_dotenv is not None:
82
+ _load_dotenv()
83
+
84
+ # Get API token from parameter or environment
85
+ self._api_token = api_token or os.getenv("YEPCODE_API_TOKEN")
86
+ if not self._api_token:
87
+ raise ValueError(
88
+ "YepCode API token is required. Provide it via api_token parameter or YEPCODE_API_TOKEN environment variable."
89
+ )
90
+
91
+ self._timeout = timeout
92
+ self._remove_on_done = remove_on_done
93
+ self._sync_execution = sync_execution
94
+
95
+ try:
96
+ config = YepCodeApiConfig(api_token=self._api_token)
97
+ self._runner = YepCodeRun(config)
98
+ except Exception as e:
99
+ raise RuntimeError(f"Failed to initialize YepCode runner: {str(e)}") from e
100
+
101
+ @property
102
+ def code_extractor(self) -> CodeExtractor:
103
+ """(Experimental) Export a code extractor that can be used by an agent."""
104
+ return MarkdownCodeExtractor()
105
+
106
+ @property
107
+ def timeout(self) -> int:
108
+ """The timeout for code execution."""
109
+ return self._timeout
110
+
111
+ def _normalize_language(self, language: str) -> str:
112
+ """Normalize language name to YepCode format."""
113
+ lang = language.lower()
114
+ if lang in ["js", "javascript"]:
115
+ return "javascript"
116
+ elif lang in ["python", "py"]:
117
+ return "python"
118
+ else:
119
+ return lang
120
+
121
+ def execute_code_blocks(self, code_blocks: list[CodeBlock]) -> YepCodeCodeResult:
122
+ """Execute the code blocks and return the result.
123
+
124
+ Args:
125
+ code_blocks (List[CodeBlock]): The code blocks to execute.
126
+
127
+ Returns:
128
+ YepCodeCodeResult: The result of the code execution.
129
+ """
130
+ if not code_blocks:
131
+ return YepCodeCodeResult(exit_code=0, output="")
132
+
133
+ outputs: list[str] = []
134
+ last_execution_id: str | None = None
135
+
136
+ for code_block in code_blocks:
137
+ lang = self._normalize_language(code_block.language)
138
+
139
+ if lang not in ["python", "javascript"]:
140
+ return YepCodeCodeResult(
141
+ exit_code=1,
142
+ output=f"Unsupported language: {code_block.language}. Supported languages: {', '.join(self.SUPPORTED_LANGUAGES)}",
143
+ )
144
+
145
+ try:
146
+ # Execute code using YepCode
147
+ execution = self._runner.run(
148
+ code_block.code,
149
+ {
150
+ "language": lang,
151
+ "removeOnDone": self._remove_on_done,
152
+ "timeout": self._timeout * 1000, # Convert to milliseconds
153
+ },
154
+ )
155
+
156
+ last_execution_id = execution.id
157
+
158
+ if self._sync_execution:
159
+ # Wait for execution to complete
160
+ execution.wait_for_done()
161
+
162
+ logs_output = ""
163
+ # Get logs
164
+ if execution.logs:
165
+ logs_output = "\n\nExecution logs:\n" + "\n".join([
166
+ f"{log.timestamp} - {log.level}: {log.message}" for log in execution.logs
167
+ ])
168
+
169
+ # Check if execution was successful
170
+ if execution.error:
171
+ output = f"Execution failed with error:\n{execution.error}{logs_output}"
172
+
173
+ return YepCodeCodeResult(exit_code=1, output=output, execution_id=execution.id)
174
+
175
+ # Get output
176
+ output = ""
177
+ if execution.return_value:
178
+ output = f"Execution result:\n{execution.return_value}"
179
+
180
+ output += logs_output
181
+
182
+ outputs.append(output)
183
+ else:
184
+ outputs.append(f"Execution started with ID: {execution.id}")
185
+
186
+ except Exception as e:
187
+ return YepCodeCodeResult(
188
+ exit_code=1,
189
+ output=f"Error executing code: {str(e)}",
190
+ execution_id=last_execution_id,
191
+ )
192
+
193
+ return YepCodeCodeResult(exit_code=0, output="\n===\n".join(outputs), execution_id=last_execution_id)
194
+
195
+ def restart(self) -> None:
196
+ """Restart the code executor."""
197
+ pass
@@ -10,7 +10,7 @@ import tempfile
10
10
  import uuid
11
11
  from typing import Any
12
12
 
13
- from asyncer import asyncify
13
+ from anyio import to_thread
14
14
 
15
15
  from .python_environment import PythonEnvironment
16
16
 
@@ -320,7 +320,7 @@ class DockerPythonEnvironment(PythonEnvironment):
320
320
  os.makedirs(script_dir, exist_ok=True)
321
321
 
322
322
  # Write the code to the script file on the host
323
- await asyncify(self._write_to_file)(host_script_path, code)
323
+ await to_thread.run_sync(self._write_to_file, host_script_path, code)
324
324
 
325
325
  # Path to the script in the container
326
326
  container_script_path = f"/workspace/{rel_path}"
@@ -329,7 +329,7 @@ class DockerPythonEnvironment(PythonEnvironment):
329
329
  exec_cmd = ["docker", "exec", self._container_name, "python", container_script_path]
330
330
 
331
331
  # Run the command with a timeout
332
- result = await asyncify(self._run_subprocess_with_timeout)(exec_cmd, timeout)
332
+ result = await to_thread.run_sync(self._run_subprocess_with_timeout, exec_cmd, timeout)
333
333
 
334
334
  return {
335
335
  "success": result[0],
@@ -8,7 +8,7 @@ import subprocess
8
8
  import sys
9
9
  from typing import Any
10
10
 
11
- from asyncer import asyncify
11
+ from anyio import to_thread
12
12
 
13
13
  from .python_environment import PythonEnvironment
14
14
 
@@ -62,14 +62,14 @@ class SystemPythonEnvironment(PythonEnvironment):
62
62
  if script_dir:
63
63
  os.makedirs(script_dir, exist_ok=True)
64
64
 
65
- # Write the code to the script file using asyncify (from base class)
66
- await asyncify(self._write_to_file)(script_path, code)
65
+ # Write the code to the script file using anyio.to_thread.run_sync (from base class)
66
+ await to_thread.run_sync(self._write_to_file, script_path, code)
67
67
 
68
68
  logging.info(f"Wrote code to {script_path}")
69
69
 
70
70
  try:
71
- # Execute directly with subprocess using asyncify for better reliability
72
- result = await asyncify(self._run_subprocess)([python_executable, script_path], timeout)
71
+ # Execute directly with subprocess using anyio.to_thread.run_sync for better reliability
72
+ result = await to_thread.run_sync(self._run_subprocess, [python_executable, script_path], timeout)
73
73
 
74
74
  # Main execution result
75
75
  return {
@@ -9,7 +9,7 @@ import sys
9
9
  import tempfile
10
10
  from typing import Any
11
11
 
12
- from asyncer import asyncify
12
+ from anyio import to_thread
13
13
 
14
14
  from .python_environment import PythonEnvironment
15
15
 
@@ -132,14 +132,14 @@ class VenvPythonEnvironment(PythonEnvironment):
132
132
  if script_dir:
133
133
  os.makedirs(script_dir, exist_ok=True)
134
134
 
135
- # Write the code to the script file using asyncify (from base class)
136
- await asyncify(self._write_to_file)(script_path, code)
135
+ # Write the code to the script file using anyio.to_thread.run_sync (from base class)
136
+ await to_thread.run_sync(self._write_to_file, script_path, code)
137
137
 
138
138
  logging.info(f"Wrote code to {script_path}")
139
139
 
140
140
  try:
141
- # Execute directly with subprocess using asyncify for better reliability
142
- result = await asyncify(self._run_subprocess)([python_executable, script_path], timeout)
141
+ # Execute directly with subprocess using anyio.to_thread.run_sync for better reliability
142
+ result = await to_thread.run_sync(self._run_subprocess, [python_executable, script_path], timeout)
143
143
 
144
144
  # Main execution result
145
145
  return {
@@ -946,7 +946,7 @@ class GenerateCodeExecutionReplyEvent(BaseEvent):
946
946
  else:
947
947
  f(
948
948
  colored(
949
- f"\n>>>>>>>> EXECUTING {num_code_blocks} CODE BLOCKS (inferred languages are [{', '.join([x for x in self.code_blocks])}])...",
949
+ f"\n>>>>>>>> EXECUTING {num_code_blocks} CODE BLOCKS (inferred languages are [{', '.join(list(self.code_blocks))}])...",
950
950
  "red",
951
951
  ),
952
952
  flush=True,
@@ -62,7 +62,7 @@ def _change_usage_summary_format(
62
62
  usage_summary_altered_format: dict[str, list[dict[str, Any]]] = {"usages": []}
63
63
  for k, v in usage_summary.items():
64
64
  if isinstance(k, str) and isinstance(v, dict):
65
- current_usage = {key: value for key, value in v.items()}
65
+ current_usage = dict(v.items())
66
66
  current_usage["model"] = k
67
67
  usage_summary_altered_format["usages"].append(current_usage)
68
68
  else:
@@ -26,6 +26,16 @@ P = ParamSpec("P")
26
26
  T = TypeVar("T")
27
27
 
28
28
 
29
+ def asyncify(func: Callable[P, T]) -> Callable[P, Awaitable[T]]:
30
+ if is_coroutine_callable(func):
31
+ return cast(Callable[P, Awaitable[T]], func)
32
+
33
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
34
+ return await run_in_threadpool(func, *args, **kwargs)
35
+
36
+ return wrapper
37
+
38
+
29
39
  async def run_async(
30
40
  func: Callable[P, T] | Callable[P, Awaitable[T]],
31
41
  *args: P.args,
autogen/graph_utils.py CHANGED
@@ -24,7 +24,7 @@ def has_self_loops(allowed_speaker_transitions: dict[str, list[Agent]]) -> bool:
24
24
  Returns:
25
25
  True if there are self loops in the allowed_speaker_transitions_Dict.
26
26
  """
27
- return any([key in value for key, value in allowed_speaker_transitions.items()])
27
+ return any(key in value for key, value in allowed_speaker_transitions.items())
28
28
 
29
29
 
30
30
  def check_graph_validity(
@@ -58,17 +58,15 @@ def check_graph_validity(
58
58
  raise ValueError("allowed_speaker_transitions_dict must be a dictionary.")
59
59
 
60
60
  # All values must be lists of Agent or empty
61
- if not all([isinstance(value, list) for value in allowed_speaker_transitions_dict.values()]):
61
+ if not all(isinstance(value, list) for value in allowed_speaker_transitions_dict.values()):
62
62
  raise ValueError("allowed_speaker_transitions_dict must be a dictionary with lists as values.")
63
63
 
64
64
  # Check 2. Every key exists in agents
65
- if not all([key in agents for key in allowed_speaker_transitions_dict]):
65
+ if not all(key in agents for key in allowed_speaker_transitions_dict):
66
66
  raise ValueError("allowed_speaker_transitions_dict has keys not in agents.")
67
67
 
68
68
  # Check 3. Every value is a list of Agents or empty list (not string).
69
- if not all([
70
- all([isinstance(agent, Agent) for agent in value]) for value in allowed_speaker_transitions_dict.values()
71
- ]):
69
+ if not all(all(isinstance(agent, Agent) for agent in value) for value in allowed_speaker_transitions_dict.values()):
72
70
  raise ValueError("allowed_speaker_transitions_dict has values that are not lists of Agents.")
73
71
 
74
72
  # Warnings
@@ -129,7 +127,7 @@ def invert_disallowed_to_allowed(
129
127
  allowed_speaker_transitions_dict: A dictionary of keys and list as values. The keys are the names of the agents, and the values are the names of the agents that the key agent can transition to.
130
128
  """
131
129
  # Create a fully connected allowed_speaker_transitions_dict of all agents
132
- allowed_speaker_transitions_dict = {agent: [other_agent for other_agent in agents] for agent in agents}
130
+ allowed_speaker_transitions_dict = {agent: list(agents) for agent in agents}
133
131
 
134
132
  # Remove edges from allowed_speaker_transitions_dict according to the disallowed_speaker_transitions_dict
135
133
  for key, value in disallowed_speaker_transitions_dict.items():
autogen/import_utils.py CHANGED
@@ -14,6 +14,10 @@ from logging import getLogger
14
14
  from pathlib import Path
15
15
  from typing import Any, Generic, Optional, TypeVar
16
16
 
17
+ from packaging import version
18
+
19
+ from .fast_depends.utils import is_coroutine_callable
20
+
17
21
  __all__ = [
18
22
  "optional_import_block",
19
23
  "patch_object",
@@ -53,25 +57,34 @@ class ModuleInfo:
53
57
  # Aka similarly named module in the autogen or test directory
54
58
  return f"'{self.name}' is not installed."
55
59
 
56
- installed_version = (
60
+ # Ensure that the retrieved version is a string. Some packages might unexpectedly
61
+ # have a __version__ attribute that is not a string (e.g., a module).
62
+ raw_version_attr = (
57
63
  sys.modules[self.name].__version__ if hasattr(sys.modules[self.name], "__version__") else None
58
64
  )
65
+ installed_version = raw_version_attr if isinstance(raw_version_attr, str) else None
59
66
  if installed_version is None and (self.min_version or self.max_version):
60
67
  return f"'{self.name}' is installed, but the version is not available."
61
68
 
62
- if self.min_version:
63
- msg = f"'{self.name}' is installed, but the installed version {installed_version} is too low (required '{self}')."
64
- if not self.min_inclusive and installed_version == self.min_version:
65
- return msg
66
- if self.min_inclusive and installed_version < self.min_version: # type: ignore[operator]
67
- return msg
68
-
69
- if self.max_version:
70
- msg = f"'{self.name}' is installed, but the installed version {installed_version} is too high (required '{self}')."
71
- if not self.max_inclusive and installed_version == self.max_version:
72
- return msg
73
- if self.max_inclusive and installed_version > self.max_version: # type: ignore[operator]
74
- return msg
69
+ if installed_version:
70
+ # Convert to version object for comparison
71
+ installed_ver = version.parse(installed_version)
72
+
73
+ if self.min_version:
74
+ min_ver = version.parse(self.min_version)
75
+ msg = f"'{self.name}' is installed, but the installed version {installed_version} is too low (required '{self}')."
76
+ if not self.min_inclusive and installed_ver == min_ver:
77
+ return msg
78
+ if self.min_inclusive and installed_ver < min_ver:
79
+ return msg
80
+
81
+ if self.max_version:
82
+ max_ver = version.parse(self.max_version)
83
+ msg = f"'{self.name}' is installed, but the installed version {installed_version} is too high (required '{self}')."
84
+ if not self.max_inclusive and installed_ver == max_ver:
85
+ return msg
86
+ if self.max_inclusive and installed_ver > max_ver:
87
+ return msg
75
88
 
76
89
  return None
77
90
 
@@ -470,7 +483,7 @@ def run_for_optional_imports(modules: str | Iterable[str], dep_target: str) -> C
470
483
  if isinstance(o, type):
471
484
  wrapped = require_optional_import(modules, dep_target)(o)
472
485
  else:
473
- if inspect.iscoroutinefunction(o):
486
+ if is_coroutine_callable(o):
474
487
 
475
488
  @wraps(o)
476
489
  async def wrapped(*args: Any, **kwargs: Any) -> Any:
@@ -63,19 +63,22 @@ class PydanticAIInteroperability:
63
63
 
64
64
  @wraps(f)
65
65
  def wrapper(*args: Any, **kwargs: Any) -> Any:
66
- if tool_typed.current_retry >= max_retries:
66
+ current_retry = 0 if ctx_typed is None else ctx_typed.retries.get(tool_typed.name, 0)
67
+
68
+ if current_retry >= max_retries:
67
69
  raise ValueError(f"{tool_typed.name} failed after {max_retries} retries")
68
70
 
69
71
  try:
70
72
  if ctx_typed is not None:
71
73
  kwargs.pop("ctx", None)
72
- ctx_typed.retry = tool_typed.current_retry
74
+ ctx_typed.retry = current_retry
73
75
  result = f(**kwargs, ctx=ctx_typed) # type: ignore[call-arg]
76
+ ctx_typed.retries[tool_typed.name] = 0
74
77
  else:
75
78
  result = f(**kwargs) # type: ignore[call-arg]
76
- tool_typed.current_retry = 0
77
79
  except Exception as e:
78
- tool_typed.current_retry += 1
80
+ if ctx_typed is not None:
81
+ ctx_typed.retries[tool_typed.name] += 1
79
82
  raise e
80
83
 
81
84
  return result
@@ -151,7 +154,7 @@ class PydanticAIInteroperability:
151
154
  name=pydantic_ai_tool.name,
152
155
  description=pydantic_ai_tool.description,
153
156
  func_or_tool=func,
154
- parameters_json_schema=pydantic_ai_tool._parameters_json_schema,
157
+ parameters_json_schema=pydantic_ai_tool.function_schema.json_schema,
155
158
  )
156
159
 
157
160
  @classmethod