LemonSlice transport updates
This commit is contained in:
4
changelog/3995.added.md
Normal file
4
changelog/3995.added.md
Normal file
@@ -0,0 +1,4 @@
|
||||
- Updated LemonSlice transport:
|
||||
- Added `on_avatar_connected` and `on_avatar_disconnected` events triggered when the avatar joins and leaves the room
|
||||
- Added `api_url` parameter to `LemonSliceNewSessionRequest` to allow overriding the LemonSlice API endpoint
|
||||
- Added support for passing arbitrary named parameters to the LemonSlice API endpoint
|
||||
@@ -114,6 +114,14 @@ async def main():
|
||||
logger.info("Client disconnected")
|
||||
await task.cancel()
|
||||
|
||||
@transport.event_handler("on_avatar_connected")
|
||||
async def on_avatar_connected(transport, participant):
|
||||
logger.info("Avatar connected")
|
||||
|
||||
@transport.event_handler("on_avatar_disconnected")
|
||||
async def on_avatar_disconnected(transport, participant, reason):
|
||||
logger.info(f"Avatar disconnected. Reason: {reason}")
|
||||
|
||||
runner = PipelineRunner()
|
||||
|
||||
await runner.run(task)
|
||||
|
||||
@@ -44,7 +44,9 @@ class LemonSliceApi:
|
||||
idle_timeout: Optional[int] = None,
|
||||
daily_room_url: Optional[str] = None,
|
||||
daily_token: Optional[str] = None,
|
||||
properties: Optional[dict[str, Any]] = None,
|
||||
connection_properties: Optional[dict[str, Any]] = None,
|
||||
extra_properties: Optional[dict[str, Any]] = None,
|
||||
api_url: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Create a new session with the specified agent_id or agent_image_url.
|
||||
|
||||
@@ -55,7 +57,9 @@ class LemonSliceApi:
|
||||
idle_timeout: Idle timeout in seconds.
|
||||
daily_room_url: Daily room URL to use for the session.
|
||||
daily_token: Daily token for authenticating with the room.
|
||||
properties: Additional properties to pass to the session.
|
||||
connection_properties: Additional connection properties to pass to the session.
|
||||
extra_properties: Additional properties to pass to the session.
|
||||
api_url: LemonSlice API URL override.
|
||||
|
||||
Returns:
|
||||
Dictionary containing session_id, room_url, and control_url.
|
||||
@@ -64,16 +68,14 @@ class LemonSliceApi:
|
||||
ValueError: If neither agent_id nor agent_image_url is provided.
|
||||
"""
|
||||
if not agent_id and not agent_image_url:
|
||||
# Fallback to a default agent if none is provided
|
||||
logger.debug("No agent_id or agent_image_url provided, using default agent")
|
||||
agent_id = "agent_080308d8b6e99f47"
|
||||
raise ValueError("Provide an agent_id or agent_image_url")
|
||||
if agent_id and agent_image_url:
|
||||
raise ValueError("Provide exactly one of agent_id or agent_image_url, not both")
|
||||
|
||||
logger.debug(
|
||||
f"Creating LemonSlice session: agent_id={agent_id}, agent_image_url={agent_image_url}"
|
||||
)
|
||||
payload: dict[str, object] = {"transport_type": "daily"}
|
||||
payload: dict[str, Any] = {"transport_type": "daily"}
|
||||
if agent_id is not None:
|
||||
payload["agent_id"] = agent_id
|
||||
if agent_image_url is not None:
|
||||
@@ -82,16 +84,19 @@ class LemonSliceApi:
|
||||
payload["agent_prompt"] = agent_prompt
|
||||
if idle_timeout is not None:
|
||||
payload["idle_timeout"] = idle_timeout
|
||||
properties_dict: dict[str, Any] = dict(properties) if properties else {}
|
||||
properties_dict: dict[str, Any] = (
|
||||
dict(connection_properties) if connection_properties else {}
|
||||
)
|
||||
if daily_room_url is not None:
|
||||
properties_dict["daily_url"] = daily_room_url
|
||||
if daily_token is not None:
|
||||
properties_dict["daily_token"] = daily_token
|
||||
if properties_dict:
|
||||
payload["properties"] = properties_dict
|
||||
async with self._session.post(
|
||||
self.LEMONSLICE_URL, headers=self._headers, json=payload
|
||||
) as r:
|
||||
if extra_properties:
|
||||
payload.update(extra_properties)
|
||||
url = api_url if api_url is not None else self.LEMONSLICE_URL
|
||||
async with self._session.post(url, headers=self._headers, json=payload) as r:
|
||||
r.raise_for_status()
|
||||
response = await r.json()
|
||||
logger.debug(f"Created LemonSlice session: {response}")
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import Any, Awaitable, Callable, Mapping, Optional
|
||||
import aiohttp
|
||||
from daily.daily import AudioData
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
BotStartedSpeakingFrame,
|
||||
@@ -54,9 +54,12 @@ class LemonSliceNewSessionRequest(BaseModel):
|
||||
idle_timeout: Idle timeout in seconds.
|
||||
daily_room_url: Daily room URL to use for the session.
|
||||
daily_token: Daily token for authenticating with the room.
|
||||
lemonslice_properties: Additional properties to pass to the session.
|
||||
lemonslice_properties: Additional connection properties to pass to the session.
|
||||
api_url: Override the LemonSlice API URL.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
agent_image_url: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
agent_prompt: Optional[str] = None
|
||||
@@ -64,6 +67,7 @@ class LemonSliceNewSessionRequest(BaseModel):
|
||||
daily_room_url: Optional[str] = None
|
||||
daily_token: Optional[str] = None
|
||||
lemonslice_properties: Optional[dict] = None
|
||||
api_url: Optional[str] = None
|
||||
|
||||
|
||||
class LemonSliceCallbacks(BaseModel):
|
||||
@@ -135,6 +139,8 @@ class LemonSliceTransportClient:
|
||||
|
||||
async def _initialize(self) -> str:
|
||||
"""Initialize the conversation and return the room URL."""
|
||||
connection_properties = dict(self._session_request.lemonslice_properties or {})
|
||||
extra_properties = self._session_request.model_extra
|
||||
response = await self._api.create_session(
|
||||
agent_image_url=self._session_request.agent_image_url,
|
||||
agent_id=self._session_request.agent_id,
|
||||
@@ -142,7 +148,9 @@ class LemonSliceTransportClient:
|
||||
idle_timeout=self._session_request.idle_timeout,
|
||||
daily_room_url=self._session_request.daily_room_url,
|
||||
daily_token=self._session_request.daily_token,
|
||||
properties=self._session_request.lemonslice_properties,
|
||||
connection_properties=connection_properties if connection_properties else None,
|
||||
extra_properties=extra_properties if extra_properties else None,
|
||||
api_url=self._session_request.api_url,
|
||||
)
|
||||
self._session_id = response["session_id"]
|
||||
self._control_url = response["control_url"]
|
||||
@@ -669,6 +677,8 @@ class LemonSliceTransport(BaseTransport):
|
||||
|
||||
- on_client_connected(transport, participant): Participant connected to the session
|
||||
- on_client_disconnected(transport, participant): Participant disconnected from the session
|
||||
- on_avatar_connected(transport, participant): LemonSlice avatar connected to the session
|
||||
- on_avatar_disconnected(transport, participant, reason): LemonSlice avatar disconnected from the session
|
||||
|
||||
Example::
|
||||
|
||||
@@ -722,11 +732,15 @@ class LemonSliceTransport(BaseTransport):
|
||||
# these handlers.
|
||||
self._register_event_handler("on_client_connected")
|
||||
self._register_event_handler("on_client_disconnected")
|
||||
self._register_event_handler("on_avatar_connected")
|
||||
self._register_event_handler("on_avatar_disconnected")
|
||||
|
||||
async def _on_participant_left(self, participant, reason):
|
||||
"""Handle participant left events."""
|
||||
ls_bot_name = await self._client.get_bot_name()
|
||||
if participant.get("info", {}).get("userName", "") != ls_bot_name:
|
||||
if participant.get("info", {}).get("userName", "") == ls_bot_name:
|
||||
await self._on_avatar_disconnected(participant, reason)
|
||||
else:
|
||||
await self._on_client_disconnected(participant)
|
||||
|
||||
async def _on_participant_joined(self, participant):
|
||||
@@ -736,6 +750,7 @@ class LemonSliceTransport(BaseTransport):
|
||||
# Ignore the LemonSlice bot's microphone
|
||||
if participant.get("info", {}).get("userName", "") == ls_bot_name:
|
||||
self._lemonslice_participant_id = participant["id"]
|
||||
await self._on_avatar_connected(participant)
|
||||
else:
|
||||
await self._on_client_connected(participant)
|
||||
if self._lemonslice_participant_id:
|
||||
@@ -782,6 +797,14 @@ class LemonSliceTransport(BaseTransport):
|
||||
self._output = LemonSliceOutputTransport(client=self._client, params=self._params)
|
||||
return self._output
|
||||
|
||||
async def _on_avatar_connected(self, participant: Any):
|
||||
"""Handle avatar connected events."""
|
||||
await self._call_event_handler("on_avatar_connected", participant)
|
||||
|
||||
async def _on_avatar_disconnected(self, participant: Any, reason: str):
|
||||
"""Handle avatar disconnected events."""
|
||||
await self._call_event_handler("on_avatar_disconnected", participant, reason)
|
||||
|
||||
async def _on_client_connected(self, participant: Any):
|
||||
"""Handle client connected events."""
|
||||
await self._call_event_handler("on_client_connected", participant)
|
||||
|
||||
Reference in New Issue
Block a user