Repository: Significant-Gravitas/Auto-GPT
File: autogpt_platform/backend/backend/util/service.py (Python)

Code before:

        :param func: The original function (sync/async, bound or unbound)
        :return: A FastAPI endpoint function.
        """
        sig = inspect.signature(func)
        fields = {}

        is_bounded_method = False
        for name, param in sig.parameters.items():
            if name in ("self", "cls"):
                is_bounded_method = True
                continue

            # Use the provided annotation or fallback to str if not specified
            annotation = (
                param.annotation if param.annotation != inspect.Parameter.empty else str
            )

            # If a default value is provided, use it; otherwise, mark the field as required with '...'
            default = param.default if param.default != inspect.Parameter.empty else ...

            fields[name] = (annotation, default)

        # Dynamically create a Pydantic model for the request body
        RequestBodyModel = create_model("RequestBodyModel", **fields)
        f = func.__get__(self) if is_bounded_method else func

        if asyncio.iscoroutinefunction(f):

            async def async_endpoint(body: RequestBodyModel):  # type: ignore
                try:
                    return await f(
                        **{name: getattr(body, name) for name in body.model_fields}
                    )
                except Exception as e:
                    logger.exception(f"Error in {func.__name__}: {e}")
                    return responses.JSONResponse(status_code=500, content=e)

            return async_endpoint
        else:

            def sync_endpoint(body: RequestBodyModel):  # type: ignore
                try:
                    return f(
                        **{name: getattr(body, name) for name in body.model_fields}
                    )
                except Exception as e:
                    logger.exception(f"Error in {func.__name__}: {e}")
                    return responses.JSONResponse(status_code=500, content=e)

            return sync_endpoint

Review comment (suggestion):
```suggestion
        f = func.__get__(self) if is_bound_method else func
```

Code after:

# ----- Begin Pyro Expose Block ---- #
def pyro_expose(func: C) -> C:
    """
    Decorator to mark a method or class to be exposed for remote calls.

    ## ⚠️ Gotcha
    Aside from "simple" types, only Pydantic models are passed unscathed *if annotated*.
    Any other passed or returned class objects are converted to dictionaries by Pyro.
    """

    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            msg = f"Error in {func.__name__}: {e}"
            if isinstance(e, ValueError):
                logger.warning(msg)
            else:
                logger.exception(msg)
            raise

    register_pydantic_serializers(func)

    return pyro.expose(wrapper)  # type: ignore


def register_pydantic_serializers(func: Callable):
    """Register custom serializers and deserializers for annotated Pydantic models"""
    for name, annotation in func.__annotations__.items():
        try:
            pydantic_types = _pydantic_models_from_type_annotation(annotation)
        except Exception as e:
            raise TypeError(f"Error while exposing {func.__name__}: {e}")

        for model in pydantic_types:
            logger.debug(
                f"Registering Pyro (de)serializers for {func.__name__} annotation "
                f"'{name}': {model.__qualname__}"
            )
            pyro.register_class_to_dict(model, _make_custom_serializer(model))
            pyro.register_dict_to_class(
                model.__qualname__, _make_custom_deserializer(model)
            )


def _make_custom_serializer(model: Type[BaseModel]):
    def custom_class_to_dict(obj):
        data = {
            "__class__": obj.__class__.__qualname__,
            **obj.model_dump(),