Module livekit.agents.llm.function_context

Functions

def ai_callable(*,
name: str | None = None,
description: str | _UseDocMarker = <livekit.agents.llm.function_context._UseDocMarker object>,
auto_retry: bool = False) ‑> Callable
Expand source code
def ai_callable(
    *,
    name: str | None = None,
    description: str | _UseDocMarker = USE_DOCSTRING,
    auto_retry: bool = False,
) -> Callable:
    def deco(f):
        _set_metadata(f, name=name, desc=description, auto_retry=auto_retry)
        return f

    return deco
def is_type_supported(t: type) ‑> bool
Expand source code
def is_type_supported(t: type) -> bool:
    if t in (str, int, float, bool):
        return True

    if typing.get_origin(t) is list:
        in_type = typing.get_args(t)[0]
        return is_type_supported(in_type)

    is_optional, ty = _is_optional_type(t)
    if is_optional:
        return is_type_supported(ty)

    if issubclass(t, enum.Enum):
        initial_type = None
        for e in t:
            if initial_type is None:
                initial_type = type(e.value)
            if type(e.value) is not initial_type:
                return False

        return initial_type in (str, int)

    return False

Classes

class CalledFunction (call_info: FunctionCallInfo,
task: asyncio.Task[Any],
result: Any | None = None,
exception: BaseException | None = None)
Expand source code
@dataclass
class CalledFunction:
    call_info: FunctionCallInfo
    task: asyncio.Task[Any]
    result: Any | None = None
    exception: BaseException | None = None

CalledFunction(call_info: 'FunctionCallInfo', task: 'asyncio.Task[Any]', result: 'Any | None' = None, exception: 'BaseException | None' = None)

Class variables

var call_infoFunctionCallInfo
var exception : BaseException | None
var result : typing.Any | None
var task : _asyncio.Task[typing.Any]
class FunctionArgInfo (name: str, description: str, type: type, default: Any, choices: tuple | None)
Expand source code
@dataclass(frozen=True)
class FunctionArgInfo:
    name: str
    description: str
    type: type
    default: Any
    choices: tuple | None

FunctionArgInfo(name: 'str', description: 'str', type: 'type', default: 'Any', choices: 'tuple | None')

Class variables

var choices : tuple | None
var default : Any
var description : str
var name : str
var type : type
class FunctionCallInfo (tool_call_id: str,
function_info: FunctionInfo,
raw_arguments: str,
arguments: dict[str, Any])
Expand source code
@dataclass(frozen=True)
class FunctionCallInfo:
    tool_call_id: str
    function_info: FunctionInfo
    raw_arguments: str
    arguments: dict[str, Any]

    def execute(self) -> CalledFunction:
        function_info = self.function_info
        func = functools.partial(function_info.callable, **self.arguments)
        if asyncio.iscoroutinefunction(function_info.callable):
            task = asyncio.create_task(func())
        else:
            task = asyncio.create_task(asyncio.to_thread(func))

        called_fnc = CalledFunction(call_info=self, task=task)

        def _on_done(fut):
            try:
                called_fnc.result = fut.result()
            except BaseException as e:
                called_fnc.exception = e

        task.add_done_callback(_on_done)
        return called_fnc

FunctionCallInfo(tool_call_id: 'str', function_info: 'FunctionInfo', raw_arguments: 'str', arguments: 'dict[str, Any]')

Class variables

var arguments : dict[str, typing.Any]
var function_infoFunctionInfo
var raw_arguments : str
var tool_call_id : str

Methods

def execute(self) ‑> CalledFunction
Expand source code
def execute(self) -> CalledFunction:
    function_info = self.function_info
    func = functools.partial(function_info.callable, **self.arguments)
    if asyncio.iscoroutinefunction(function_info.callable):
        task = asyncio.create_task(func())
    else:
        task = asyncio.create_task(asyncio.to_thread(func))

    called_fnc = CalledFunction(call_info=self, task=task)

    def _on_done(fut):
        try:
            called_fnc.result = fut.result()
        except BaseException as e:
            called_fnc.exception = e

    task.add_done_callback(_on_done)
    return called_fnc
