autogen_core.code_executor._func_with_reqs 源代码

# File based from: https://github.com/microsoft/autogen/blob/main/autogen/coding/func_with_reqs.py
# Credit to original authors

from __future__ import annotations

import functools
import inspect
from dataclasses import dataclass, field
from importlib.abc import SourceLoader
from importlib.util import module_from_spec, spec_from_loader
from textwrap import dedent, indent
from typing import Any, Callable, Generic, List, Sequence, Set, Tuple, TypeVar, Union

from typing_extensions import ParamSpec

T = TypeVar("T")
P = ParamSpec("P")


def _to_code(func: Union[FunctionWithRequirements[T, P], Callable[P, T], FunctionWithRequirementsStr]) -> str:
    if isinstance(func, FunctionWithRequirementsStr):
        return func.func

    if isinstance(func, FunctionWithRequirements):
        code = inspect.getsource(func.func)
    else:
        code = inspect.getsource(func)
    # Strip the decorator
    if code.startswith("@"):
        code = code[code.index("\n") + 1 :]
    return code


[文档] @dataclass(frozen=True) class Alias: name: str alias: str
[文档] @dataclass(frozen=True) class ImportFromModule: module: str imports: Tuple[Union[str, Alias], ...] # backward compatibility def __init__( self, module: str, imports: Union[Tuple[Union[str, Alias], ...], List[Union[str, Alias]]], ): object.__setattr__(self, "module", module) if isinstance(imports, list): object.__setattr__(self, "imports", tuple(imports)) else: object.__setattr__(self, "imports", imports)
Import = Union[str, ImportFromModule, Alias] def _import_to_str(im: Import) -> str: if isinstance(im, str): return f"import {im}" elif isinstance(im, Alias): return f"import {im.name} as {im.alias}" else: def to_str(i: Union[str, Alias]) -> str: if isinstance(i, str): return i else: return f"{i.name} as {i.alias}" imports = ", ".join(map(to_str, im.imports)) return f"from {im.module} import {imports}" class _StringLoader(SourceLoader): def __init__(self, data: str): self.data = data def get_source(self, fullname: str) -> str: return self.data def get_data(self, path: str) -> bytes: return self.data.encode("utf-8") def get_filename(self, fullname: str) -> str: return "<not a real path>/" + fullname + ".py"
[文档] @dataclass class FunctionWithRequirementsStr: func: str compiled_func: Callable[..., Any] _func_name: str python_packages: Sequence[str] = field(default_factory=list) global_imports: Sequence[Import] = field(default_factory=list) def __init__(self, func: str, python_packages: Sequence[str] = [], global_imports: Sequence[Import] = []): self.func = func self.python_packages = python_packages self.global_imports = global_imports module_name = "func_module" loader = _StringLoader(func) spec = spec_from_loader(module_name, loader) if spec is None: raise ValueError("Could not create spec") module = module_from_spec(spec) if spec.loader is None: raise ValueError("Could not create loader") try: spec.loader.exec_module(module) except Exception as e: raise ValueError(f"Could not compile function: {e}") from e functions = inspect.getmembers(module, inspect.isfunction) if len(functions) != 1: raise ValueError("The string must contain exactly one function") self._func_name, self.compiled_func = functions[0] def __call__(self, *args: Any, **kwargs: Any) -> None: raise NotImplementedError("String based function with requirement objects are not directly callable")
[文档] @dataclass class FunctionWithRequirements(Generic[T, P]): func: Callable[P, T] python_packages: Sequence[str] = field(default_factory=list) global_imports: Sequence[Import] = field(default_factory=list)
[文档] @classmethod def from_callable( cls, func: Callable[P, T], python_packages: Sequence[str] = [], global_imports: Sequence[Import] = [] ) -> FunctionWithRequirements[T, P]: return cls(python_packages=python_packages, global_imports=global_imports, func=func)
[文档] @staticmethod def from_str( func: str, python_packages: Sequence[str] = [], global_imports: Sequence[Import] = [] ) -> FunctionWithRequirementsStr: return FunctionWithRequirementsStr(func=func, python_packages=python_packages, global_imports=global_imports)
# Type this based on F def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: return self.func(*args, **kwargs)
[文档] def with_requirements( python_packages: Sequence[str] = [], global_imports: Sequence[Import] = [] ) -> Callable[[Callable[P, T]], FunctionWithRequirements[T, P]]: """ 为函数添加代码执行环境所需的包和导入依赖装饰器。 该装饰器通过将函数包装在`FunctionWithRequirements`对象中,使其可在动态执行的代码块中被引用。 该对象会跟踪函数的依赖项。当被装饰的函数传递给代码执行器时,它可以在执行的代码中按名称导入, 所有依赖项会自动处理。 Args: python_packages (Sequence[str], optional): 函数所需的Python包。 可包含版本说明(如["pandas>=1.0.0"])。默认为[]。 global_imports (Sequence[Import], optional): 函数所需的导入语句。 可以是字符串("numpy")、ImportFromModule对象或Alias对象。默认为[]。 Returns: Callable[[Callable[P, T]], FunctionWithRequirements[T, P]]: 一个装饰器, 包装目标函数的同时保留其功能并注册其依赖项。 Example: .. code-block:: python import tempfile import asyncio from autogen_core import CancellationToken from autogen_core.code_executor import with_requirements, CodeBlock from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor import pandas @with_requirements(python_packages=["pandas"], global_imports=["pandas"]) def load_data() -> pandas.DataFrame: \"\"\"加载示例数据。 Returns: pandas.DataFrame: 包含示例数据的DataFrame \"\"\" data = { "name": ["John", "Anna", "Peter", "Linda"], "location": ["New York", "Paris", "Berlin", "London"], "age": [24, 13, 53, 33], } return pandas.DataFrame(data) async def run_example(): # 被装饰的函数可在执行代码中使用 with tempfile.TemporaryDirectory() as temp_dir: executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, functions=[load_data]) code = f\"\"\"from {executor.functions_module} import load_data # 使用导入的函数 data = load_data() print(data['name'][0])\"\"\" result = await executor.execute_code_blocks( code_blocks=[CodeBlock(language="python", code=code)], cancellation_token=CancellationToken(), ) print(result.output) # 输出: John # 运行异步示例 asyncio.run(run_example()) """ def wrapper(func: Callable[P, T]) -> FunctionWithRequirements[T, P]: func_with_reqs = FunctionWithRequirements( python_packages=python_packages, global_imports=global_imports, func=func ) functools.update_wrapper(func_with_reqs, func) return func_with_reqs return wrapper
def build_python_functions_file( funcs: Sequence[Union[FunctionWithRequirements[Any, P], Callable[..., Any], FunctionWithRequirementsStr]], ) -> str: """:meta private:""" # First collect all global imports global_imports: Set[Import] = set() for func in funcs: if isinstance(func, (FunctionWithRequirements, FunctionWithRequirementsStr)): global_imports.update(func.global_imports) content = "\n".join(map(_import_to_str, global_imports)) + "\n\n" for func in funcs: content += _to_code(func) + "\n\n" return content def to_stub(func: Union[Callable[..., Any], FunctionWithRequirementsStr]) -> str: """生成函数的存根字符串 Args: func (Callable[..., Any]): 需要生成存根的函数 Returns: str: 函数的存根 """ if isinstance(func, FunctionWithRequirementsStr): return to_stub(func.compiled_func) content = f"def {func.__name__}{inspect.signature(func)}:\n" docstring = func.__doc__ if docstring: docstring = dedent(docstring) docstring = '"""' + docstring + '"""' docstring = indent(docstring, " ") content += docstring + "\n" content += " ..." return content def to_code(func: Union[FunctionWithRequirements[T, P], Callable[P, T], FunctionWithRequirementsStr]) -> str: return _to_code(func) def import_to_str(im: Import) -> str: return _import_to_str(im)