Skip to content

Avatar Agent

avatar

Avatar download and validation for user profiles.

Simplified: URL-only format, saved to media/images/ like regular media. Avatars go through regular media enrichment pipeline (no special moderation).

AvatarProcessingError

Bases: EgregoraError

Error during avatar processing.

AvatarContext dataclass

AvatarContext(
    docs_dir: Path,
    media_dir: Path,
    profiles_dir: Path,
    vision_model: str,
    avatar_namespace: UUID,
    cache: EnrichmentCache | None = None,
)

Context for avatar processing operations.

download_avatar_from_url

download_avatar_from_url(
    url: str,
    media_dir: Path,
    namespace: UUID,
    timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
    client: Client | None = None,
) -> tuple[uuid.UUID, Path]

Download avatar from URL and save to avatars directory.

Parameters:

Name Type Description Default
url str

URL of the avatar image

required
media_dir Path

Root media directory (e.g., site_root/media)

required
namespace UUID

UUID namespace for avatar generation

required
timeout float

HTTP timeout in seconds

DEFAULT_DOWNLOAD_TIMEOUT
client Client | None

Optional httpx.Client to reuse

None

Returns:

Type Description
tuple[UUID, Path]

Tuple of (avatar_uuid, avatar_path)

Raises:

Type Description
AvatarProcessingError

If download fails or image is invalid

Source code in src/egregora/agents/avatar.py
@sleep_and_retry
@limits(calls=10, period=60)
def download_avatar_from_url(
    url: str,
    media_dir: Path,
    namespace: uuid.UUID,
    timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
    client: httpx.Client | None = None,
) -> tuple[uuid.UUID, Path]:
    """Download avatar from URL and save to avatars directory.

    Args:
        url: URL of the avatar image
        media_dir: Root media directory (e.g., site_root/media)
        namespace: UUID namespace for avatar generation
        timeout: HTTP timeout in seconds
        client: Optional httpx.Client to reuse

    Returns:
        Tuple of (avatar_uuid, avatar_path)

    Raises:
        AvatarProcessingError: If download fails or image is invalid

    """
    try:
        with safe_dns_validation(url):
            if client:
                return _download_avatar_with_client(client, url, media_dir, namespace)

            with _create_secure_client(timeout) as new_client:
                return _download_avatar_with_client(new_client, url, media_dir, namespace)
    except SSRFValidationError as exc:
        raise AvatarProcessingError(str(exc)) from exc

process_avatar_commands

process_avatar_commands(
    messages_table: Table, context: AvatarContext
) -> dict[str, str]

Process all avatar commands from messages table.

Source code in src/egregora/agents/avatar.py
def process_avatar_commands(
    messages_table: Table,
    context: AvatarContext,
) -> dict[str, str]:
    """Process all avatar commands from messages table."""
    logger.info("Processing avatar commands from messages")
    commands = extract_commands(messages_table)
    avatar_commands = [cmd for cmd in commands if cmd.get("command", {}).get("target") == "avatar"]
    if not avatar_commands:
        logger.info("No avatar commands found")
        return {}

    logger.info("Found %s avatar command(s)", len(avatar_commands))
    results: dict[str, str] = {}

    with _create_secure_client() as client:
        for cmd_entry in avatar_commands:
            author_uuid = cmd_entry["author"]
            timestamp_raw = cmd_entry["timestamp"]
            command = cmd_entry["command"]
            cmd_type = command["command"]
            target = command["target"]
            if cmd_type in ("set", "unset") and target == "avatar":
                if cmd_type == "set":
                    timestamp_dt = ensure_datetime(timestamp_raw)
                    result = _process_set_avatar_command(
                        author_uuid=author_uuid,
                        timestamp=timestamp_dt,
                        context=context,
                        value=command.get("value"),
                        client=client,
                    )
                    results[author_uuid] = result
                elif cmd_type == "unset":
                    result = _process_unset_avatar_command(
                        author_uuid=author_uuid,
                        timestamp=str(timestamp_raw),
                        profiles_dir=context.profiles_dir,
                    )
                    results[author_uuid] = result
    return results