class FunctionContext
Expand source code
class FunctionContext:
    def __init__(self) -> None:
        self._fncs = dict[str, FunctionInfo]()

        for _, member in inspect.getmembers(self, predicate=inspect.ismethod):
            if hasattr(member, METADATA_ATTR):
                self._register_ai_function(member)

    def ai_callable(
        self,
        *,
        name: str | None = None,
        description: str | _UseDocMarker = USE_DOCSTRING,
        auto_retry: bool = True,
    ) -> Callable:
        def deco(f):
            _set_metadata(f, name=name, desc=description, auto_retry=auto_retry)
            self._register_ai_function(f)

        return deco

    def _register_ai_function(self, fnc: Callable) -> None:
        if not hasattr(fnc, METADATA_ATTR):
            logger.warning(f"function {fnc.__name__} does not have ai metadata")
            return

        metadata: _AIFncMetadata = getattr(fnc, METADATA_ATTR)
        fnc_name = metadata.name
        if fnc_name in self._fncs:
            raise ValueError(f"duplicate ai_callable name: {fnc_name}")

        sig = inspect.signature(fnc)

        # get_type_hints with include_extra=True is needed when using Annotated
        # using typing.get_args with param.Annotated is returning an empty tuple for some reason
        type_hints = typing.get_type_hints(
            fnc, include_extras=True
        )  # Annotated[T, ...] -> T
        args = dict[str, FunctionArgInfo]()

        for name, param in sig.parameters.items():
            if param.kind not in (
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                inspect.Parameter.KEYWORD_ONLY,
            ):
                raise ValueError(f"{fnc_name}: unsupported parameter kind {param.kind}")

            inner_th, type_info = _extract_types(type_hints[name])

            if not is_type_supported(inner_th):
                raise ValueError(
                    f"{fnc_name}: unsupported type {inner_th} for parameter {name}"
                )

            desc = type_info.description if type_info else ""
            choices = type_info.choices if type_info else ()

            if (
                isinstance(inner_th, type)
                and issubclass(inner_th, enum.Enum)
                and not choices
            ):
                # the enum must be a str or int (and at least one value)
                # this is verified by is_type_supported
                choices = tuple([item.value for item in inner_th])
                inner_th = type(choices[0])

            args[name] = FunctionArgInfo(
                name=name,
                description=desc,
                type=inner_th,
                default=param.default,
                choices=choices,
            )

        self._fncs[metadata.name] = FunctionInfo(
            name=metadata.name,
            description=metadata.description,
            auto_retry=metadata.auto_retry,
            callable=fnc,
            arguments=args,
        )

    @property
    def ai_functions(self) -> dict[str, FunctionInfo]:
        return self._fncs

Instance variables

prop ai_functions : dict[str, FunctionInfo]
Expand source code
@property
def ai_functions(self) -> dict[str, FunctionInfo]:
    return self._fncs

Methods

def ai_callable(self,
*,
name: str | None = None,
description: str | _UseDocMarker = <livekit.agents.llm.function_context._UseDocMarker object>,
auto_retry: bool = True) ‑> Callable
Expand source code
def ai_callable(
    self,
    *,
    name: str | None = None,
    description: str | _UseDocMarker = USE_DOCSTRING,
    auto_retry: bool = True,
) -> Callable:
    def deco(f):
        _set_metadata(f, name=name, desc=description, auto_retry=auto_retry)
        self._register_ai_function(f)

    return deco
class FunctionInfo (name: str,
description: str,
auto_retry: bool,
callable: Callable,
arguments: dict[str, FunctionArgInfo])
Expand source code
@dataclass(frozen=True)
class FunctionInfo:
    name: str
    description: str
    auto_retry: bool
    callable: Callable
    arguments: dict[str, FunctionArgInfo]

FunctionInfo(name: 'str', description: 'str', auto_retry: 'bool', callable: 'Callable', arguments: 'dict[str, FunctionArgInfo]')

Class variables

var arguments : dict[str, FunctionArgInfo]
var auto_retry : bool
var callable : Callable
var description : str
var name : str
class TypeInfo (description: str, choices: tuple | list[Any] = ())
Expand source code
@dataclass(frozen=True, init=False)
class TypeInfo:
    description: str
    choices: tuple

    def __init__(self, description: str, choices: tuple | list[Any] = tuple()) -> None:
        object.__setattr__(self, "description", description)

        if isinstance(choices, list):
            choices = tuple(choices)

        object.__setattr__(self, "choices", choices)

TypeInfo(description: 'str', choices: 'tuple | list[Any]' = ()) -> 'None'

Class variables

var choices : tuple
var description : str