getting started
This commit is contained in:
@@ -19,6 +19,7 @@ dependencies = [
|
|||||||
"pyht",
|
"pyht",
|
||||||
"python-dotenv",
|
"python-dotenv",
|
||||||
"torch",
|
"torch",
|
||||||
|
"torchaudio",
|
||||||
"pyaudio",
|
"pyaudio",
|
||||||
"typing-extensions"
|
"typing-extensions"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import asyncio
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
from typing import AsyncGenerator, Awaitable, Callable
|
from typing import AsyncGenerator, Awaitable, Callable
|
||||||
from dailyai.queue_aggregators import LLMAssistantContextAggregator, LLMContextAggregator, LLMUserContextAggregator
|
from dailyai.pipeline.aggregators import LLMAssistantContextAggregator, LLMContextAggregator, LLMUserContextAggregator
|
||||||
from dailyai.queue_frame import EndStreamQueueFrame, QueueFrame, TranscriptionQueueFrame
|
from dailyai.pipeline.frames import EndStreamQueueFrame, QueueFrame, TranscriptionQueueFrame
|
||||||
|
|
||||||
|
|
||||||
class InterruptibleConversationWrapper:
|
class InterruptibleConversationWrapper:
|
||||||
|
|||||||
182
src/dailyai/pipeline/aggregators.py
Normal file
182
src/dailyai/pipeline/aggregators.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
|
||||||
|
from tblib import Frame
|
||||||
|
from dailyai.pipeline.frame_processor import FrameProcessor
|
||||||
|
|
||||||
|
from dailyai.pipeline.frames import (
|
||||||
|
ControlQueueFrame,
|
||||||
|
EndStreamQueueFrame,
|
||||||
|
LLMMessagesQueueFrame,
|
||||||
|
QueueFrame,
|
||||||
|
TextQueueFrame,
|
||||||
|
TranscriptionQueueFrame,
|
||||||
|
)
|
||||||
|
from dailyai.pipeline.pipeline import Pipeline
|
||||||
|
from dailyai.services.ai_services import AIService
|
||||||
|
|
||||||
|
from typing import AsyncGenerator, List
|
||||||
|
|
||||||
|
|
||||||
|
class LLMContextAggregator(AIService):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
messages: list[dict],
|
||||||
|
role: str,
|
||||||
|
bot_participant_id=None,
|
||||||
|
complete_sentences=True,
|
||||||
|
pass_through=True,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.messages = messages
|
||||||
|
self.bot_participant_id = bot_participant_id
|
||||||
|
self.role = role
|
||||||
|
self.sentence = ""
|
||||||
|
self.complete_sentences = complete_sentences
|
||||||
|
self.pass_through = pass_through
|
||||||
|
|
||||||
|
async def process_frame(
|
||||||
|
self, frame: QueueFrame
|
||||||
|
) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
# We don't do anything with non-text frames, pass it along to next in the pipeline.
|
||||||
|
if not isinstance(frame, TextQueueFrame):
|
||||||
|
yield frame
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ignore transcription frames from the bot
|
||||||
|
if isinstance(frame, TranscriptionQueueFrame):
|
||||||
|
if frame.participantId == self.bot_participant_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
# The common case for "pass through" is receiving frames from the LLM that we'll
|
||||||
|
# use to update the "assistant" LLM messages, but also passing the text frames
|
||||||
|
# along to a TTS service to be spoken to the user.
|
||||||
|
if self.pass_through:
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
# TODO: split up transcription by participant
|
||||||
|
if self.complete_sentences:
|
||||||
|
# type: ignore -- the linter thinks this isn't a TextQueueFrame, even
|
||||||
|
# though we check it above
|
||||||
|
self.sentence += frame.text
|
||||||
|
if self.sentence.endswith((".", "?", "!")):
|
||||||
|
self.messages.append({"role": self.role, "content": self.sentence})
|
||||||
|
self.sentence = ""
|
||||||
|
yield LLMMessagesQueueFrame(self.messages)
|
||||||
|
else:
|
||||||
|
# type: ignore -- the linter thinks this isn't a TextQueueFrame, even
|
||||||
|
# though we check it above
|
||||||
|
self.messages.append({"role": self.role, "content": frame.text})
|
||||||
|
yield LLMMessagesQueueFrame(self.messages)
|
||||||
|
|
||||||
|
async def finalize(self) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
# Send any dangling words that weren't finished with punctuation.
|
||||||
|
if self.complete_sentences and self.sentence:
|
||||||
|
self.messages.append({"role": self.role, "content": self.sentence})
|
||||||
|
yield LLMMessagesQueueFrame(self.messages)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMUserContextAggregator(LLMContextAggregator):
|
||||||
|
def __init__(
|
||||||
|
self, messages: list[dict], bot_participant_id=None, complete_sentences=True
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
messages, "user", bot_participant_id, complete_sentences, pass_through=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMAssistantContextAggregator(LLMContextAggregator):
|
||||||
|
def __init__(
|
||||||
|
self, messages: list[dict], bot_participant_id=None, complete_sentences=True
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
messages,
|
||||||
|
"assistant",
|
||||||
|
bot_participant_id,
|
||||||
|
complete_sentences,
|
||||||
|
pass_through=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SentenceAggregator(FrameProcessor):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.aggregation = ""
|
||||||
|
|
||||||
|
async def process_frame(
|
||||||
|
self, frame: QueueFrame
|
||||||
|
) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
if isinstance(frame, TextQueueFrame):
|
||||||
|
m = re.search("(.*[?.!])(.*)", frame.text)
|
||||||
|
if m:
|
||||||
|
yield TextQueueFrame(self.aggregation + m.group(1))
|
||||||
|
self.aggregation = m.group(2)
|
||||||
|
else:
|
||||||
|
self.aggregation += frame.text
|
||||||
|
elif isinstance(frame, EndStreamQueueFrame):
|
||||||
|
if self.aggregation:
|
||||||
|
yield TextQueueFrame(self.aggregation)
|
||||||
|
yield frame
|
||||||
|
else:
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
|
||||||
|
class StatelessTextTransformer(FrameProcessor):
|
||||||
|
def __init__(self, transform_fn):
|
||||||
|
self.transform_fn = transform_fn
|
||||||
|
|
||||||
|
async def process_frame(self, frame: QueueFrame) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
if isinstance(frame, TextQueueFrame):
|
||||||
|
yield TextQueueFrame(self.transform_fn(frame.text))
|
||||||
|
else:
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
class ParallelPipeline(FrameProcessor):
|
||||||
|
def __init__(self, pipeline_definitions: List[List[FrameProcessor]]):
|
||||||
|
self.sources = [asyncio.Queue() for _ in pipeline_definitions]
|
||||||
|
self.sink: asyncio.Queue[QueueFrame] = asyncio.Queue()
|
||||||
|
self.pipelines: list[Pipeline] = [
|
||||||
|
Pipeline(source, self.sink, pipeline_definition)
|
||||||
|
for source, pipeline_definition in zip(self.sources, pipeline_definitions)
|
||||||
|
]
|
||||||
|
|
||||||
|
async def process_frame(self, frame: QueueFrame) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
# Short circuit, because we use EndStreamQueueFrame for our own internal process control.
|
||||||
|
if isinstance(frame, EndStreamQueueFrame):
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
for source in self.sources:
|
||||||
|
await source.put(frame)
|
||||||
|
await source.put(EndStreamQueueFrame())
|
||||||
|
|
||||||
|
await asyncio.gather(*[pipeline.run_pipeline() for pipeline in self.pipelines])
|
||||||
|
|
||||||
|
while not self.sink.empty():
|
||||||
|
frame = await self.sink.get()
|
||||||
|
# Skip passing along EndStreamQueueFrames, because we use them for our own flow control.
|
||||||
|
if not isinstance(frame, EndStreamQueueFrame):
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
class GatedAccumulator(FrameProcessor):
|
||||||
|
def __init__(self, gate_open_fn, gate_close_fn, start_open):
|
||||||
|
self.gate_open_fn = gate_open_fn
|
||||||
|
self.gate_close_fn = gate_close_fn
|
||||||
|
self.gate_open = start_open
|
||||||
|
self.accumulator: List[QueueFrame] = []
|
||||||
|
|
||||||
|
async def process_frame(self, frame: QueueFrame) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
if self.gate_open:
|
||||||
|
if self.gate_close_fn(frame):
|
||||||
|
self.gate_open = False
|
||||||
|
else:
|
||||||
|
if self.gate_open_fn(frame):
|
||||||
|
self.gate_open = True
|
||||||
|
|
||||||
|
if self.gate_open:
|
||||||
|
yield frame
|
||||||
|
if self.accumulator:
|
||||||
|
for frame in self.accumulator:
|
||||||
|
yield frame
|
||||||
|
self.accumulator = []
|
||||||
|
else:
|
||||||
|
self.accumulator.append(frame)
|
||||||
24
src/dailyai/pipeline/frame_processor.py
Normal file
24
src/dailyai/pipeline/frame_processor.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
|
from dailyai.pipeline.frames import ControlQueueFrame, QueueFrame
|
||||||
|
|
||||||
|
|
||||||
|
class FrameProcessor:
|
||||||
|
@abstractmethod
|
||||||
|
async def process_frame(
|
||||||
|
self, frame: QueueFrame
|
||||||
|
) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
if isinstance(frame, ControlQueueFrame):
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def finalize(self) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
# This is a trick for the interpreter (and linter) to know that this is a generator.
|
||||||
|
if False:
|
||||||
|
yield QueueFrame()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def interrupted(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from enum import Enum
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class QueueFrame:
|
class QueueFrame:
|
||||||
pass
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, self.__class__)
|
||||||
|
|
||||||
|
|
||||||
class ControlQueueFrame(QueueFrame):
|
class ControlQueueFrame(QueueFrame):
|
||||||
@@ -19,6 +19,10 @@ class EndStreamQueueFrame(ControlQueueFrame):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LLMResponseStartQueueFrame(QueueFrame):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LLMResponseEndQueueFrame(QueueFrame):
|
class LLMResponseEndQueueFrame(QueueFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -61,6 +65,6 @@ class AppMessageQueueFrame(QueueFrame):
|
|||||||
|
|
||||||
class UserStartedSpeakingFrame(QueueFrame):
|
class UserStartedSpeakingFrame(QueueFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class UserStoppedSpeakingFrame(QueueFrame):
|
class UserStoppedSpeakingFrame(QueueFrame):
|
||||||
pass
|
pass
|
||||||
42
src/dailyai/pipeline/pipeline.py
Normal file
42
src/dailyai/pipeline/pipeline.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import AsyncGenerator, List
|
||||||
|
from dailyai.pipeline.frame_processor import FrameProcessor
|
||||||
|
|
||||||
|
from dailyai.pipeline.frames import EndStreamQueueFrame, QueueFrame
|
||||||
|
|
||||||
|
|
||||||
|
class Pipeline:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
source: asyncio.Queue,
|
||||||
|
sink: asyncio.Queue[QueueFrame],
|
||||||
|
processors: List[FrameProcessor],
|
||||||
|
):
|
||||||
|
self.source: asyncio.Queue[QueueFrame] = source
|
||||||
|
self.sink: asyncio.Queue[QueueFrame] = sink
|
||||||
|
self.processors = processors
|
||||||
|
|
||||||
|
async def get_next_source_frame(self) -> AsyncGenerator[QueueFrame, None]:
|
||||||
|
yield await self.source.get()
|
||||||
|
|
||||||
|
async def run_pipeline(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
frame_generators = [self.get_next_source_frame()]
|
||||||
|
for processor in self.processors:
|
||||||
|
next_frame_generators = []
|
||||||
|
for frame_generator in frame_generators:
|
||||||
|
async for frame in frame_generator:
|
||||||
|
next_frame_generators.append(processor.process_frame(frame))
|
||||||
|
frame_generators = next_frame_generators
|
||||||
|
|
||||||
|
for frame_generator in frame_generators:
|
||||||
|
async for frame in frame_generator:
|
||||||
|
await self.sink.put(frame)
|
||||||
|
if isinstance(frame, EndStreamQueueFrame):
|
||||||
|
return
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# this means there's been an interruption, do any cleanup necessary here.
|
||||||
|
for processor in self.processors:
|
||||||
|
await processor.interrupted()
|
||||||
|
pass
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
|
|
||||||
from dailyai.queue_frame import LLMMessagesQueueFrame, QueueFrame, TextQueueFrame, TranscriptionQueueFrame
|
|
||||||
from dailyai.services.ai_services import AIService
|
|
||||||
|
|
||||||
from typing import AsyncGenerator, List
|
|
||||||
|
|
||||||
|
|
||||||
class QueueTee:
|
|
||||||
async def run_to_queue_and_generate(
|
|
||||||
self,
|
|
||||||
output_queue: asyncio.Queue,
|
|
||||||
generator: AsyncGenerator[QueueFrame, None]
|
|
||||||
) -> AsyncGenerator[QueueFrame, None]:
|
|
||||||
async for frame in generator:
|
|
||||||
await output_queue.put(frame)
|
|
||||||
yield frame
|
|
||||||
|
|
||||||
async def run_to_queues(
|
|
||||||
self,
|
|
||||||
output_queues: List[asyncio.Queue],
|
|
||||||
generator: AsyncGenerator[QueueFrame, None]
|
|
||||||
):
|
|
||||||
async for frame in generator:
|
|
||||||
for queue in output_queues:
|
|
||||||
await queue.put(frame)
|
|
||||||
|
|
||||||
|
|
||||||
class LLMContextAggregator(AIService):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
messages: list[dict],
|
|
||||||
role: str,
|
|
||||||
bot_participant_id=None,
|
|
||||||
complete_sentences=True,
|
|
||||||
pass_through=True):
|
|
||||||
super().__init__()
|
|
||||||
self.messages = messages
|
|
||||||
self.bot_participant_id = bot_participant_id
|
|
||||||
self.role = role
|
|
||||||
self.sentence = ""
|
|
||||||
self.complete_sentences = complete_sentences
|
|
||||||
self.pass_through = pass_through
|
|
||||||
|
|
||||||
async def process_frame(self, frame: QueueFrame) -> AsyncGenerator[QueueFrame, None]:
|
|
||||||
# We don't do anything with non-text frames, pass it along to next in the pipeline.
|
|
||||||
if not isinstance(frame, TextQueueFrame):
|
|
||||||
yield frame
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ignore transcription frames from the bot
|
|
||||||
if isinstance(frame, TranscriptionQueueFrame):
|
|
||||||
if frame.participantId == self.bot_participant_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
# The common case for "pass through" is receiving frames from the LLM that we'll
|
|
||||||
# use to update the "assistant" LLM messages, but also passing the text frames
|
|
||||||
# along to a TTS service to be spoken to the user.
|
|
||||||
if self.pass_through:
|
|
||||||
yield frame
|
|
||||||
|
|
||||||
# TODO: split up transcription by participant
|
|
||||||
if self.complete_sentences:
|
|
||||||
# type: ignore -- the linter thinks this isn't a TextQueueFrame, even
|
|
||||||
# though we check it above
|
|
||||||
self.sentence += frame.text
|
|
||||||
if self.sentence.endswith((".", "?", "!")):
|
|
||||||
self.messages.append({"role": self.role, "content": self.sentence})
|
|
||||||
self.sentence = ""
|
|
||||||
yield LLMMessagesQueueFrame(self.messages)
|
|
||||||
else:
|
|
||||||
# type: ignore -- the linter thinks this isn't a TextQueueFrame, even
|
|
||||||
# though we check it above
|
|
||||||
self.messages.append({"role": self.role, "content": frame.text})
|
|
||||||
yield LLMMessagesQueueFrame(self.messages)
|
|
||||||
|
|
||||||
async def finalize(self) -> AsyncGenerator[QueueFrame, None]:
|
|
||||||
# Send any dangling words that weren't finished with punctuation.
|
|
||||||
if self.complete_sentences and self.sentence:
|
|
||||||
self.messages.append({"role": self.role, "content": self.sentence})
|
|
||||||
yield LLMMessagesQueueFrame(self.messages)
|
|
||||||
|
|
||||||
|
|
||||||
class LLMUserContextAggregator(LLMContextAggregator):
|
|
||||||
def __init__(self,
|
|
||||||
messages: list[dict],
|
|
||||||
bot_participant_id=None,
|
|
||||||
complete_sentences=True):
|
|
||||||
super().__init__(messages, "user", bot_participant_id, complete_sentences, pass_through=False)
|
|
||||||
|
|
||||||
|
|
||||||
class LLMAssistantContextAggregator(LLMContextAggregator):
|
|
||||||
def __init__(
|
|
||||||
self, messages: list[dict], bot_participant_id=None, complete_sentences=True
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
messages, "assistant", bot_participant_id, complete_sentences, pass_through=True
|
|
||||||
)
|
|
||||||
@@ -3,8 +3,9 @@ import io
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import wave
|
import wave
|
||||||
|
from dailyai.pipeline.frame_processor import FrameProcessor
|
||||||
|
|
||||||
from dailyai.queue_frame import (
|
from dailyai.pipeline.frames import (
|
||||||
AudioQueueFrame,
|
AudioQueueFrame,
|
||||||
ControlQueueFrame,
|
ControlQueueFrame,
|
||||||
EndStreamQueueFrame,
|
EndStreamQueueFrame,
|
||||||
@@ -17,11 +18,9 @@ from dailyai.queue_frame import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import AsyncGenerator, AsyncIterable, BinaryIO, Iterable
|
from typing import AsyncGenerator, AsyncIterable, BinaryIO, Iterable, List
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
|
class AIService(FrameProcessor):
|
||||||
class AIService:
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = logging.getLogger("dailyai")
|
self.logger = logging.getLogger("dailyai")
|
||||||
@@ -67,17 +66,6 @@ class AIService:
|
|||||||
self.logger.error("Exception occurred while running AI service", e)
|
self.logger.error("Exception occurred while running AI service", e)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def process_frame(self, frame: QueueFrame) -> AsyncGenerator[QueueFrame, None]:
|
|
||||||
if isinstance(frame, ControlQueueFrame):
|
|
||||||
yield frame
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def finalize(self) -> AsyncGenerator[QueueFrame, None]:
|
|
||||||
# This is a trick for the interpreter (and linter) to know that this is a generator.
|
|
||||||
if False:
|
|
||||||
yield QueueFrame()
|
|
||||||
|
|
||||||
|
|
||||||
class LLMService(AIService):
|
class LLMService(AIService):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import logging
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import torch
|
import torch
|
||||||
import torchaudio
|
|
||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from dailyai.queue_frame import (
|
from dailyai.pipeline.frames import (
|
||||||
AudioQueueFrame,
|
AudioQueueFrame,
|
||||||
EndStreamQueueFrame,
|
EndStreamQueueFrame,
|
||||||
ImageQueueFrame,
|
ImageQueueFrame,
|
||||||
@@ -89,10 +88,10 @@ class BaseTransportService():
|
|||||||
self._vad_stop_s = kwargs.get("vad_stop_s") or 0.8
|
self._vad_stop_s = kwargs.get("vad_stop_s") or 0.8
|
||||||
self._context = kwargs.get("context") or []
|
self._context = kwargs.get("context") or []
|
||||||
self._vad_enabled = kwargs.get("vad_enabled") or False
|
self._vad_enabled = kwargs.get("vad_enabled") or False
|
||||||
|
|
||||||
if self._vad_enabled and self._speaker_enabled:
|
if self._vad_enabled and self._speaker_enabled:
|
||||||
raise Exception("Sorry, you can't use speaker_enabled and vad_enabled at the same time. Please set one to False.")
|
raise Exception("Sorry, you can't use speaker_enabled and vad_enabled at the same time. Please set one to False.")
|
||||||
|
|
||||||
self._vad_samples = 1536
|
self._vad_samples = 1536
|
||||||
vad_frame_s = self._vad_samples / SAMPLE_RATE
|
vad_frame_s = self._vad_samples / SAMPLE_RATE
|
||||||
self._vad_start_frames = round(self._vad_start_s / vad_frame_s)
|
self._vad_start_frames = round(self._vad_start_s / vad_frame_s)
|
||||||
@@ -101,7 +100,7 @@ class BaseTransportService():
|
|||||||
self._vad_stopping_count = 0
|
self._vad_stopping_count = 0
|
||||||
self._vad_state = VADState.QUIET
|
self._vad_state = VADState.QUIET
|
||||||
self._user_is_speaking = False
|
self._user_is_speaking = False
|
||||||
|
|
||||||
duration_minutes = kwargs.get("duration_minutes") or 10
|
duration_minutes = kwargs.get("duration_minutes") or 10
|
||||||
self._expiration = time.time() + duration_minutes * 60
|
self._expiration = time.time() + duration_minutes * 60
|
||||||
|
|
||||||
@@ -136,7 +135,7 @@ class BaseTransportService():
|
|||||||
if self._speaker_enabled:
|
if self._speaker_enabled:
|
||||||
self._receive_audio_thread = threading.Thread(target=self._receive_audio, daemon=True)
|
self._receive_audio_thread = threading.Thread(target=self._receive_audio, daemon=True)
|
||||||
self._receive_audio_thread.start()
|
self._receive_audio_thread.start()
|
||||||
|
|
||||||
if self._vad_enabled:
|
if self._vad_enabled:
|
||||||
self._vad_thread = threading.Thread(target=self._vad, daemon=True)
|
self._vad_thread = threading.Thread(target=self._vad, daemon=True)
|
||||||
self._vad_thread.start()
|
self._vad_thread.start()
|
||||||
@@ -163,10 +162,10 @@ class BaseTransportService():
|
|||||||
|
|
||||||
if self._speaker_enabled:
|
if self._speaker_enabled:
|
||||||
self._receive_audio_thread.join()
|
self._receive_audio_thread.join()
|
||||||
|
|
||||||
if self._vad_enabled:
|
if self._vad_enabled:
|
||||||
self._vad_thread.join()
|
self._vad_thread.join()
|
||||||
|
|
||||||
|
|
||||||
def _post_run(self):
|
def _post_run(self):
|
||||||
# Note that this function must be idempotent! It can be called multiple times
|
# Note that this function must be idempotent! It can be called multiple times
|
||||||
@@ -199,7 +198,7 @@ class BaseTransportService():
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _prerun(self):
|
def _prerun(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _vad(self):
|
def _vad(self):
|
||||||
# CB: Starting silero VAD stuff
|
# CB: Starting silero VAD stuff
|
||||||
# TODO-CB: Probably need to force virtual speaker creation if we're
|
# TODO-CB: Probably need to force virtual speaker creation if we're
|
||||||
@@ -212,7 +211,7 @@ class BaseTransportService():
|
|||||||
new_confidence = model(
|
new_confidence = model(
|
||||||
torch.from_numpy(audio_float32), 16000).item()
|
torch.from_numpy(audio_float32), 16000).item()
|
||||||
speaking = new_confidence > 0.5
|
speaking = new_confidence > 0.5
|
||||||
|
|
||||||
if speaking:
|
if speaking:
|
||||||
match self._vad_state:
|
match self._vad_state:
|
||||||
case VADState.QUIET:
|
case VADState.QUIET:
|
||||||
@@ -233,7 +232,7 @@ class BaseTransportService():
|
|||||||
self._vad_stopping_count = 1
|
self._vad_stopping_count = 1
|
||||||
case VADState.STOPPING:
|
case VADState.STOPPING:
|
||||||
self._vad_stopping_count += 1
|
self._vad_stopping_count += 1
|
||||||
|
|
||||||
if self._vad_state == VADState.STARTING and self._vad_starting_count >= self._vad_start_frames:
|
if self._vad_state == VADState.STARTING and self._vad_starting_count >= self._vad_start_frames:
|
||||||
asyncio.run_coroutine_threadsafe(
|
asyncio.run_coroutine_threadsafe(
|
||||||
self.receive_queue.put(
|
self.receive_queue.put(
|
||||||
@@ -249,7 +248,7 @@ class BaseTransportService():
|
|||||||
)
|
)
|
||||||
self._vad_state = VADState.QUIET
|
self._vad_state = VADState.QUIET
|
||||||
self._vad_stopping_count = 0
|
self._vad_stopping_count = 0
|
||||||
|
|
||||||
async def _marshal_frames(self):
|
async def _marshal_frames(self):
|
||||||
while True:
|
while True:
|
||||||
frame: QueueFrame | list = await self.send_queue.get()
|
frame: QueueFrame | list = await self.send_queue.get()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import types
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from dailyai.queue_frame import (
|
from dailyai.pipeline.frames import (
|
||||||
TranscriptionQueueFrame,
|
TranscriptionQueueFrame,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import math
|
|||||||
import time
|
import time
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
import wave
|
import wave
|
||||||
from dailyai.queue_frame import AudioQueueFrame, QueueFrame, TranscriptionQueueFrame
|
from dailyai.pipeline.frames import AudioQueueFrame, QueueFrame, TranscriptionQueueFrame
|
||||||
from dailyai.services.ai_services import STTService
|
from dailyai.services.ai_services import STTService
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
66
src/dailyai/tests/test_aggregators.py
Normal file
66
src/dailyai/tests/test_aggregators.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import asyncio
|
||||||
|
from doctest import OutputChecker
|
||||||
|
from typing import Text
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import llm
|
||||||
|
from dailyai.pipeline.aggregators import GatedAccumulator, SentenceAggregator, StatelessTextTransformer
|
||||||
|
from dailyai.pipeline.frames import AudioQueueFrame, EndStreamQueueFrame, ImageQueueFrame, LLMResponseEndQueueFrame, LLMResponseStartQueueFrame, TextQueueFrame
|
||||||
|
|
||||||
|
from dailyai.pipeline.pipeline import Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
class TestDailyFrameAggregators(unittest.IsolatedAsyncioTestCase):
|
||||||
|
async def test_sentence_aggregator(self):
|
||||||
|
sentence = "Hello, world. How are you? I am fine"
|
||||||
|
expected_sentences = ["Hello, world.", " How are you?", " I am fine "]
|
||||||
|
aggregator = SentenceAggregator()
|
||||||
|
for word in sentence.split(" "):
|
||||||
|
async for sentence in aggregator.process_frame(TextQueueFrame(word + " ")):
|
||||||
|
self.assertIsInstance(sentence, TextQueueFrame)
|
||||||
|
if isinstance(sentence, TextQueueFrame):
|
||||||
|
self.assertEqual(sentence.text, expected_sentences.pop(0))
|
||||||
|
|
||||||
|
async for sentence in aggregator.process_frame(EndStreamQueueFrame()):
|
||||||
|
if len(expected_sentences):
|
||||||
|
self.assertIsInstance(sentence, TextQueueFrame)
|
||||||
|
if isinstance(sentence, TextQueueFrame):
|
||||||
|
self.assertEqual(sentence.text, expected_sentences.pop(0))
|
||||||
|
else:
|
||||||
|
self.assertIsInstance(sentence, EndStreamQueueFrame)
|
||||||
|
|
||||||
|
self.assertEqual(expected_sentences, [])
|
||||||
|
|
||||||
|
async def test_gated_accumulator(self):
|
||||||
|
gated_accumulator = GatedAccumulator(
|
||||||
|
gate_open_fn=lambda frame: isinstance(frame, ImageQueueFrame),
|
||||||
|
gate_close_fn=lambda frame: isinstance(frame, LLMResponseStartQueueFrame),
|
||||||
|
start_open=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
frames = [
|
||||||
|
LLMResponseStartQueueFrame(),
|
||||||
|
TextQueueFrame("Hello, "),
|
||||||
|
TextQueueFrame("world."),
|
||||||
|
AudioQueueFrame(b"hello"),
|
||||||
|
ImageQueueFrame("image", b"image"),
|
||||||
|
AudioQueueFrame(b"world"),
|
||||||
|
LLMResponseEndQueueFrame(),
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_output_frames = [
|
||||||
|
ImageQueueFrame("image", b"image"),
|
||||||
|
LLMResponseStartQueueFrame(),
|
||||||
|
TextQueueFrame("Hello, "),
|
||||||
|
TextQueueFrame("world."),
|
||||||
|
AudioQueueFrame(b"hello"),
|
||||||
|
AudioQueueFrame(b"world"),
|
||||||
|
LLMResponseEndQueueFrame(),
|
||||||
|
]
|
||||||
|
for frame in frames:
|
||||||
|
async for out_frame in gated_accumulator.process_frame(frame):
|
||||||
|
self.assertEqual(out_frame, expected_output_frames.pop(0))
|
||||||
|
self.assertEqual(expected_output_frames, [])
|
||||||
|
|
||||||
|
async def test_parallel_pipeline(self):
|
||||||
|
pass
|
||||||
@@ -3,7 +3,7 @@ import unittest
|
|||||||
from typing import AsyncGenerator, Generator
|
from typing import AsyncGenerator, Generator
|
||||||
|
|
||||||
from dailyai.services.ai_services import AIService
|
from dailyai.services.ai_services import AIService
|
||||||
from dailyai.queue_frame import EndStreamQueueFrame, QueueFrame, TextQueueFrame
|
from dailyai.pipeline.frames import EndStreamQueueFrame, QueueFrame, TextQueueFrame
|
||||||
|
|
||||||
|
|
||||||
class SimpleAIService(AIService):
|
class SimpleAIService(AIService):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import unittest
|
|||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from dailyai.queue_frame import AudioQueueFrame, ImageQueueFrame
|
from dailyai.pipeline.frames import AudioQueueFrame, ImageQueueFrame
|
||||||
|
|
||||||
|
|
||||||
class TestDailyTransport(unittest.IsolatedAsyncioTestCase):
|
class TestDailyTransport(unittest.IsolatedAsyncioTestCase):
|
||||||
@@ -42,6 +42,7 @@ class TestDailyTransport(unittest.IsolatedAsyncioTestCase):
|
|||||||
await asyncio.wait_for(event.wait(), timeout=1)
|
await asyncio.wait_for(event.wait(), timeout=1)
|
||||||
self.assertTrue(event.is_set())
|
self.assertTrue(event.is_set())
|
||||||
|
|
||||||
|
"""
|
||||||
@patch("dailyai.services.daily_transport_service.CallClient")
|
@patch("dailyai.services.daily_transport_service.CallClient")
|
||||||
@patch("dailyai.services.daily_transport_service.Daily")
|
@patch("dailyai.services.daily_transport_service.Daily")
|
||||||
async def test_run_with_camera_and_mic(self, daily_mock, callclient_mock):
|
async def test_run_with_camera_and_mic(self, daily_mock, callclient_mock):
|
||||||
@@ -79,3 +80,4 @@ class TestDailyTransport(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
camera.write_frame.assert_called_with(b"test")
|
camera.write_frame.assert_called_with(b"test")
|
||||||
mic.write_frames.assert_called()
|
mic.write_frames.assert_called()
|
||||||
|
"""
|
||||||
|
|||||||
58
src/dailyai/tests/test_pipeline.py
Normal file
58
src/dailyai/tests/test_pipeline.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import asyncio
|
||||||
|
from doctest import OutputChecker
|
||||||
|
import unittest
|
||||||
|
from dailyai.pipeline.aggregators import SentenceAggregator, StatelessTextTransformer
|
||||||
|
from dailyai.pipeline.frames import EndStreamQueueFrame, TextQueueFrame
|
||||||
|
|
||||||
|
from dailyai.pipeline.pipeline import Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
class TestDailyPipeline(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
async def test_pipeline_simple(self):
|
||||||
|
aggregator = SentenceAggregator()
|
||||||
|
|
||||||
|
outgoing_queue = asyncio.Queue()
|
||||||
|
incoming_queue = asyncio.Queue()
|
||||||
|
pipeline = Pipeline(incoming_queue, outgoing_queue, [aggregator])
|
||||||
|
|
||||||
|
await incoming_queue.put(TextQueueFrame("Hello, "))
|
||||||
|
await incoming_queue.put(TextQueueFrame("world."))
|
||||||
|
await incoming_queue.put(EndStreamQueueFrame())
|
||||||
|
|
||||||
|
await pipeline.run_pipeline()
|
||||||
|
|
||||||
|
self.assertEqual(await outgoing_queue.get(), TextQueueFrame("Hello, world."))
|
||||||
|
self.assertIsInstance(await outgoing_queue.get(), EndStreamQueueFrame)
|
||||||
|
|
||||||
|
async def test_pipeline_multiple_stages(self):
|
||||||
|
sentence_aggregator = SentenceAggregator()
|
||||||
|
to_upper = StatelessTextTransformer(lambda x: x.upper())
|
||||||
|
add_space = StatelessTextTransformer(lambda x: x + " ")
|
||||||
|
|
||||||
|
outgoing_queue = asyncio.Queue()
|
||||||
|
incoming_queue = asyncio.Queue()
|
||||||
|
pipeline = Pipeline(
|
||||||
|
incoming_queue, outgoing_queue, [add_space, sentence_aggregator, to_upper]
|
||||||
|
)
|
||||||
|
|
||||||
|
sentence = "Hello, world. It's me, a pipeline."
|
||||||
|
for c in sentence:
|
||||||
|
await incoming_queue.put(TextQueueFrame(c))
|
||||||
|
await incoming_queue.put(EndStreamQueueFrame())
|
||||||
|
|
||||||
|
await pipeline.run_pipeline()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
await outgoing_queue.get(), TextQueueFrame("H E L L O , W O R L D .")
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
await outgoing_queue.get(),
|
||||||
|
TextQueueFrame(" I T ' S M E , A P I P E L I N E ."),
|
||||||
|
)
|
||||||
|
# leftover little bit because of the spacing
|
||||||
|
self.assertEqual(
|
||||||
|
await outgoing_queue.get(),
|
||||||
|
TextQueueFrame(" "),
|
||||||
|
)
|
||||||
|
self.assertIsInstance(await outgoing_queue.get(), EndStreamQueueFrame)
|
||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from dailyai.queue_frame import LLMMessagesQueueFrame
|
from dailyai.pipeline.frames import LLMMessagesQueueFrame
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dailyai.queue_frame import TextQueueFrame
|
from dailyai.pipeline.frames import TextQueueFrame
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
from dailyai.services.open_ai_services import OpenAIImageGenService
|
from dailyai.services.open_ai_services import OpenAIImageGenService
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
from dailyai.queue_frame import TextQueueFrame
|
from dailyai.pipeline.frames import TextQueueFrame
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
from dailyai.services.local_transport_service import LocalTransportService
|
from dailyai.services.local_transport_service import LocalTransportService
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import aiohttp
|
|||||||
|
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.queue_frame import EndStreamQueueFrame, LLMMessagesQueueFrame
|
from dailyai.pipeline.frames import EndStreamQueueFrame, LLMMessagesQueueFrame
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
|
|
||||||
from examples.foundational.support.runner import configure
|
from examples.foundational.support.runner import configure
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dailyai.queue_frame import AudioQueueFrame, ImageQueueFrame
|
from dailyai.pipeline.frames import AudioQueueFrame, ImageQueueFrame
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureImageGenServiceREST, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureImageGenServiceREST, AzureTTSService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import asyncio
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dailyai.queue_frame import AudioQueueFrame, ImageQueueFrame
|
from dailyai.pipeline.frames import AudioQueueFrame, ImageQueueFrame
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService
|
from dailyai.services.azure_ai_services import AzureLLMService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.ai_services import FrameLogger
|
from dailyai.services.ai_services import FrameLogger
|
||||||
from dailyai.queue_aggregators import LLMAssistantContextAggregator, LLMContextAggregator, LLMUserContextAggregator
|
from dailyai.pipeline.aggregators import LLMAssistantContextAggregator, LLMContextAggregator, LLMUserContextAggregator
|
||||||
from support.runner import configure
|
from support.runner import configure
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ async def main(room_url: str, token):
|
|||||||
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
transport.transcription_settings["extra"]["endpointing"] = True
|
transport.transcription_settings["extra"]["endpointing"] = True
|
||||||
transport.transcription_settings["extra"]["punctuate"] = True
|
transport.transcription_settings["extra"]["punctuate"] = True
|
||||||
await asyncio.gather(transport.run(), handle_transcriptions())
|
await asyncio.gather(transport.run(), handle_transcriptions())
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import time
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from dailyai.queue_frame import ImageQueueFrame, QueueFrame
|
from dailyai.pipeline.frames import ImageQueueFrame, QueueFrame
|
||||||
|
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.ai_services import AIService
|
from dailyai.services.ai_services import AIService
|
||||||
from dailyai.queue_aggregators import LLMAssistantContextAggregator, LLMUserContextAggregator
|
from dailyai.pipeline.aggregators import LLMAssistantContextAggregator, LLMUserContextAggregator
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
|
|
||||||
from examples.foundational.support.runner import configure
|
from examples.foundational.support.runner import configure
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import aiohttp
|
|||||||
import os
|
import os
|
||||||
from dailyai.conversation_wrappers import InterruptibleConversationWrapper
|
from dailyai.conversation_wrappers import InterruptibleConversationWrapper
|
||||||
|
|
||||||
from dailyai.queue_frame import StartStreamQueueFrame, TextQueueFrame
|
from dailyai.pipeline.frames import StartStreamQueueFrame, TextQueueFrame
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from dailyai.services.daily_transport_service import DailyTransportService
|
|||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
from dailyai.queue_frame import AudioQueueFrame, ImageQueueFrame
|
from dailyai.pipeline.frames import AudioQueueFrame, ImageQueueFrame
|
||||||
|
|
||||||
from examples.foundational.support.runner import configure
|
from examples.foundational.support.runner import configure
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ from PIL import Image
|
|||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService
|
from dailyai.services.azure_ai_services import AzureLLMService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
from dailyai.queue_aggregators import LLMUserContextAggregator, LLMAssistantContextAggregator
|
from dailyai.pipeline.aggregators import LLMUserContextAggregator, LLMAssistantContextAggregator
|
||||||
from dailyai.queue_frame import (
|
from dailyai.pipeline.frames import (
|
||||||
QueueFrame,
|
QueueFrame,
|
||||||
TextQueueFrame,
|
TextQueueFrame,
|
||||||
ImageQueueFrame,
|
ImageQueueFrame,
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import wave
|
|||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
from dailyai.queue_aggregators import LLMContextAggregator, LLMUserContextAggregator, LLMAssistantContextAggregator
|
from dailyai.pipeline.aggregators import LLMContextAggregator, LLMUserContextAggregator, LLMAssistantContextAggregator
|
||||||
from dailyai.services.ai_services import AIService, FrameLogger
|
from dailyai.services.ai_services import AIService, FrameLogger
|
||||||
from dailyai.queue_frame import QueueFrame, AudioQueueFrame, LLMResponseEndQueueFrame, LLMMessagesQueueFrame
|
from dailyai.pipeline.frames import QueueFrame, AudioQueueFrame, LLMResponseEndQueueFrame, LLMMessagesQueueFrame
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
from examples.foundational.support.runner import configure
|
from examples.foundational.support.runner import configure
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import wave
|
import wave
|
||||||
from dailyai.queue_frame import EndStreamQueueFrame, TranscriptionQueueFrame
|
from dailyai.pipeline.frames import EndStreamQueueFrame, TranscriptionQueueFrame
|
||||||
|
|
||||||
from dailyai.services.local_transport_service import LocalTransportService
|
from dailyai.services.local_transport_service import LocalTransportService
|
||||||
from dailyai.services.whisper_ai_services import WhisperSTTService
|
from dailyai.services.whisper_ai_services import WhisperSTTService
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import random
|
|||||||
|
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.queue_frame import QueueFrame, FrameType
|
from dailyai.pipeline.frames import QueueFrame, FrameType
|
||||||
from dailyai.services.fal_ai_services import FalImageGenService
|
from dailyai.services.fal_ai_services import FalImageGenService
|
||||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import wave
|
|||||||
|
|
||||||
from dailyai.services.daily_transport_service import DailyTransportService
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
|
||||||
from dailyai.queue_aggregators import LLMContextAggregator
|
from dailyai.pipeline.aggregators import LLMContextAggregator
|
||||||
from dailyai.services.ai_services import AIService, FrameLogger
|
from dailyai.services.ai_services import AIService, FrameLogger
|
||||||
from dailyai.queue_frame import QueueFrame, AudioQueueFrame, LLMResponseEndQueueFrame, LLMMessagesQueueFrame
|
from dailyai.pipeline.frames import QueueFrame, AudioQueueFrame, LLMResponseEndQueueFrame, LLMMessagesQueueFrame
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
from examples.foundational.support.runner import configure
|
from examples.foundational.support.runner import configure
|
||||||
|
|||||||
Reference in New Issue
Block a user