CoACT initialize (#292)
This commit is contained in:
7
mm_agents/coact/autogen/interop/pydantic_ai/__init__.py
Normal file
7
mm_agents/coact/autogen/interop/pydantic_ai/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from .pydantic_ai import PydanticAIInteroperability
|
||||
|
||||
__all__ = ["PydanticAIInteroperability"]
|
||||
168
mm_agents/coact/autogen/interop/pydantic_ai/pydantic_ai.py
Normal file
168
mm_agents/coact/autogen/interop/pydantic_ai/pydantic_ai.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
from functools import wraps
|
||||
from inspect import signature
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from ...doc_utils import export_module
|
||||
from ...import_utils import optional_import_block, require_optional_import
|
||||
from ...tools import Tool
|
||||
from ..registry import register_interoperable_class
|
||||
|
||||
__all__ = ["PydanticAIInteroperability"]
|
||||
|
||||
with optional_import_block():
|
||||
from pydantic_ai import RunContext
|
||||
from pydantic_ai.tools import Tool as PydanticAITool
|
||||
from pydantic_ai.usage import Usage
|
||||
|
||||
|
||||
@register_interoperable_class("pydanticai")
|
||||
@export_module("autogen.interop")
|
||||
class PydanticAIInteroperability:
|
||||
"""A class implementing the `Interoperable` protocol for converting Pydantic AI tools
|
||||
into a general `Tool` format.
|
||||
|
||||
This class takes a `PydanticAITool` and converts it into a standard `Tool` object,
|
||||
ensuring compatibility between Pydantic AI tools and other systems that expect
|
||||
the `Tool` format. It also provides a mechanism for injecting context parameters
|
||||
into the tool's function.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@require_optional_import("pydantic_ai", "interop-pydantic-ai")
|
||||
def inject_params(
|
||||
ctx: Any,
|
||||
tool: Any,
|
||||
) -> Callable[..., Any]:
|
||||
"""Wraps the tool's function to inject context parameters and handle retries.
|
||||
|
||||
This method ensures that context parameters are properly passed to the tool
|
||||
when invoked and that retries are managed according to the tool's settings.
|
||||
|
||||
Args:
|
||||
ctx (Optional[RunContext[Any]]): The run context, which may include dependencies and retry information.
|
||||
tool (PydanticAITool): The Pydantic AI tool whose function is to be wrapped.
|
||||
|
||||
Returns:
|
||||
Callable[..., Any]: A wrapped function that includes context injection and retry handling.
|
||||
|
||||
Raises:
|
||||
ValueError: If the tool fails after the maximum number of retries.
|
||||
"""
|
||||
ctx_typed: Optional[RunContext[Any]] = ctx # type: ignore[no-any-unimported]
|
||||
tool_typed: PydanticAITool[Any] = tool # type: ignore[no-any-unimported]
|
||||
|
||||
max_retries = tool_typed.max_retries if tool_typed.max_retries is not None else 1
|
||||
f = tool_typed.function
|
||||
|
||||
@wraps(f)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
if tool_typed.current_retry >= max_retries:
|
||||
raise ValueError(f"{tool_typed.name} failed after {max_retries} retries")
|
||||
|
||||
try:
|
||||
if ctx_typed is not None:
|
||||
kwargs.pop("ctx", None)
|
||||
ctx_typed.retry = tool_typed.current_retry
|
||||
result = f(**kwargs, ctx=ctx_typed) # type: ignore[call-arg]
|
||||
else:
|
||||
result = f(**kwargs) # type: ignore[call-arg]
|
||||
tool_typed.current_retry = 0
|
||||
except Exception as e:
|
||||
tool_typed.current_retry += 1
|
||||
raise e
|
||||
|
||||
return result
|
||||
|
||||
sig = signature(f)
|
||||
if ctx_typed is not None:
|
||||
new_params = [param for name, param in sig.parameters.items() if name != "ctx"]
|
||||
else:
|
||||
new_params = list(sig.parameters.values())
|
||||
|
||||
wrapper.__signature__ = sig.replace(parameters=new_params) # type: ignore[attr-defined]
|
||||
|
||||
return wrapper
|
||||
|
||||
@classmethod
|
||||
@require_optional_import("pydantic_ai", "interop-pydantic-ai")
|
||||
def convert_tool(cls, tool: Any, deps: Any = None, **kwargs: Any) -> Tool:
|
||||
"""Converts a given Pydantic AI tool into a general `Tool` format.
|
||||
|
||||
This method verifies that the provided tool is a valid `PydanticAITool`,
|
||||
handles context dependencies if necessary, and returns a standardized `Tool` object.
|
||||
|
||||
Args:
|
||||
tool (Any): The tool to convert, expected to be an instance of `PydanticAITool`.
|
||||
deps (Any, optional): The dependencies to inject into the context, required if
|
||||
the tool takes a context. Defaults to None.
|
||||
**kwargs (Any): Additional arguments that are not used in this method.
|
||||
|
||||
Returns:
|
||||
Tool: A standardized `Tool` object converted from the Pydantic AI tool.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provided tool is not an instance of `PydanticAITool`, or if
|
||||
dependencies are missing for tools that require a context.
|
||||
UserWarning: If the `deps` argument is provided for a tool that does not take a context.
|
||||
"""
|
||||
if not isinstance(tool, PydanticAITool):
|
||||
raise ValueError(f"Expected an instance of `pydantic_ai.tools.Tool`, got {type(tool)}")
|
||||
|
||||
# needed for type checking
|
||||
pydantic_ai_tool: PydanticAITool[Any] = tool # type: ignore[no-any-unimported]
|
||||
|
||||
if tool.takes_ctx and deps is None:
|
||||
raise ValueError("If the tool takes a context, the `deps` argument must be provided")
|
||||
if not tool.takes_ctx and deps is not None:
|
||||
warnings.warn(
|
||||
"The `deps` argument is provided but will be ignored because the tool does not take a context.",
|
||||
UserWarning,
|
||||
)
|
||||
|
||||
ctx = (
|
||||
RunContext(
|
||||
model=None, # type: ignore [arg-type]
|
||||
usage=Usage(),
|
||||
prompt="",
|
||||
deps=deps,
|
||||
retry=0,
|
||||
# All messages send to or returned by a model.
|
||||
# This is mostly used on pydantic_ai Agent level.
|
||||
messages=[], # TODO: check in the future if this is needed on Tool level
|
||||
tool_name=pydantic_ai_tool.name,
|
||||
)
|
||||
if tool.takes_ctx
|
||||
else None
|
||||
)
|
||||
|
||||
func = PydanticAIInteroperability.inject_params(
|
||||
ctx=ctx,
|
||||
tool=pydantic_ai_tool,
|
||||
)
|
||||
|
||||
return Tool(
|
||||
name=pydantic_ai_tool.name,
|
||||
description=pydantic_ai_tool.description,
|
||||
func_or_tool=func,
|
||||
parameters_json_schema=pydantic_ai_tool._parameters_json_schema,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_unsupported_reason(cls) -> Optional[str]:
|
||||
if sys.version_info < (3, 9):
|
||||
return "This submodule is only supported for Python versions 3.9 and above"
|
||||
|
||||
with optional_import_block() as result:
|
||||
import pydantic_ai.tools # noqa: F401
|
||||
|
||||
if not result.is_successful:
|
||||
return "Please install `interop-pydantic-ai` extra to use this module:\n\n\tpip install ag2[interop-pydantic-ai]"
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user