Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ src/smallestai/waves/helpers/**

# Hand-written README (do not regenerate)
README.md

# Optional framework adapters (hand-written)
src/smallestai/integrations/**
9 changes: 9 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 5.6.0 - 2026-08-06

* **integrations**: optional framework adapters. `from smallestai.integrations.pipecat
import SmallestSTTService` and `from smallestai.integrations.livekit import TTS` now
re-export the framework-native Smallest AI plugins from the SDK namespace. The core
package does not depend on pipecat or livekit; install the extra you need
(`pip install "smallestai[pipecat]"` / `"smallestai[livekit]"`). Adapters lazy-import
their framework and raise a clear "install the extra" error otherwise.

## 5.5.0 - 2026-08-05

DevX pass (backward-compatible).
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dynamic = ["version"]

[tool.poetry]
name = "smallestai"
version = "5.5.0"
version = "5.6.0"
description = ""
readme = "README.md"
authors = []
Expand Down Expand Up @@ -56,6 +56,14 @@ rich = ">=14.2.0"
questionary = ">=2.1.1"
tomli = ">=2.3.0"
tomli-w = ">=1.2.0"
# Optional framework adapters (smallestai.integrations.*). Installed only via extras,
# never pulled into a core install. See `[tool.poetry.extras]` below.
pipecat-ai = { version = "*", optional = true, extras = ["smallest"] }
livekit-plugins-smallestai = { version = "*", optional = true }

[tool.poetry.extras]
pipecat = ["pipecat-ai"]
livekit = ["livekit-plugins-smallestai"]

[tool.poetry.scripts]
smallestai = "smallestai.cli.main:main"
Expand Down
10 changes: 10 additions & 0 deletions src/smallestai/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Optional adapters for third-party voice-agent frameworks.

These are thin, lazy re-exports of the framework-native Smallest AI plugins, so you
can reach them from the SDK namespace without the core SDK depending on those
frameworks. Each adapter imports its framework only on first use and raises a clear
"install the extra" error otherwise.

from smallestai.integrations.pipecat import SmallestSTTService # needs smallestai[pipecat]
from smallestai.integrations.livekit import TTS # needs smallestai[livekit]
"""
46 changes: 46 additions & 0 deletions src/smallestai/integrations/livekit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Lazy adapter for the Smallest AI LiveKit plugin.

Re-exports ``livekit.plugins.smallestai`` under the SDK namespace:

from smallestai.integrations.livekit import TTS

The core ``smallestai`` package does not depend on LiveKit. Install the extra to use
this adapter::

pip install "smallestai[livekit]"

Anything the framework plugin exposes is available here; the names forward to
``livekit.plugins.smallestai``.
"""

from __future__ import annotations

import typing

_INSTALL_HINT = 'pip install "smallestai[livekit]" (or: pip install livekit-plugins-smallestai)'


def _load() -> typing.Any:
try:
import livekit.plugins.smallestai as _mod # type: ignore[import-not-found]
except ImportError as exc: # pragma: no cover - exercised via the missing-dep test
raise ImportError(
"The livekit integration requires livekit-plugins-smallestai. Install it with:\n "
+ _INSTALL_HINT
) from exc
return _mod


def __getattr__(name: str) -> typing.Any:
module = _load()
try:
return getattr(module, name)
except AttributeError as exc:
raise AttributeError(f"'livekit.plugins.smallestai' has no attribute {name!r}") from exc


def __dir__() -> typing.List[str]:
try:
return sorted(dir(_load()))
except ImportError:
return []
45 changes: 45 additions & 0 deletions src/smallestai/integrations/pipecat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Lazy adapter for the Smallest AI pipecat services.

Re-exports ``pipecat.services.smallest`` under the SDK namespace:

from smallestai.integrations.pipecat import SmallestSTTService, SmallestTTSService

The core ``smallestai`` package does not depend on pipecat. Install the extra to use
this adapter::

pip install "smallestai[pipecat]"

Anything the framework plugin exposes is available here; the names simply forward to
``pipecat.services.smallest``.
"""

from __future__ import annotations

import typing

_INSTALL_HINT = 'pip install "smallestai[pipecat]" (or: pip install "pipecat-ai[smallest]")'


def _load() -> typing.Any:
try:
import pipecat.services.smallest as _mod # type: ignore[import-not-found]
except ImportError as exc: # pragma: no cover - exercised via the missing-dep test
raise ImportError(
"The pipecat integration requires pipecat-ai. Install it with:\n " + _INSTALL_HINT
) from exc
return _mod


def __getattr__(name: str) -> typing.Any:
module = _load()
try:
return getattr(module, name)
except AttributeError as exc:
raise AttributeError(f"'pipecat.services.smallest' has no attribute {name!r}") from exc


def __dir__() -> typing.List[str]:
try:
return sorted(dir(_load()))
except ImportError:
return []
55 changes: 55 additions & 0 deletions tests/custom/test_integrations_adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""smallestai.integrations.* lazy framework adapters.

Importing an adapter module must never require the framework. Accessing a name
either forwards to the framework plugin (if installed) or raises a clear
"install the extra" ImportError.
"""
import importlib

import pytest


def _framework_installed(module_path: str) -> bool:
try:
importlib.import_module(module_path)
return True
except ImportError:
return False


def test_importing_adapter_modules_never_requires_framework():
# Must import cleanly whether or not pipecat/livekit are present.
importlib.import_module("smallestai.integrations.pipecat")
importlib.import_module("smallestai.integrations.livekit")


def test_pipecat_adapter_forwards_or_raises_clear_error():
import smallestai.integrations.pipecat as pc

if _framework_installed("pipecat.services.smallest"):
import pipecat.services.smallest as real

assert pc.SmallestSTTService is real.SmallestSTTService
assert "SmallestSTTService" in dir(pc)
else:
with pytest.raises(ImportError) as ei:
_ = pc.SmallestSTTService
assert 'smallestai[pipecat]' in str(ei.value)
assert dir(pc) == []


def test_livekit_adapter_forwards_or_raises_clear_error():
import smallestai.integrations.livekit as lk

if _framework_installed("livekit.plugins.smallestai"):
import livekit.plugins.smallestai as real

assert lk.TTS is real.TTS
else:
with pytest.raises(ImportError) as ei:
_ = lk.TTS
assert 'smallestai[livekit]' in str(ei.value)


if __name__ == "__main__":
pytest.main([__file__, "-v"])
Loading