Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
name: lint
|
name: format
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -12,12 +12,12 @@ on:
|
|||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build-lint-${{ github.event.pull_request.number || github.ref }}
|
group: build-format-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
autopep8:
|
ruff-format:
|
||||||
name: "Formatting lints"
|
name: "Formatting checker"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: "3.10"
|
||||||
- name: Setup virtual environment
|
- name: Setup virtual environment
|
||||||
run: |
|
run: |
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
@@ -34,11 +34,8 @@ jobs:
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r dev-requirements.txt
|
pip install -r dev-requirements.txt
|
||||||
- name: autopep8
|
- name: Ruff formatter
|
||||||
id: autopep8
|
id: ruff
|
||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
autopep8 --max-line-length 100 --exit-code -r -d --exclude "*_pb2.py" -a -a src/
|
ruff format --config line-length=100 --diff --exclude "*_pb2.py"
|
||||||
- name: Fail if autopep8 requires changes
|
|
||||||
if: steps.autopep8.outputs.exit-code == 2
|
|
||||||
run: exit 1
|
|
||||||
2
.github/workflows/tests.yaml
vendored
2
.github/workflows/tests.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r test-requirements.txt
|
pip install -r dev-requirements.txt -r test-requirements.txt
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -170,22 +170,24 @@ pytest --doctest-modules --ignore-glob="*to_be_updated*" --ignore-glob=*pipeline
|
|||||||
|
|
||||||
## Setting up your editor
|
## Setting up your editor
|
||||||
|
|
||||||
This project uses strict [PEP 8](https://peps.python.org/pep-0008/) formatting.
|
This project uses strict [PEP 8](https://peps.python.org/pep-0008/) formatting via [Ruff](https://github.com/astral-sh/ruff).
|
||||||
|
|
||||||
### Emacs
|
### Emacs
|
||||||
|
|
||||||
You can use [use-package](https://github.com/jwiegley/use-package) to install [py-autopep8](https://codeberg.org/ideasman42/emacs-py-autopep8) package and configure `autopep8` arguments:
|
You can use [use-package](https://github.com/jwiegley/use-package) to install [emacs-lazy-ruff](https://github.com/christophermadsen/emacs-lazy-ruff) package and configure `ruff` arguments:
|
||||||
|
|
||||||
```elisp
|
```elisp
|
||||||
(use-package py-autopep8
|
(use-package lazy-ruff
|
||||||
:ensure t
|
:ensure t
|
||||||
:defer t
|
:hook ((python-mode . lazy-ruff-mode))
|
||||||
:hook ((python-mode . py-autopep8-mode))
|
|
||||||
:config
|
:config
|
||||||
(setq py-autopep8-options '("-a" "-a", "--max-line-length=100")))
|
(setq lazy-ruff-format-command "ruff format --config line-length=100")
|
||||||
|
(setq lazy-ruff-only-format-block t)
|
||||||
|
(setq lazy-ruff-only-format-region t)
|
||||||
|
(setq lazy-ruff-only-format-buffer t))
|
||||||
```
|
```
|
||||||
|
|
||||||
`autopep8` was installed in the `venv` environment described before, so you should be able to use [pyvenv-auto](https://github.com/ryotaro612/pyvenv-auto) to automatically load that environment inside Emacs.
|
`ruff` was installed in the `venv` environment described before, so you should be able to use [pyvenv-auto](https://github.com/ryotaro612/pyvenv-auto) to automatically load that environment inside Emacs.
|
||||||
|
|
||||||
```elisp
|
```elisp
|
||||||
(use-package pyvenv-auto
|
(use-package pyvenv-auto
|
||||||
@@ -198,18 +200,14 @@ You can use [use-package](https://github.com/jwiegley/use-package) to install [p
|
|||||||
### Visual Studio Code
|
### Visual Studio Code
|
||||||
|
|
||||||
Install the
|
Install the
|
||||||
[autopep8](https://marketplace.visualstudio.com/items?itemName=ms-python.autopep8) extension. Then edit the user settings (_Ctrl-Shift-P_ `Open User Settings (JSON)`) and set it as the default Python formatter, enable formatting on save and configure `autopep8` arguments:
|
[Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) extension. Then edit the user settings (_Ctrl-Shift-P_ `Open User Settings (JSON)`) and set it as the default Python formatter, enable formatting on save and configure `ruff` arguments:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.autopep8",
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"autopep8.args": [
|
"ruff.format.args": ["--config", "line-length=100"]
|
||||||
"-a",
|
|
||||||
"-a",
|
|
||||||
"--max-line-length=100"
|
|
||||||
],
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
autopep8~=2.3.1
|
|
||||||
build~=1.2.1
|
build~=1.2.1
|
||||||
grpcio-tools~=1.62.2
|
grpcio-tools~=1.62.2
|
||||||
pip-tools~=7.4.1
|
pip-tools~=7.4.1
|
||||||
pyright~=1.1.376
|
pyright~=1.1.376
|
||||||
pytest~=8.3.2
|
pytest~=8.3.2
|
||||||
|
ruff~=0.6.7
|
||||||
setuptools~=72.2.0
|
setuptools~=72.2.0
|
||||||
setuptools_scm~=8.1.0
|
setuptools_scm~=8.1.0
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import argparse
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import LLMAssistantResponseAggregator, LLMUserResponseAggregator
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.frames.frames import LLMMessagesFrame, EndFrame
|
from pipecat.frames.frames import LLMMessagesFrame, EndFrame
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
||||||
@@ -16,6 +19,7 @@ from pipecat.vad.silero import SileroVADAnalyzer
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -39,7 +43,7 @@ async def main(room_url: str, token: str):
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = ElevenLabsTTSService(
|
tts = ElevenLabsTTSService(
|
||||||
@@ -47,9 +51,7 @@ async def main(room_url: str, token: str):
|
|||||||
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -61,14 +63,16 @@ async def main(room_url: str, token: str):
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
transport.output(),
|
tts,
|
||||||
tma_out,
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,14 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
from pipecat.transports.services.helpers.daily_rest import (
|
from pipecat.transports.services.helpers.daily_rest import (
|
||||||
DailyRESTHelper, DailyRoomObject, DailyRoomProperties, DailyRoomParams)
|
DailyRESTHelper,
|
||||||
|
DailyRoomObject,
|
||||||
|
DailyRoomProperties,
|
||||||
|
DailyRoomParams,
|
||||||
|
)
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -26,37 +31,37 @@ load_dotenv(override=True)
|
|||||||
|
|
||||||
MAX_SESSION_TIME = 5 * 60 # 5 minutes
|
MAX_SESSION_TIME = 5 * 60 # 5 minutes
|
||||||
REQUIRED_ENV_VARS = [
|
REQUIRED_ENV_VARS = [
|
||||||
'DAILY_API_KEY',
|
"DAILY_API_KEY",
|
||||||
'OPENAI_API_KEY',
|
"OPENAI_API_KEY",
|
||||||
'ELEVENLABS_API_KEY',
|
"ELEVENLABS_API_KEY",
|
||||||
'ELEVENLABS_VOICE_ID',
|
"ELEVENLABS_VOICE_ID",
|
||||||
'FLY_API_KEY',
|
"FLY_API_KEY",
|
||||||
'FLY_APP_NAME',]
|
"FLY_APP_NAME",
|
||||||
|
]
|
||||||
|
|
||||||
FLY_API_HOST = os.getenv("FLY_API_HOST", "https://api.machines.dev/v1")
|
FLY_API_HOST = os.getenv("FLY_API_HOST", "https://api.machines.dev/v1")
|
||||||
FLY_APP_NAME = os.getenv("FLY_APP_NAME", "pipecat-fly-example")
|
FLY_APP_NAME = os.getenv("FLY_APP_NAME", "pipecat-fly-example")
|
||||||
FLY_API_KEY = os.getenv("FLY_API_KEY", "")
|
FLY_API_KEY = os.getenv("FLY_API_KEY", "")
|
||||||
FLY_HEADERS = {
|
FLY_HEADERS = {"Authorization": f"Bearer {FLY_API_KEY}", "Content-Type": "application/json"}
|
||||||
'Authorization': f"Bearer {FLY_API_KEY}",
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
daily_helpers = {}
|
daily_helpers = {}
|
||||||
|
|
||||||
|
|
||||||
# ----------------- API ----------------- #
|
# ----------------- API ----------------- #
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -64,7 +69,7 @@ app.add_middleware(
|
|||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"]
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# ----------------- Main ----------------- #
|
# ----------------- Main ----------------- #
|
||||||
@@ -73,13 +78,15 @@ app.add_middleware(
|
|||||||
async def spawn_fly_machine(room_url: str, token: str):
|
async def spawn_fly_machine(room_url: str, token: str):
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
# Use the same image as the bot runner
|
# Use the same image as the bot runner
|
||||||
async with session.get(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS) as r:
|
async with session.get(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Unable to get machine info from Fly: {text}")
|
raise Exception(f"Unable to get machine info from Fly: {text}")
|
||||||
|
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
image = data[0]['config']['image']
|
image = data[0]["config"]["image"]
|
||||||
|
|
||||||
# Machine configuration
|
# Machine configuration
|
||||||
cmd = f"python3 bot.py -u {room_url} -t {token}"
|
cmd = f"python3 bot.py -u {room_url} -t {token}"
|
||||||
@@ -88,31 +95,28 @@ async def spawn_fly_machine(room_url: str, token: str):
|
|||||||
"config": {
|
"config": {
|
||||||
"image": image,
|
"image": image,
|
||||||
"auto_destroy": True,
|
"auto_destroy": True,
|
||||||
"init": {
|
"init": {"cmd": cmd},
|
||||||
"cmd": cmd
|
"restart": {"policy": "no"},
|
||||||
},
|
"guest": {"cpu_kind": "shared", "cpus": 1, "memory_mb": 1024},
|
||||||
"restart": {
|
|
||||||
"policy": "no"
|
|
||||||
},
|
|
||||||
"guest": {
|
|
||||||
"cpu_kind": "shared",
|
|
||||||
"cpus": 1,
|
|
||||||
"memory_mb": 1024
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Spawn a new machine instance
|
# Spawn a new machine instance
|
||||||
async with session.post(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS, json=worker_props) as r:
|
async with session.post(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS, json=worker_props
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Problem starting a bot worker: {text}")
|
raise Exception(f"Problem starting a bot worker: {text}")
|
||||||
|
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
# Wait for the machine to enter the started state
|
# Wait for the machine to enter the started state
|
||||||
vm_id = data['id']
|
vm_id = data["id"]
|
||||||
|
|
||||||
async with session.get(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines/{vm_id}/wait?state=started", headers=FLY_HEADERS) as r:
|
async with session.get(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines/{vm_id}/wait?state=started",
|
||||||
|
headers=FLY_HEADERS,
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Bot was unable to enter started state: {text}")
|
raise Exception(f"Bot was unable to enter started state: {text}")
|
||||||
@@ -134,29 +138,23 @@ async def start_bot(request: Request) -> JSONResponse:
|
|||||||
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", "")
|
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", "")
|
||||||
|
|
||||||
if not room_url:
|
if not room_url:
|
||||||
params = DailyRoomParams(
|
params = DailyRoomParams(properties=DailyRoomProperties())
|
||||||
properties=DailyRoomProperties()
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
room: DailyRoomObject = await daily_helpers["rest"].create_room(params=params)
|
room: DailyRoomObject = await daily_helpers["rest"].create_room(params=params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Unable to provision room {e}")
|
||||||
status_code=500,
|
|
||||||
detail=f"Unable to provision room {e}")
|
|
||||||
else:
|
else:
|
||||||
# Check passed room URL exists, we should assume that it already has a sip set up
|
# Check passed room URL exists, we should assume that it already has a sip set up
|
||||||
try:
|
try:
|
||||||
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Room not found: {room_url}")
|
||||||
status_code=500, detail=f"Room not found: {room_url}")
|
|
||||||
|
|
||||||
# Give the agent a token to join the session
|
# Give the agent a token to join the session
|
||||||
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
|
||||||
if not room or not token:
|
if not room or not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room_url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room_url}")
|
|
||||||
|
|
||||||
# Launch a new fly.io machine, or run as a shell process (not recommended)
|
# Launch a new fly.io machine, or run as a shell process (not recommended)
|
||||||
run_as_process = os.getenv("RUN_AS_PROCESS", False)
|
run_as_process = os.getenv("RUN_AS_PROCESS", False)
|
||||||
@@ -167,24 +165,26 @@ async def start_bot(request: Request) -> JSONResponse:
|
|||||||
[f"python3 -m bot -u {room.url} -t {token}"],
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__)))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await spawn_fly_machine(room.url, token)
|
await spawn_fly_machine(room.url, token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to spawn VM: {e}")
|
||||||
status_code=500, detail=f"Failed to spawn VM: {e}")
|
|
||||||
|
|
||||||
# Grab a token for the user to join with
|
# Grab a token for the user to join with
|
||||||
user_token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
user_token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
|
||||||
return JSONResponse({
|
return JSONResponse(
|
||||||
"room_url": room.url,
|
{
|
||||||
"token": user_token,
|
"room_url": room.url,
|
||||||
})
|
"token": user_token,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Check environment variables
|
# Check environment variables
|
||||||
@@ -193,23 +193,19 @@ if __name__ == "__main__":
|
|||||||
raise Exception(f"Missing environment variable: {env_var}.")
|
raise Exception(f"Missing environment variable: {env_var}.")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument(
|
||||||
default=os.getenv("HOST", "0.0.0.0"), help="Host address")
|
"--host", type=str, default=os.getenv("HOST", "0.0.0.0"), help="Host address"
|
||||||
parser.add_argument("--port", type=int,
|
)
|
||||||
default=os.getenv("PORT", 7860), help="Port number")
|
parser.add_argument("--port", type=int, default=os.getenv("PORT", 7860), help="Port number")
|
||||||
parser.add_argument("--reload", action="store_true",
|
parser.add_argument(
|
||||||
default=False, help="Reload code on change")
|
"--reload", action="store_true", default=False, help="Reload code on change"
|
||||||
|
)
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(
|
uvicorn.run("bot_runner:app", host=config.host, port=config.port, reload=config.reload)
|
||||||
"bot_runner:app",
|
|
||||||
host=config.host,
|
|
||||||
port=config.port,
|
|
||||||
reload=config.reload
|
|
||||||
)
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Pipecat runner shutting down...")
|
print("Pipecat runner shutting down...")
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import argparse
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import LLMAssistantResponseAggregator, LLMUserResponseAggregator
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
from pipecat.frames.frames import (
|
LLMAssistantResponseAggregator,
|
||||||
LLMMessagesFrame,
|
LLMUserResponseAggregator,
|
||||||
EndFrame
|
|
||||||
)
|
)
|
||||||
|
from pipecat.frames.frames import LLMMessagesFrame, EndFrame
|
||||||
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport, DailyDialinSettings
|
from pipecat.transports.services.daily import DailyParams, DailyTransport, DailyDialinSettings
|
||||||
@@ -18,6 +18,7 @@ from pipecat.vad.silero import SileroVADAnalyzer
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -31,10 +32,7 @@ async def main(room_url: str, token: str, callId: str, callDomain: str):
|
|||||||
# diallin_settings are only needed if Daily's SIP URI is used
|
# diallin_settings are only needed if Daily's SIP URI is used
|
||||||
# If you are handling this via Twilio, Telnyx, set this to None
|
# If you are handling this via Twilio, Telnyx, set this to None
|
||||||
# and handle call-forwarding when on_dialin_ready fires.
|
# and handle call-forwarding when on_dialin_ready fires.
|
||||||
diallin_settings = DailyDialinSettings(
|
diallin_settings = DailyDialinSettings(call_id=callId, call_domain=callDomain)
|
||||||
call_id=callId,
|
|
||||||
call_domain=callDomain
|
|
||||||
)
|
|
||||||
|
|
||||||
transport = DailyTransport(
|
transport = DailyTransport(
|
||||||
room_url,
|
room_url,
|
||||||
@@ -50,7 +48,7 @@ async def main(room_url: str, token: str, callId: str, callDomain: str):
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = ElevenLabsTTSService(
|
tts = ElevenLabsTTSService(
|
||||||
@@ -58,10 +56,7 @@ async def main(room_url: str, token: str, callId: str, callDomain: str):
|
|||||||
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -73,14 +68,16 @@ async def main(room_url: str, token: str, callId: str, callDomain: str):
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
transport.output(),
|
tts,
|
||||||
tma_out,
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ provisioning a room and starting a Pipecat bot in response.
|
|||||||
Refer to README for more information.
|
Refer to README for more information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
@@ -25,17 +24,18 @@ from pipecat.transports.services.helpers.daily_rest import (
|
|||||||
DailyRoomObject,
|
DailyRoomObject,
|
||||||
DailyRoomProperties,
|
DailyRoomProperties,
|
||||||
DailyRoomSipParams,
|
DailyRoomSipParams,
|
||||||
DailyRoomParams)
|
DailyRoomParams,
|
||||||
|
)
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# ------------ Configuration ------------ #
|
# ------------ Configuration ------------ #
|
||||||
|
|
||||||
MAX_SESSION_TIME = 5 * 60 # 5 minutes
|
MAX_SESSION_TIME = 5 * 60 # 5 minutes
|
||||||
REQUIRED_ENV_VARS = ['OPENAI_API_KEY', 'DAILY_API_KEY',
|
REQUIRED_ENV_VARS = ["OPENAI_API_KEY", "DAILY_API_KEY", "ELEVENLABS_API_KEY", "ELEVENLABS_VOICE_ID"]
|
||||||
'ELEVENLABS_API_KEY', 'ELEVENLABS_VOICE_ID']
|
|
||||||
|
|
||||||
daily_helpers = {}
|
daily_helpers = {}
|
||||||
|
|
||||||
@@ -47,12 +47,13 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -60,7 +61,7 @@ app.add_middleware(
|
|||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"]
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -80,10 +81,7 @@ async def _create_daily_room(room_url, callId, callDomain=None, vendor="daily"):
|
|||||||
properties=DailyRoomProperties(
|
properties=DailyRoomProperties(
|
||||||
# Note: these are the default values, except for the display name
|
# Note: these are the default values, except for the display name
|
||||||
sip=DailyRoomSipParams(
|
sip=DailyRoomSipParams(
|
||||||
display_name="dialin-user",
|
display_name="dialin-user", video=False, sip_mode="dial-in", num_endpoints=1
|
||||||
video=False,
|
|
||||||
sip_mode="dial-in",
|
|
||||||
num_endpoints=1
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -97,8 +95,7 @@ async def _create_daily_room(room_url, callId, callDomain=None, vendor="daily"):
|
|||||||
print(f"Joining existing room: {room_url}")
|
print(f"Joining existing room: {room_url}")
|
||||||
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Room not found: {room_url}")
|
||||||
status_code=500, detail=f"Room not found: {room_url}")
|
|
||||||
|
|
||||||
print(f"Daily room: {room.url} {room.config.sip_endpoint}")
|
print(f"Daily room: {room.url} {room.config.sip_endpoint}")
|
||||||
|
|
||||||
@@ -106,8 +103,7 @@ async def _create_daily_room(room_url, callId, callDomain=None, vendor="daily"):
|
|||||||
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
|
||||||
if not room or not token:
|
if not room or not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get room or token token")
|
||||||
status_code=500, detail=f"Failed to get room or token token")
|
|
||||||
|
|
||||||
# Spawn a new agent, and join the user session
|
# Spawn a new agent, and join the user session
|
||||||
# Note: this is mostly for demonstration purposes (refer to 'deployment' in docs)
|
# Note: this is mostly for demonstration purposes (refer to 'deployment' in docs)
|
||||||
@@ -120,14 +116,10 @@ async def _create_daily_room(room_url, callId, callDomain=None, vendor="daily"):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[bot_proc],
|
[bot_proc], shell=True, bufsize=1, cwd=os.path.dirname(os.path.abspath(__file__))
|
||||||
shell=True,
|
|
||||||
bufsize=1,
|
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
return room
|
return room
|
||||||
|
|
||||||
@@ -150,11 +142,10 @@ async def twilio_start_bot(request: Request):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", None)
|
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", None)
|
||||||
callId = data.get('CallSid')
|
callId = data.get("CallSid")
|
||||||
|
|
||||||
if not callId:
|
if not callId:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail="Missing 'CallSid' in request")
|
||||||
status_code=500, detail="Missing 'CallSid' in request")
|
|
||||||
|
|
||||||
print("CallId: %s" % callId)
|
print("CallId: %s" % callId)
|
||||||
|
|
||||||
@@ -170,7 +161,8 @@ async def twilio_start_bot(request: Request):
|
|||||||
# http://com.twilio.music.classical.s3.amazonaws.com/BusyStrings.mp3
|
# http://com.twilio.music.classical.s3.amazonaws.com/BusyStrings.mp3
|
||||||
resp = VoiceResponse()
|
resp = VoiceResponse()
|
||||||
resp.play(
|
resp.play(
|
||||||
url="http://com.twilio.sounds.music.s3.amazonaws.com/MARKOVICHAMP-Borghestral.mp3", loop=10)
|
url="http://com.twilio.sounds.music.s3.amazonaws.com/MARKOVICHAMP-Borghestral.mp3", loop=10
|
||||||
|
)
|
||||||
return str(resp)
|
return str(resp)
|
||||||
|
|
||||||
|
|
||||||
@@ -192,18 +184,14 @@ async def daily_start_bot(request: Request) -> JSONResponse:
|
|||||||
callId = data.get("callId", None)
|
callId = data.get("callId", None)
|
||||||
callDomain = data.get("callDomain", None)
|
callDomain = data.get("callDomain", None)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail="Missing properties 'callId' or 'callDomain'")
|
||||||
status_code=500,
|
|
||||||
detail="Missing properties 'callId' or 'callDomain'")
|
|
||||||
|
|
||||||
print(f"CallId: {callId}, CallDomain: {callDomain}")
|
print(f"CallId: {callId}, CallDomain: {callDomain}")
|
||||||
room: DailyRoomObject = await _create_daily_room(room_url, callId, callDomain, "daily")
|
room: DailyRoomObject = await _create_daily_room(room_url, callId, callDomain, "daily")
|
||||||
|
|
||||||
# Grab a token for the user to join with
|
# Grab a token for the user to join with
|
||||||
return JSONResponse({
|
return JSONResponse({"room_url": room.url, "sipUri": room.config.sip_endpoint})
|
||||||
"room_url": room.url,
|
|
||||||
"sipUri": room.config.sip_endpoint
|
|
||||||
})
|
|
||||||
|
|
||||||
# ----------------- Main ----------------- #
|
# ----------------- Main ----------------- #
|
||||||
|
|
||||||
@@ -215,24 +203,18 @@ if __name__ == "__main__":
|
|||||||
raise Exception(f"Missing environment variable: {env_var}.")
|
raise Exception(f"Missing environment variable: {env_var}.")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument(
|
||||||
default=os.getenv("HOST", "0.0.0.0"), help="Host address")
|
"--host", type=str, default=os.getenv("HOST", "0.0.0.0"), help="Host address"
|
||||||
parser.add_argument("--port", type=int,
|
)
|
||||||
default=os.getenv("PORT", 7860), help="Port number")
|
parser.add_argument("--port", type=int, default=os.getenv("PORT", 7860), help="Port number")
|
||||||
parser.add_argument("--reload", action="store_true",
|
parser.add_argument("--reload", action="store_true", default=True, help="Reload code on change")
|
||||||
default=True, help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(
|
uvicorn.run("bot_runner:app", host=config.host, port=config.port, reload=config.reload)
|
||||||
"bot_runner:app",
|
|
||||||
host=config.host,
|
|
||||||
port=config.port,
|
|
||||||
reload=config.reload
|
|
||||||
)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Pipecat runner shutting down...")
|
print("Pipecat runner shutting down...")
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import argparse
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import LLMAssistantResponseAggregator, LLMUserResponseAggregator
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
from pipecat.frames.frames import (
|
LLMAssistantResponseAggregator,
|
||||||
LLMMessagesFrame,
|
LLMUserResponseAggregator,
|
||||||
EndFrame
|
|
||||||
)
|
)
|
||||||
|
from pipecat.frames.frames import LLMMessagesFrame, EndFrame
|
||||||
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -21,14 +21,15 @@ from twilio.rest import Client
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
logger.add(sys.stderr, level="DEBUG")
|
||||||
|
|
||||||
|
|
||||||
twilio_account_sid = os.getenv('TWILIO_ACCOUNT_SID')
|
twilio_account_sid = os.getenv("TWILIO_ACCOUNT_SID")
|
||||||
twilio_auth_token = os.getenv('TWILIO_AUTH_TOKEN')
|
twilio_auth_token = os.getenv("TWILIO_AUTH_TOKEN")
|
||||||
twilioclient = Client(twilio_account_sid, twilio_auth_token)
|
twilioclient = Client(twilio_account_sid, twilio_auth_token)
|
||||||
|
|
||||||
daily_api_key = os.getenv("DAILY_API_KEY", "")
|
daily_api_key = os.getenv("DAILY_API_KEY", "")
|
||||||
@@ -51,7 +52,7 @@ async def main(room_url: str, token: str, callId: str, sipUri: str):
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = ElevenLabsTTSService(
|
tts = ElevenLabsTTSService(
|
||||||
@@ -59,10 +60,7 @@ async def main(room_url: str, token: str, callId: str, sipUri: str):
|
|||||||
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
voice_id=os.getenv("ELEVENLABS_VOICE_ID", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -74,14 +72,16 @@ async def main(room_url: str, token: str, callId: str, sipUri: str):
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
transport.output(),
|
tts,
|
||||||
tma_out,
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ async def main(room_url: str, token: str, callId: str, sipUri: str):
|
|||||||
try:
|
try:
|
||||||
# The TwiML is updated using Twilio's client library
|
# The TwiML is updated using Twilio's client library
|
||||||
call = twilioclient.calls(callId).update(
|
call = twilioclient.calls(callId).update(
|
||||||
twiml=f'<Response><Dial><Sip>{sipUri}</Sip></Dial></Response>'
|
twiml=f"<Response><Dial><Sip>{sipUri}</Sip></Dial></Response>"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Failed to forward call: {str(e)}")
|
raise Exception(f"Failed to forward call: {str(e)}")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -32,7 +33,8 @@ async def main():
|
|||||||
(room_url, _) = await configure(session)
|
(room_url, _) = await configure(session)
|
||||||
|
|
||||||
transport = DailyTransport(
|
transport = DailyTransport(
|
||||||
room_url, None, "Say One Thing", DailyParams(audio_out_enabled=True))
|
room_url, None, "Say One Thing", DailyParams(audio_out_enabled=True)
|
||||||
|
)
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -47,10 +49,11 @@ async def main():
|
|||||||
# participant joins.
|
# participant joins.
|
||||||
@transport.event_handler("on_participant_joined")
|
@transport.event_handler("on_participant_joined")
|
||||||
async def on_new_participant_joined(transport, participant):
|
async def on_new_participant_joined(transport, participant):
|
||||||
participant_name = participant["info"]["userName"] or ''
|
participant_name = participant["info"]["userName"] or ""
|
||||||
await task.queue_frames([TextFrame(f"Hello there, {participant_name}!"), EndFrame()])
|
await task.queue_frames([TextFrame(f"Hello there, {participant_name}!"), EndFrame()])
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from pipecat.transports.local.audio import LocalAudioTransport
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,25 +34,22 @@ async def main():
|
|||||||
(room_url, _) = await configure(session)
|
(room_url, _) = await configure(session)
|
||||||
|
|
||||||
transport = DailyTransport(
|
transport = DailyTransport(
|
||||||
room_url,
|
room_url, None, "Say One Thing From an LLM", DailyParams(audio_out_enabled=True)
|
||||||
None,
|
)
|
||||||
"Say One Thing From an LLM",
|
|
||||||
DailyParams(audio_out_enabled=True))
|
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are an LLM in a WebRTC session, and this is a 'hello world' demo. Say hello to the world.",
|
"content": "You are an LLM in a WebRTC session, and this is a 'hello world' demo. Say hello to the world.",
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -35,17 +36,11 @@ async def main():
|
|||||||
room_url,
|
room_url,
|
||||||
None,
|
None,
|
||||||
"Show a still frame image",
|
"Show a still frame image",
|
||||||
DailyParams(
|
DailyParams(camera_out_enabled=True, camera_out_width=1024, camera_out_height=1024),
|
||||||
camera_out_enabled=True,
|
|
||||||
camera_out_width=1024,
|
|
||||||
camera_out_height=1024
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(
|
params=FalImageGenService.InputParams(image_size="square_hd"),
|
||||||
image_size="square_hd"
|
|
||||||
),
|
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
key=os.getenv("FAL_KEY"),
|
key=os.getenv("FAL_KEY"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from pipecat.transports.local.tk import TkLocalTransport
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -35,15 +36,11 @@ async def main():
|
|||||||
|
|
||||||
transport = TkLocalTransport(
|
transport = TkLocalTransport(
|
||||||
tk_root,
|
tk_root,
|
||||||
TransportParams(
|
TransportParams(camera_out_enabled=True, camera_out_width=1024, camera_out_height=1024),
|
||||||
camera_out_enabled=True,
|
)
|
||||||
camera_out_width=1024,
|
|
||||||
camera_out_height=1024))
|
|
||||||
|
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(
|
params=FalImageGenService.InputParams(image_size="square_hd"),
|
||||||
image_size="square_hd"
|
|
||||||
),
|
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
key=os.getenv("FAL_KEY"),
|
key=os.getenv("FAL_KEY"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -58,8 +59,7 @@ async def main():
|
|||||||
voice_id=os.getenv("ELEVENLABS_VOICE_ID"),
|
voice_id=os.getenv("ELEVENLABS_VOICE_ID"),
|
||||||
)
|
)
|
||||||
|
|
||||||
messages = [{"role": "system",
|
messages = [{"role": "system", "content": "tell the user a joke about llamas"}]
|
||||||
"content": "tell the user a joke about llamas"}]
|
|
||||||
|
|
||||||
# Start a task to run the LLM to create a joke, and convert the LLM
|
# Start a task to run the LLM to create a joke, and convert the LLM
|
||||||
# output to audio frames. This task will run in parallel with generating
|
# output to audio frames. This task will run in parallel with generating
|
||||||
@@ -77,8 +77,7 @@ async def main():
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
merge_pipeline = SequentialMergePipeline(
|
merge_pipeline = SequentialMergePipeline([simple_tts_pipeline, llm_pipeline])
|
||||||
[simple_tts_pipeline, llm_pipeline])
|
|
||||||
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
transport.run(merge_pipeline),
|
transport.run(merge_pipeline),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
TextFrame
|
TextFrame,
|
||||||
)
|
)
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
@@ -34,6 +34,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -81,8 +82,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
camera_out_enabled=True,
|
camera_out_enabled=True,
|
||||||
camera_out_width=1024,
|
camera_out_width=1024,
|
||||||
camera_out_height=1024
|
camera_out_height=1024,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
@@ -90,14 +91,10 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(
|
params=FalImageGenService.InputParams(image_size="square_hd"),
|
||||||
image_size="square_hd"
|
|
||||||
),
|
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
key=os.getenv("FAL_KEY"),
|
key=os.getenv("FAL_KEY"),
|
||||||
)
|
)
|
||||||
@@ -112,15 +109,17 @@ async def main():
|
|||||||
#
|
#
|
||||||
# Note that `SyncParallelPipeline` requires all processors in it to be
|
# Note that `SyncParallelPipeline` requires all processors in it to be
|
||||||
# synchronous (which is the default for most processors).
|
# synchronous (which is the default for most processors).
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
llm, # LLM
|
[
|
||||||
sentence_aggregator, # Aggregates LLM output into full sentences
|
llm, # LLM
|
||||||
SyncParallelPipeline( # Run pipelines in parallel aggregating the result
|
sentence_aggregator, # Aggregates LLM output into full sentences
|
||||||
[month_prepender, tts], # Create "Month: sentence" and output audio
|
SyncParallelPipeline( # Run pipelines in parallel aggregating the result
|
||||||
[imagegen] # Generate image
|
[month_prepender, tts], # Create "Month: sentence" and output audio
|
||||||
),
|
[imagegen], # Generate image
|
||||||
transport.output() # Transport output
|
),
|
||||||
])
|
transport.output(), # Transport output
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
frames = []
|
frames = []
|
||||||
for month in [
|
for month in [
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ from pipecat.frames.frames import (
|
|||||||
TTSAudioRawFrame,
|
TTSAudioRawFrame,
|
||||||
URLImageRawFrame,
|
URLImageRawFrame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
TextFrame)
|
TextFrame,
|
||||||
|
)
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
|
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
|
||||||
@@ -48,7 +49,12 @@ async def main():
|
|||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
async def get_month_data(month):
|
async def get_month_data(month):
|
||||||
messages = [{"role": "system", "content": f"Describe a nature photograph suitable for use in a calendar, for the month of {month}. Include only the image description with no preamble. Limit the description to one sentence, please.", }]
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": f"Describe a nature photograph suitable for use in a calendar, for the month of {month}. Include only the image description with no preamble. Limit the description to one sentence, please.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
class ImageDescription(FrameProcessor):
|
class ImageDescription(FrameProcessor):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -74,7 +80,8 @@ async def main():
|
|||||||
if isinstance(frame, TTSAudioRawFrame):
|
if isinstance(frame, TTSAudioRawFrame):
|
||||||
self.audio.extend(frame.audio)
|
self.audio.extend(frame.audio)
|
||||||
self.frame = OutputAudioRawFrame(
|
self.frame = OutputAudioRawFrame(
|
||||||
bytes(self.audio), frame.sample_rate, frame.num_channels)
|
bytes(self.audio), frame.sample_rate, frame.num_channels
|
||||||
|
)
|
||||||
|
|
||||||
class ImageGrabber(FrameProcessor):
|
class ImageGrabber(FrameProcessor):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -87,9 +94,7 @@ async def main():
|
|||||||
if isinstance(frame, URLImageRawFrame):
|
if isinstance(frame, URLImageRawFrame):
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -97,11 +102,10 @@ async def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(
|
params=FalImageGenService.InputParams(image_size="square_hd"),
|
||||||
image_size="square_hd"
|
|
||||||
),
|
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
key=os.getenv("FAL_KEY"))
|
key=os.getenv("FAL_KEY"),
|
||||||
|
)
|
||||||
|
|
||||||
sentence_aggregator = SentenceAggregator()
|
sentence_aggregator = SentenceAggregator()
|
||||||
|
|
||||||
@@ -119,15 +123,17 @@ async def main():
|
|||||||
#
|
#
|
||||||
# Note that `SyncParallelPipeline` requires all processors in it to
|
# Note that `SyncParallelPipeline` requires all processors in it to
|
||||||
# be synchronous (which is the default for most processors).
|
# be synchronous (which is the default for most processors).
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
llm, # LLM
|
[
|
||||||
sentence_aggregator, # Aggregates LLM output into full sentences
|
llm, # LLM
|
||||||
description, # Store sentence
|
sentence_aggregator, # Aggregates LLM output into full sentences
|
||||||
SyncParallelPipeline(
|
description, # Store sentence
|
||||||
[tts, audio_grabber], # Generate and store audio for the given sentence
|
SyncParallelPipeline(
|
||||||
[imagegen, image_grabber] # Generate and storeimage for the given sentence
|
[tts, audio_grabber], # Generate and store audio for the given sentence
|
||||||
)
|
[imagegen, image_grabber], # Generate and storeimage for the given sentence
|
||||||
])
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
await task.queue_frame(LLMMessagesFrame(messages))
|
await task.queue_frame(LLMMessagesFrame(messages))
|
||||||
@@ -148,7 +154,9 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
camera_out_enabled=True,
|
camera_out_enabled=True,
|
||||||
camera_out_width=1024,
|
camera_out_width=1024,
|
||||||
camera_out_height=1024))
|
camera_out_height=1024,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
pipeline = Pipeline([transport.output()])
|
pipeline = Pipeline([transport.output()])
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, LLMMessagesFrame, MetricsFrame
|
from pipecat.frames.frames import Frame, LLMMessagesFrame, MetricsFrame
|
||||||
from pipecat.metrics.metrics import TTFBMetricsData, ProcessingMetricsData, LLMUsageMetricsData, TTSUsageMetricsData
|
from pipecat.metrics.metrics import (
|
||||||
|
TTFBMetricsData,
|
||||||
|
ProcessingMetricsData,
|
||||||
|
LLMUsageMetricsData,
|
||||||
|
TTSUsageMetricsData,
|
||||||
|
)
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
@@ -29,6 +34,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -48,7 +54,8 @@ class MetricsLogger(FrameProcessor):
|
|||||||
print(
|
print(
|
||||||
f"!!! MetricsFrame: {frame}, tokens: {
|
f"!!! MetricsFrame: {frame}, tokens: {
|
||||||
tokens.prompt_tokens}, characters: {
|
tokens.prompt_tokens}, characters: {
|
||||||
tokens.completion_tokens}")
|
tokens.completion_tokens}"
|
||||||
|
)
|
||||||
elif isinstance(d, TTSUsageMetricsData):
|
elif isinstance(d, TTSUsageMetricsData):
|
||||||
print(f"!!! MetricsFrame: {frame}, characters: {d.value}")
|
print(f"!!! MetricsFrame: {frame}, characters: {d.value}")
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
@@ -66,8 +73,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -75,10 +82,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
ml = MetricsLogger()
|
ml = MetricsLogger()
|
||||||
|
|
||||||
@@ -91,15 +95,17 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
ml,
|
tts,
|
||||||
transport.output(),
|
ml,
|
||||||
tma_out,
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -107,8 +113,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -52,16 +53,21 @@ class ImageSyncAggregator(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if not isinstance(frame, SystemFrame) and direction == FrameDirection.DOWNSTREAM:
|
if not isinstance(frame, SystemFrame) and direction == FrameDirection.DOWNSTREAM:
|
||||||
await self.push_frame(OutputImageRawFrame(
|
await self.push_frame(
|
||||||
image=self._speaking_image_bytes,
|
OutputImageRawFrame(
|
||||||
size=(1024, 1024),
|
image=self._speaking_image_bytes,
|
||||||
format=self._speaking_image_format)
|
size=(1024, 1024),
|
||||||
|
format=self._speaking_image_format,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
await self.push_frame(OutputImageRawFrame(
|
await self.push_frame(
|
||||||
image=self._waiting_image_bytes,
|
OutputImageRawFrame(
|
||||||
size=(1024, 1024),
|
image=self._waiting_image_bytes,
|
||||||
format=self._waiting_image_format))
|
size=(1024, 1024),
|
||||||
|
format=self._waiting_image_format,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ async def main():
|
|||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
@@ -90,9 +96,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -109,21 +113,23 @@ async def main():
|
|||||||
os.path.join(os.path.dirname(__file__), "assets", "waiting.png"),
|
os.path.join(os.path.dirname(__file__), "assets", "waiting.png"),
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
image_sync_aggregator,
|
transport.input(),
|
||||||
tma_in,
|
image_sync_aggregator,
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
transport.output(),
|
tts,
|
||||||
tma_out
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
participant_name = participant["info"]["userName"] or ''
|
participant_name = participant["info"]["userName"] or ""
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
await task.queue_frames([TextFrame(f"Hi there {participant_name}!")])
|
await task.queue_frames([TextFrame(f"Hi there {participant_name}!")])
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,8 +46,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -52,9 +55,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -66,28 +67,32 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(
|
task = PipelineTask(
|
||||||
allow_interruptions=True,
|
pipeline,
|
||||||
enable_metrics=True,
|
PipelineParams(
|
||||||
enable_usage_metrics=True,
|
allow_interruptions=True,
|
||||||
report_only_initial_ttfb=True,
|
enable_metrics=True,
|
||||||
))
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.anthropic import AnthropicLLMService
|
from pipecat.services.anthropic import AnthropicLLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,8 +46,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -53,8 +56,8 @@ async def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
llm = AnthropicLLMService(
|
llm = AnthropicLLMService(
|
||||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
api_key=os.getenv("ANTHROPIC_API_KEY"), model="claude-3-opus-20240229"
|
||||||
model="claude-3-opus-20240229")
|
)
|
||||||
|
|
||||||
# todo: think more about how to handle system prompts in a more general way. OpenAI,
|
# todo: think more about how to handle system prompts in a more general way. OpenAI,
|
||||||
# Google, and Anthropic all have slightly different approaches to providing a system
|
# Google, and Anthropic all have slightly different approaches to providing a system
|
||||||
@@ -69,14 +72,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.processors.frameworks.langchain import LangchainProcessor
|
from pipecat.processors.frameworks.langchain import LangchainProcessor
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -32,6 +34,7 @@ from loguru import logger
|
|||||||
from runner import configure
|
from runner import configure
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -70,19 +73,22 @@ async def main():
|
|||||||
|
|
||||||
prompt = ChatPromptTemplate.from_messages(
|
prompt = ChatPromptTemplate.from_messages(
|
||||||
[
|
[
|
||||||
("system",
|
(
|
||||||
"Be nice and helpful. Answer very briefly and without special characters like `#` or `*`. "
|
"system",
|
||||||
"Your response will be synthesized to voice and those characters will create unnatural sounds.",
|
"Be nice and helpful. Answer very briefly and without special characters like `#` or `*`. "
|
||||||
),
|
"Your response will be synthesized to voice and those characters will create unnatural sounds.",
|
||||||
|
),
|
||||||
MessagesPlaceholder("chat_history"),
|
MessagesPlaceholder("chat_history"),
|
||||||
("human", "{input}"),
|
("human", "{input}"),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
chain = prompt | ChatOpenAI(model="gpt-4o", temperature=0.7)
|
chain = prompt | ChatOpenAI(model="gpt-4o", temperature=0.7)
|
||||||
history_chain = RunnableWithMessageHistory(
|
history_chain = RunnableWithMessageHistory(
|
||||||
chain,
|
chain,
|
||||||
get_session_history,
|
get_session_history,
|
||||||
history_messages_key="chat_history",
|
history_messages_key="chat_history",
|
||||||
input_messages_key="input")
|
input_messages_key="input",
|
||||||
|
)
|
||||||
lc = LangchainProcessor(history_chain)
|
lc = LangchainProcessor(history_chain)
|
||||||
|
|
||||||
tma_in = LLMUserResponseAggregator()
|
tma_in = LLMUserResponseAggregator()
|
||||||
@@ -90,12 +96,12 @@ async def main():
|
|||||||
|
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
transport.input(), # Transport user input
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
tma_in, # User responses
|
||||||
lc, # Langchain
|
lc, # Langchain
|
||||||
tts, # TTS
|
tts, # TTS
|
||||||
transport.output(), # Transport bot output
|
transport.output(), # Transport bot output
|
||||||
tma_out, # Assistant spoken responses
|
tma_out, # Assistant spoken responses
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,11 +115,7 @@ async def main():
|
|||||||
# the `LLMMessagesFrame` will be picked up by the LangchainProcessor using
|
# the `LLMMessagesFrame` will be picked up by the LangchainProcessor using
|
||||||
# only the content of the last message to inject it in the prompt defined
|
# only the content of the last message to inject it in the prompt defined
|
||||||
# above. So no role is required here.
|
# above. So no role is required here.
|
||||||
messages = [(
|
messages = [({"content": "Please briefly introduce yourself to the user."})]
|
||||||
{
|
|
||||||
"content": "Please briefly introduce yourself to the user."
|
|
||||||
}
|
|
||||||
)]
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.deepgram import DeepgramSTTService, DeepgramTTSService
|
from pipecat.services.deepgram import DeepgramSTTService, DeepgramTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,21 +46,17 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True
|
vad_audio_passthrough=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = DeepgramTTSService(
|
tts = DeepgramTTSService(
|
||||||
aiohttp_session=session,
|
aiohttp_session=session, api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-helios-en"
|
||||||
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
|
||||||
voice="aura-helios-en"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -69,15 +68,17 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
stt, # STT
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
stt, # STT
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -85,8 +86,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.playht import PlayHTTTSService
|
from pipecat.services.playht import PlayHTTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -44,8 +47,8 @@ async def main():
|
|||||||
audio_out_sample_rate=16000,
|
audio_out_sample_rate=16000,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = PlayHTTTSService(
|
tts = PlayHTTTSService(
|
||||||
@@ -54,9 +57,7 @@ async def main():
|
|||||||
voice_url="s3://voice-cloning-zero-shot/801a663f-efd0-4254-98d0-5c175514c3e8/jennifer/manifest.json",
|
voice_url="s3://voice-cloning-zero-shot/801a663f-efd0-4254-98d0-5c175514c3e8/jennifer/manifest.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -68,14 +69,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -83,8 +86,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.azure import AzureLLMService, AzureSTTService, AzureTTSService
|
from pipecat.services.azure import AzureLLMService, AzureSTTService, AzureTTSService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
from pipecat.vad.silero import SileroVADAnalyzer
|
from pipecat.vad.silero import SileroVADAnalyzer
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -45,7 +48,7 @@ async def main():
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True,
|
vad_audio_passthrough=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
stt = AzureSTTService(
|
stt = AzureSTTService(
|
||||||
@@ -74,15 +77,17 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
stt, # STT
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
stt, # STT
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -90,8 +95,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.openai import OpenAITTSService
|
from pipecat.services.openai import OpenAITTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -44,18 +47,13 @@ async def main():
|
|||||||
audio_out_sample_rate=24000,
|
audio_out_sample_rate=24000,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = OpenAITTSService(
|
tts = OpenAITTSService(api_key=os.getenv("OPENAI_API_KEY"), voice="alloy")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
voice="alloy"
|
|
||||||
)
|
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -67,14 +65,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -82,8 +82,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from loguru import logger
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -46,8 +47,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -60,9 +61,7 @@ async def main():
|
|||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
api_key=os.getenv("OPENAI_API_KEY"),
|
||||||
openpipe_api_key=os.getenv("OPENPIPE_API_KEY"),
|
openpipe_api_key=os.getenv("OPENPIPE_API_KEY"),
|
||||||
model="gpt-4o",
|
model="gpt-4o",
|
||||||
tags={
|
tags={"conversation_id": f"pipecat-{timestamp}"},
|
||||||
"conversation_id": f"pipecat-{timestamp}"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
@@ -74,14 +73,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -89,8 +90,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.deepgram import DeepgramSTTService, DeepgramTTSService
|
from pipecat.services.deepgram import DeepgramSTTService, DeepgramTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.services.xtts import XTTSService
|
from pipecat.services.xtts import XTTSService
|
||||||
@@ -26,6 +28,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -45,19 +48,17 @@ async def main():
|
|||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = XTTSService(
|
tts = XTTSService(
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
voice_id="Claribel Dervla",
|
voice_id="Claribel Dervla",
|
||||||
language="en",
|
language="en",
|
||||||
base_url="http://localhost:8000"
|
base_url="http://localhost:8000",
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -69,14 +70,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -84,8 +87,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.gladia import GladiaSTTService
|
from pipecat.services.gladia import GladiaSTTService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
@@ -26,6 +28,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -45,7 +48,7 @@ async def main():
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True,
|
vad_audio_passthrough=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
stt = GladiaSTTService(
|
stt = GladiaSTTService(
|
||||||
@@ -57,9 +60,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -71,15 +72,17 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
stt, # STT
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
stt, # STT
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -87,8 +90,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.lmnt import LmntTTSService
|
from pipecat.services.lmnt import LmntTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -44,18 +47,13 @@ async def main():
|
|||||||
audio_out_sample_rate=24000,
|
audio_out_sample_rate=24000,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = LmntTTSService(
|
tts = LmntTTSService(api_key=os.getenv("LMNT_API_KEY"), voice_id="morgan")
|
||||||
api_key=os.getenv("LMNT_API_KEY"),
|
|
||||||
voice_id="morgan"
|
|
||||||
)
|
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -67,14 +65,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -82,8 +82,7 @@ async def main():
|
|||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.together import TogetherLLMService
|
from pipecat.services.together import TogetherLLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,8 +46,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -62,8 +65,8 @@ async def main():
|
|||||||
extra={
|
extra={
|
||||||
"frequency_penalty": 2.0,
|
"frequency_penalty": 2.0,
|
||||||
"presence_penalty": 0.0,
|
"presence_penalty": 0.0,
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
@@ -76,14 +79,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from pipecat.frames.frames import AudioFrame, EndFrame, ImageFrame, LLMMessagesF
|
|||||||
from runner import configure
|
from runner import configure
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logging.basicConfig(format=f"%(levelno)s %(asctime)s %(message)s")
|
logging.basicConfig(format=f"%(levelno)s %(asctime)s %(message)s")
|
||||||
@@ -53,9 +54,7 @@ async def main():
|
|||||||
voice_id="jBpfuIE2acCO8z3wKNLl",
|
voice_id="jBpfuIE2acCO8z3wKNLl",
|
||||||
)
|
)
|
||||||
dalle = FalImageGenService(
|
dalle = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(
|
params=FalImageGenService.InputParams(image_size="1024x1024"),
|
||||||
image_size="1024x1024"
|
|
||||||
),
|
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
key=os.getenv("FAL_KEY"),
|
key=os.getenv("FAL_KEY"),
|
||||||
)
|
)
|
||||||
@@ -75,13 +74,11 @@ async def main():
|
|||||||
|
|
||||||
async def get_text_and_audio(messages) -> Tuple[str, bytearray]:
|
async def get_text_and_audio(messages) -> Tuple[str, bytearray]:
|
||||||
"""This function streams text from the LLM and uses the TTS service to convert
|
"""This function streams text from the LLM and uses the TTS service to convert
|
||||||
that text to speech as it's received. """
|
that text to speech as it's received."""
|
||||||
source_queue = asyncio.Queue()
|
source_queue = asyncio.Queue()
|
||||||
sink_queue = asyncio.Queue()
|
sink_queue = asyncio.Queue()
|
||||||
sentence_aggregator = SentenceAggregator()
|
sentence_aggregator = SentenceAggregator()
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline([llm, sentence_aggregator, tts1], source_queue, sink_queue)
|
||||||
[llm, sentence_aggregator, tts1], source_queue, sink_queue
|
|
||||||
)
|
|
||||||
|
|
||||||
await source_queue.put(LLMMessagesFrame(messages))
|
await source_queue.put(LLMMessagesFrame(messages))
|
||||||
await source_queue.put(EndFrame())
|
await source_queue.put(EndFrame())
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ import aiohttp
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, InputAudioRawFrame, InputImageRawFrame, OutputAudioRawFrame, OutputImageRawFrame
|
from pipecat.frames.frames import (
|
||||||
|
Frame,
|
||||||
|
InputAudioRawFrame,
|
||||||
|
InputImageRawFrame,
|
||||||
|
OutputAudioRawFrame,
|
||||||
|
OutputImageRawFrame,
|
||||||
|
)
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
@@ -20,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -27,21 +34,20 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class MirrorProcessor(FrameProcessor):
|
class MirrorProcessor(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if isinstance(frame, InputAudioRawFrame):
|
if isinstance(frame, InputAudioRawFrame):
|
||||||
await self.push_frame(OutputAudioRawFrame(
|
await self.push_frame(
|
||||||
audio=frame.audio,
|
OutputAudioRawFrame(
|
||||||
sample_rate=frame.sample_rate,
|
audio=frame.audio,
|
||||||
num_channels=frame.num_channels)
|
sample_rate=frame.sample_rate,
|
||||||
|
num_channels=frame.num_channels,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif isinstance(frame, InputImageRawFrame):
|
elif isinstance(frame, InputImageRawFrame):
|
||||||
await self.push_frame(OutputImageRawFrame(
|
await self.push_frame(
|
||||||
image=frame.image,
|
OutputImageRawFrame(image=frame.image, size=frame.size, format=frame.format)
|
||||||
size=frame.size,
|
|
||||||
format=frame.format)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
@@ -52,15 +58,17 @@ async def main():
|
|||||||
(room_url, token) = await configure(session)
|
(room_url, token) = await configure(session)
|
||||||
|
|
||||||
transport = DailyTransport(
|
transport = DailyTransport(
|
||||||
room_url, token, "Test",
|
room_url,
|
||||||
|
token,
|
||||||
|
"Test",
|
||||||
DailyParams(
|
DailyParams(
|
||||||
audio_in_enabled=True,
|
audio_in_enabled=True,
|
||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
camera_out_enabled=True,
|
camera_out_enabled=True,
|
||||||
camera_out_is_live=True,
|
camera_out_is_live=True,
|
||||||
camera_out_width=1280,
|
camera_out_width=1280,
|
||||||
camera_out_height=720
|
camera_out_height=720,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ import sys
|
|||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, InputAudioRawFrame, InputImageRawFrame, OutputAudioRawFrame, OutputImageRawFrame
|
from pipecat.frames.frames import (
|
||||||
|
Frame,
|
||||||
|
InputAudioRawFrame,
|
||||||
|
InputImageRawFrame,
|
||||||
|
OutputAudioRawFrame,
|
||||||
|
OutputImageRawFrame,
|
||||||
|
)
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
@@ -24,31 +30,33 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
logger.add(sys.stderr, level="DEBUG")
|
||||||
|
|
||||||
class MirrorProcessor(FrameProcessor):
|
|
||||||
|
|
||||||
|
class MirrorProcessor(FrameProcessor):
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if isinstance(frame, InputAudioRawFrame):
|
if isinstance(frame, InputAudioRawFrame):
|
||||||
await self.push_frame(OutputAudioRawFrame(
|
await self.push_frame(
|
||||||
audio=frame.audio,
|
OutputAudioRawFrame(
|
||||||
sample_rate=frame.sample_rate,
|
audio=frame.audio,
|
||||||
num_channels=frame.num_channels)
|
sample_rate=frame.sample_rate,
|
||||||
|
num_channels=frame.num_channels,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif isinstance(frame, InputImageRawFrame):
|
elif isinstance(frame, InputImageRawFrame):
|
||||||
await self.push_frame(OutputImageRawFrame(
|
await self.push_frame(
|
||||||
image=frame.image,
|
OutputImageRawFrame(image=frame.image, size=frame.size, format=frame.format)
|
||||||
size=frame.size,
|
|
||||||
format=frame.format)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
(room_url, token) = await configure(session)
|
(room_url, token) = await configure(session)
|
||||||
@@ -57,8 +65,8 @@ async def main():
|
|||||||
tk_root.title("Local Mirror")
|
tk_root.title("Local Mirror")
|
||||||
|
|
||||||
daily_transport = DailyTransport(
|
daily_transport = DailyTransport(
|
||||||
room_url, token, "Test", DailyParams(
|
room_url, token, "Test", DailyParams(audio_in_enabled=True)
|
||||||
audio_in_enabled=True))
|
)
|
||||||
|
|
||||||
tk_transport = TkLocalTransport(
|
tk_transport = TkLocalTransport(
|
||||||
tk_root,
|
tk_root,
|
||||||
@@ -67,7 +75,9 @@ async def main():
|
|||||||
camera_out_enabled=True,
|
camera_out_enabled=True,
|
||||||
camera_out_is_live=True,
|
camera_out_is_live=True,
|
||||||
camera_out_width=1280,
|
camera_out_width=1280,
|
||||||
camera_out_height=720))
|
camera_out_height=720,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@daily_transport.event_handler("on_first_participant_joined")
|
@daily_transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -25,6 +27,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,8 +46,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -52,9 +55,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -67,15 +68,17 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
hey_robot_filter, # Filter out speech not directed at the robot
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
hey_robot_filter, # Filter out speech not directed at the robot
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -53,12 +54,12 @@ for file in sound_files:
|
|||||||
filename = os.path.splitext(os.path.basename(full_path))[0]
|
filename = os.path.splitext(os.path.basename(full_path))[0]
|
||||||
# Open the image and convert it to bytes
|
# Open the image and convert it to bytes
|
||||||
with wave.open(full_path) as audio_file:
|
with wave.open(full_path) as audio_file:
|
||||||
sounds[file] = OutputAudioRawFrame(audio_file.readframes(-1),
|
sounds[file] = OutputAudioRawFrame(
|
||||||
audio_file.getframerate(), audio_file.getnchannels())
|
audio_file.readframes(-1), audio_file.getframerate(), audio_file.getnchannels()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OutboundSoundEffectWrapper(FrameProcessor):
|
class OutboundSoundEffectWrapper(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
@@ -71,7 +72,6 @@ class OutboundSoundEffectWrapper(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class InboundSoundEffectWrapper(FrameProcessor):
|
class InboundSoundEffectWrapper(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
@@ -95,13 +95,11 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -122,18 +120,20 @@ async def main():
|
|||||||
fl = FrameLogger("LLM Out")
|
fl = FrameLogger("LLM Out")
|
||||||
fl2 = FrameLogger("Transcription In")
|
fl2 = FrameLogger("Transcription In")
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
in_sound,
|
tma_in,
|
||||||
fl2,
|
in_sound,
|
||||||
llm,
|
fl2,
|
||||||
fl,
|
llm,
|
||||||
tts,
|
fl,
|
||||||
out_sound,
|
tts,
|
||||||
transport.output(),
|
out_sound,
|
||||||
tma_out
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,7 +34,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class UserImageRequester(FrameProcessor):
|
class UserImageRequester(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, participant_id: str | None = None):
|
def __init__(self, participant_id: str | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._participant_id = participant_id
|
self._participant_id = participant_id
|
||||||
@@ -45,7 +45,9 @@ class UserImageRequester(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if self._participant_id and isinstance(frame, TextFrame):
|
if self._participant_id and isinstance(frame, TextFrame):
|
||||||
await self.push_frame(UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM)
|
await self.push_frame(
|
||||||
|
UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,8 +63,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_response = UserResponseAggregator()
|
user_response = UserResponseAggregator()
|
||||||
@@ -86,15 +88,17 @@ async def main():
|
|||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
image_requester.set_participant_id(participant["id"])
|
image_requester.set_participant_id(participant["id"])
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_response,
|
transport.input(),
|
||||||
image_requester,
|
user_response,
|
||||||
vision_aggregator,
|
image_requester,
|
||||||
moondream,
|
vision_aggregator,
|
||||||
tts,
|
moondream,
|
||||||
transport.output()
|
tts,
|
||||||
])
|
transport.output(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -102,5 +106,6 @@ async def main():
|
|||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,7 +34,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class UserImageRequester(FrameProcessor):
|
class UserImageRequester(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, participant_id: str | None = None):
|
def __init__(self, participant_id: str | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._participant_id = participant_id
|
self._participant_id = participant_id
|
||||||
@@ -45,7 +45,9 @@ class UserImageRequester(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if self._participant_id and isinstance(frame, TextFrame):
|
if self._participant_id and isinstance(frame, TextFrame):
|
||||||
await self.push_frame(UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM)
|
await self.push_frame(
|
||||||
|
UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
@@ -62,8 +64,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_response = UserResponseAggregator()
|
user_response = UserResponseAggregator()
|
||||||
@@ -73,8 +75,8 @@ async def main():
|
|||||||
vision_aggregator = VisionImageFrameAggregator()
|
vision_aggregator = VisionImageFrameAggregator()
|
||||||
|
|
||||||
google = GoogleLLMService(
|
google = GoogleLLMService(
|
||||||
model="gemini-1.5-flash-latest",
|
model="gemini-1.5-flash-latest", api_key=os.getenv("GOOGLE_API_KEY")
|
||||||
api_key=os.getenv("GOOGLE_API_KEY"))
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -88,15 +90,17 @@ async def main():
|
|||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
image_requester.set_participant_id(participant["id"])
|
image_requester.set_participant_id(participant["id"])
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_response,
|
transport.input(),
|
||||||
image_requester,
|
user_response,
|
||||||
vision_aggregator,
|
image_requester,
|
||||||
google,
|
vision_aggregator,
|
||||||
tts,
|
google,
|
||||||
transport.output()
|
tts,
|
||||||
])
|
transport.output(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -104,5 +108,6 @@ async def main():
|
|||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,7 +34,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class UserImageRequester(FrameProcessor):
|
class UserImageRequester(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, participant_id: str | None = None):
|
def __init__(self, participant_id: str | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._participant_id = participant_id
|
self._participant_id = participant_id
|
||||||
@@ -45,7 +45,9 @@ class UserImageRequester(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if self._participant_id and isinstance(frame, TextFrame):
|
if self._participant_id and isinstance(frame, TextFrame):
|
||||||
await self.push_frame(UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM)
|
await self.push_frame(
|
||||||
|
UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,8 +63,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_response = UserResponseAggregator()
|
user_response = UserResponseAggregator()
|
||||||
@@ -71,10 +73,7 @@ async def main():
|
|||||||
|
|
||||||
vision_aggregator = VisionImageFrameAggregator()
|
vision_aggregator = VisionImageFrameAggregator()
|
||||||
|
|
||||||
openai = OpenAILLMService(
|
openai = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -88,15 +87,17 @@ async def main():
|
|||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
image_requester.set_participant_id(participant["id"])
|
image_requester.set_participant_id(participant["id"])
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_response,
|
transport.input(),
|
||||||
image_requester,
|
user_response,
|
||||||
vision_aggregator,
|
image_requester,
|
||||||
openai,
|
vision_aggregator,
|
||||||
tts,
|
openai,
|
||||||
transport.output()
|
tts,
|
||||||
])
|
transport.output(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -104,5 +105,6 @@ async def main():
|
|||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,7 +34,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class UserImageRequester(FrameProcessor):
|
class UserImageRequester(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, participant_id: str | None = None):
|
def __init__(self, participant_id: str | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._participant_id = participant_id
|
self._participant_id = participant_id
|
||||||
@@ -45,7 +45,9 @@ class UserImageRequester(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if self._participant_id and isinstance(frame, TextFrame):
|
if self._participant_id and isinstance(frame, TextFrame):
|
||||||
await self.push_frame(UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM)
|
await self.push_frame(
|
||||||
|
UserImageRequestFrame(self._participant_id), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,8 +63,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_response = UserResponseAggregator()
|
user_response = UserResponseAggregator()
|
||||||
@@ -71,9 +73,7 @@ async def main():
|
|||||||
|
|
||||||
vision_aggregator = VisionImageFrameAggregator()
|
vision_aggregator = VisionImageFrameAggregator()
|
||||||
|
|
||||||
anthropic = AnthropicLLMService(
|
anthropic = AnthropicLLMService(api_key=os.getenv("ANTHROPIC_API_KEY"))
|
||||||
api_key=os.getenv("ANTHROPIC_API_KEY")
|
|
||||||
)
|
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -90,15 +90,17 @@ async def main():
|
|||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
image_requester.set_participant_id(participant["id"])
|
image_requester.set_participant_id(participant["id"])
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_response,
|
transport.input(),
|
||||||
image_requester,
|
user_response,
|
||||||
vision_aggregator,
|
image_requester,
|
||||||
anthropic,
|
vision_aggregator,
|
||||||
tts,
|
anthropic,
|
||||||
transport.output()
|
tts,
|
||||||
])
|
transport.output(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -106,5 +108,6 @@ async def main():
|
|||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -28,7 +29,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class TranscriptionLogger(FrameProcessor):
|
class TranscriptionLogger(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
@@ -40,8 +40,9 @@ async def main():
|
|||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
(room_url, _) = await configure(session)
|
(room_url, _) = await configure(session)
|
||||||
|
|
||||||
transport = DailyTransport(room_url, None, "Transcription bot",
|
transport = DailyTransport(
|
||||||
DailyParams(audio_in_enabled=True))
|
room_url, None, "Transcription bot", DailyParams(audio_in_enabled=True)
|
||||||
|
)
|
||||||
|
|
||||||
stt = WhisperSTTService()
|
stt = WhisperSTTService()
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from pipecat.transports.local.audio import LocalAudioTransport
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -26,7 +27,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class TranscriptionLogger(FrameProcessor):
|
class TranscriptionLogger(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -29,7 +30,6 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
class TranscriptionLogger(FrameProcessor):
|
class TranscriptionLogger(FrameProcessor):
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
@@ -41,8 +41,9 @@ async def main():
|
|||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
(room_url, _) = await configure(session)
|
(room_url, _) = await configure(session)
|
||||||
|
|
||||||
transport = DailyTransport(room_url, None, "Transcription bot",
|
transport = DailyTransport(
|
||||||
DailyParams(audio_in_enabled=True))
|
room_url, None, "Transcription bot", DailyParams(audio_in_enabled=True)
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -52,8 +53,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -61,15 +62,10 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
# Register a function_name of None to get all functions
|
# Register a function_name of None to get all functions
|
||||||
# sent to the same callback with an additional function_name parameter.
|
# sent to the same callback with an additional function_name parameter.
|
||||||
llm.register_function(
|
llm.register_function(None, fetch_weather_from_api, start_callback=start_fetch_weather)
|
||||||
None,
|
|
||||||
fetch_weather_from_api,
|
|
||||||
start_callback=start_fetch_weather)
|
|
||||||
|
|
||||||
fl_in = FrameLogger("Inner")
|
fl_in = FrameLogger("Inner")
|
||||||
fl_out = FrameLogger("Outer")
|
fl_out = FrameLogger("Outer")
|
||||||
@@ -89,17 +85,15 @@ async def main():
|
|||||||
},
|
},
|
||||||
"format": {
|
"format": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["celsius", "fahrenheit"],
|
||||||
"celsius",
|
|
||||||
"fahrenheit"],
|
|
||||||
"description": "The temperature unit to use. Infer this from the users location.",
|
"description": "The temperature unit to use. Infer this from the users location.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["location", "format"],
|
||||||
"location",
|
|
||||||
"format"],
|
|
||||||
},
|
},
|
||||||
})]
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@@ -110,16 +104,18 @@ async def main():
|
|||||||
context = OpenAILLMContext(messages, tools)
|
context = OpenAILLMContext(messages, tools)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
fl_in,
|
[
|
||||||
transport.input(),
|
fl_in,
|
||||||
context_aggregator.user(),
|
transport.input(),
|
||||||
llm,
|
context_aggregator.user(),
|
||||||
fl_out,
|
llm,
|
||||||
tts,
|
fl_out,
|
||||||
transport.output(),
|
tts,
|
||||||
context_aggregator.assistant(),
|
transport.output(),
|
||||||
])
|
context_aggregator.assistant(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@@ -133,5 +129,6 @@ async def main():
|
|||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -39,7 +40,11 @@ current_voice = "News Lady"
|
|||||||
async def switch_voice(function_name, tool_call_id, args, llm, context, result_callback):
|
async def switch_voice(function_name, tool_call_id, args, llm, context, result_callback):
|
||||||
global current_voice
|
global current_voice
|
||||||
current_voice = args["voice"]
|
current_voice = args["voice"]
|
||||||
await result_callback({"voice": f"You are now using your {current_voice} voice. Your responses should now be as if you were a {current_voice}."})
|
await result_callback(
|
||||||
|
{
|
||||||
|
"voice": f"You are now using your {current_voice} voice. Your responses should now be as if you were a {current_voice}."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def news_lady_filter(frame) -> bool:
|
async def news_lady_filter(frame) -> bool:
|
||||||
@@ -66,8 +71,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
news_lady = CartesiaTTSService(
|
news_lady = CartesiaTTSService(
|
||||||
@@ -85,9 +90,7 @@ async def main():
|
|||||||
voice_id="a0e99841-438c-4a64-b679-ae501e7d6091", # Barbershop Man
|
voice_id="a0e99841-438c-4a64-b679-ae501e7d6091", # Barbershop Man
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
llm.register_function("switch_voice", switch_voice)
|
llm.register_function("switch_voice", switch_voice)
|
||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
@@ -106,7 +109,9 @@ async def main():
|
|||||||
},
|
},
|
||||||
"required": ["voice"],
|
"required": ["voice"],
|
||||||
},
|
},
|
||||||
})]
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@@ -117,18 +122,20 @@ async def main():
|
|||||||
context = OpenAILLMContext(messages, tools)
|
context = OpenAILLMContext(messages, tools)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
context_aggregator.user(), # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
context_aggregator.user(), # User responses
|
||||||
ParallelPipeline( # TTS (one of the following vocies)
|
llm, # LLM
|
||||||
[FunctionFilter(news_lady_filter), news_lady], # News Lady voice
|
ParallelPipeline( # TTS (one of the following vocies)
|
||||||
[FunctionFilter(british_lady_filter), british_lady], # British Lady voice
|
[FunctionFilter(news_lady_filter), news_lady], # News Lady voice
|
||||||
[FunctionFilter(barbershop_man_filter), barbershop_man], # Barbershop Man voice
|
[FunctionFilter(british_lady_filter), british_lady], # British Lady voice
|
||||||
),
|
[FunctionFilter(barbershop_man_filter), barbershop_man], # Barbershop Man voice
|
||||||
transport.output(), # Transport bot output
|
),
|
||||||
context_aggregator.assistant(), # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
context_aggregator.assistant(), # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -139,7 +146,9 @@ async def main():
|
|||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": f"Please introduce yourself to the user and let them know the voices you can do. Your initial responses should be as if you were a {current_voice}."})
|
"content": f"Please introduce yourself to the user and let them know the voices you can do. Your initial responses should be as if you were a {current_voice}.",
|
||||||
|
}
|
||||||
|
)
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -64,8 +65,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True
|
vad_audio_passthrough=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
stt = WhisperSTTService(model=Model.LARGE)
|
stt = WhisperSTTService(model=Model.LARGE)
|
||||||
@@ -80,9 +81,7 @@ async def main():
|
|||||||
voice_id="846d6cb0-2301-48b6-9683-48f5618ea2f6", # Spanish-speaking Lady
|
voice_id="846d6cb0-2301-48b6-9683-48f5618ea2f6", # Spanish-speaking Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
llm.register_function("switch_language", switch_language)
|
llm.register_function("switch_language", switch_language)
|
||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
@@ -101,7 +100,9 @@ async def main():
|
|||||||
},
|
},
|
||||||
"required": ["language"],
|
"required": ["language"],
|
||||||
},
|
},
|
||||||
})]
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@@ -112,18 +113,20 @@ async def main():
|
|||||||
context = OpenAILLMContext(messages, tools)
|
context = OpenAILLMContext(messages, tools)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
stt, # STT
|
transport.input(), # Transport user input
|
||||||
context_aggregator.user(), # User responses
|
stt, # STT
|
||||||
llm, # LLM
|
context_aggregator.user(), # User responses
|
||||||
ParallelPipeline( # TTS (bot will speak the chosen language)
|
llm, # LLM
|
||||||
[FunctionFilter(english_filter), english_tts], # English
|
ParallelPipeline( # TTS (bot will speak the chosen language)
|
||||||
[FunctionFilter(spanish_filter), spanish_tts], # Spanish
|
[FunctionFilter(english_filter), english_tts], # English
|
||||||
),
|
[FunctionFilter(spanish_filter), spanish_tts], # Spanish
|
||||||
transport.output(), # Transport bot output
|
),
|
||||||
context_aggregator.assistant() # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
context_aggregator.assistant(), # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@@ -134,7 +137,9 @@ async def main():
|
|||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": f"Please introduce yourself to the user and let them know the languages you speak. Your initial responses should be in {current_language}."})
|
"content": f"Please introduce yourself to the user and let them know the languages you speak. Your initial responses should be in {current_language}.",
|
||||||
|
}
|
||||||
|
)
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -14,10 +14,16 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.deepgram import DeepgramTTSService
|
from pipecat.services.deepgram import DeepgramTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport, DailyTransportMessageFrame
|
from pipecat.transports.services.daily import (
|
||||||
|
DailyParams,
|
||||||
|
DailyTransport,
|
||||||
|
DailyTransportMessageFrame,
|
||||||
|
)
|
||||||
from pipecat.vad.silero import SileroVADAnalyzer
|
from pipecat.vad.silero import SileroVADAnalyzer
|
||||||
|
|
||||||
from runner import configure
|
from runner import configure
|
||||||
@@ -25,6 +31,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -43,15 +50,15 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = DeepgramTTSService(
|
tts = DeepgramTTSService(
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
||||||
voice="aura-asteria-en",
|
voice="aura-asteria-en",
|
||||||
base_url="http://0.0.0.0:8080/v1/speak"
|
base_url="http://0.0.0.0:8080/v1/speak",
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(
|
||||||
@@ -60,7 +67,7 @@ async def main():
|
|||||||
# model="gpt-4o"
|
# model="gpt-4o"
|
||||||
# Or, to use a local vLLM (or similar) api server
|
# Or, to use a local vLLM (or similar) api server
|
||||||
model="meta-llama/Meta-Llama-3-8B-Instruct",
|
model="meta-llama/Meta-Llama-3-8B-Instruct",
|
||||||
base_url="http://0.0.0.0:8000/v1"
|
base_url="http://0.0.0.0:8000/v1",
|
||||||
)
|
)
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
@@ -73,14 +80,16 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
tma_in, # User responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
||||||
|
|
||||||
@@ -93,8 +102,7 @@ async def main():
|
|||||||
# When the first participant joins, the bot should introduce itself.
|
# When the first participant joins, the bot should introduce itself.
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
# Handle "latency-ping" messages. The client will send app messages that look like
|
# Handle "latency-ping" messages. The client will send app messages that look like
|
||||||
@@ -111,14 +119,18 @@ async def main():
|
|||||||
logger.debug(f"Received latency ping app message: {message}")
|
logger.debug(f"Received latency ping app message: {message}")
|
||||||
ts = message["latency-ping"]["ts"]
|
ts = message["latency-ping"]["ts"]
|
||||||
# Send immediately
|
# Send immediately
|
||||||
transport.output().send_message(DailyTransportMessageFrame(
|
transport.output().send_message(
|
||||||
message={"latency-pong-msg-handler": {"ts": ts}},
|
DailyTransportMessageFrame(
|
||||||
participant_id=sender))
|
message={"latency-pong-msg-handler": {"ts": ts}}, participant_id=sender
|
||||||
|
)
|
||||||
|
)
|
||||||
# And push to the pipeline for the Daily transport.output to send
|
# And push to the pipeline for the Daily transport.output to send
|
||||||
await tma_in.push_frame(
|
await tma_in.push_frame(
|
||||||
DailyTransportMessageFrame(
|
DailyTransportMessageFrame(
|
||||||
message={"latency-pong-pipeline-delivery": {"ts": ts}},
|
message={"latency-pong-pipeline-delivery": {"ts": ts}},
|
||||||
participant_id=sender))
|
participant_id=sender,
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"message handling error: {e} - {message}")
|
logger.debug(f"message handling error: {e} - {message}")
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.processors.user_idle_processor import UserIdleProcessor
|
from pipecat.processors.user_idle_processor import UserIdleProcessor
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
@@ -26,6 +28,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -44,8 +47,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -53,9 +56,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -69,33 +70,41 @@ async def main():
|
|||||||
|
|
||||||
async def user_idle_callback(user_idle: UserIdleProcessor):
|
async def user_idle_callback(user_idle: UserIdleProcessor):
|
||||||
messages.append(
|
messages.append(
|
||||||
{"role": "system", "content": "Ask the user if they are still there and try to prompt for some input, but be short."})
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "Ask the user if they are still there and try to prompt for some input, but be short.",
|
||||||
|
}
|
||||||
|
)
|
||||||
await user_idle.push_frame(LLMMessagesFrame(messages))
|
await user_idle.push_frame(LLMMessagesFrame(messages))
|
||||||
|
|
||||||
user_idle = UserIdleProcessor(callback=user_idle_callback, timeout=5.0)
|
user_idle = UserIdleProcessor(callback=user_idle_callback, timeout=5.0)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
user_idle, # Idle user check-in
|
transport.input(), # Transport user input
|
||||||
tma_in, # User responses
|
user_idle, # Idle user check-in
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
tma_out # Assistant spoken responses
|
transport.output(), # Transport bot output
|
||||||
])
|
tma_out, # Assistant spoken responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(
|
task = PipelineTask(
|
||||||
allow_interruptions=True,
|
pipeline,
|
||||||
enable_metrics=True,
|
PipelineParams(
|
||||||
report_only_initial_ttfb=True,
|
allow_interruptions=True,
|
||||||
))
|
enable_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from runner import configure_with_args
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -29,12 +30,7 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
async def main():
|
async def main():
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument("-i", "--input", type=str, required=True, help="Input video file")
|
||||||
"-i",
|
|
||||||
"--input",
|
|
||||||
type=str,
|
|
||||||
required=True,
|
|
||||||
help="Input video file")
|
|
||||||
|
|
||||||
(room_url, _, args) = await configure_with_args(session, parser)
|
(room_url, _, args) = await configure_with_args(session, parser)
|
||||||
|
|
||||||
@@ -49,7 +45,7 @@ async def main():
|
|||||||
camera_out_width=1280,
|
camera_out_width=1280,
|
||||||
camera_out_height=720,
|
camera_out_height=720,
|
||||||
camera_out_is_live=True,
|
camera_out_is_live=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
gst = GStreamerPipelineSource(
|
gst = GStreamerPipelineSource(
|
||||||
@@ -59,13 +55,15 @@ async def main():
|
|||||||
video_height=720,
|
video_height=720,
|
||||||
audio_sample_rate=16000,
|
audio_sample_rate=16000,
|
||||||
audio_channels=1,
|
audio_channels=1,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
gst, # GStreamer file source
|
[
|
||||||
transport.output(), # Transport bot output
|
gst, # GStreamer file source
|
||||||
])
|
transport.output(), # Transport bot output
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -38,20 +39,22 @@ async def main():
|
|||||||
camera_out_width=1280,
|
camera_out_width=1280,
|
||||||
camera_out_height=720,
|
camera_out_height=720,
|
||||||
camera_out_is_live=True,
|
camera_out_is_live=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
gst = GStreamerPipelineSource(
|
gst = GStreamerPipelineSource(
|
||||||
pipeline="videotestsrc ! capsfilter caps=\"video/x-raw,width=1280,height=720,framerate=30/1\"",
|
pipeline='videotestsrc ! capsfilter caps="video/x-raw,width=1280,height=720,framerate=30/1"',
|
||||||
out_params=GStreamerPipelineSource.OutputParams(
|
out_params=GStreamerPipelineSource.OutputParams(
|
||||||
video_width=1280,
|
video_width=1280, video_height=720, clock_sync=False
|
||||||
video_height=720,
|
),
|
||||||
clock_sync=False))
|
)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
gst, # GStreamer file source
|
[
|
||||||
transport.output(), # Transport bot output
|
gst, # GStreamer file source
|
||||||
])
|
transport.output(), # Transport bot output
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -46,8 +47,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -56,8 +57,7 @@ async def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
llm = AnthropicLLMService(
|
llm = AnthropicLLMService(
|
||||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
api_key=os.getenv("ANTHROPIC_API_KEY"), model="claude-3-5-sonnet-20240620"
|
||||||
model="claude-3-5-sonnet-20240620"
|
|
||||||
)
|
)
|
||||||
llm.register_function("get_weather", get_weather)
|
llm.register_function("get_weather", get_weather)
|
||||||
|
|
||||||
@@ -90,18 +90,20 @@ async def main():
|
|||||||
context = OpenAILLMContext(messages, tools)
|
context = OpenAILLMContext(messages, tools)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
context_aggregator.user(), # User spoken responses
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
context_aggregator.user(), # User spoken responses
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
transport.output(), # Transport bot output
|
||||||
])
|
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
||||||
|
|
||||||
@ transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -55,8 +56,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -67,7 +68,7 @@ async def main():
|
|||||||
llm = AnthropicLLMService(
|
llm = AnthropicLLMService(
|
||||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
||||||
model="claude-3-5-sonnet-20240620",
|
model="claude-3-5-sonnet-20240620",
|
||||||
enable_prompt_caching_beta=True
|
enable_prompt_caching_beta=True,
|
||||||
)
|
)
|
||||||
llm.register_function("get_weather", get_weather)
|
llm.register_function("get_weather", get_weather)
|
||||||
llm.register_function("get_image", get_image)
|
llm.register_function("get_image", get_image)
|
||||||
@@ -100,7 +101,7 @@ async def main():
|
|||||||
},
|
},
|
||||||
"required": ["question"],
|
"required": ["question"],
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# todo: test with very short initial user message
|
# todo: test with very short initial user message
|
||||||
@@ -134,28 +135,28 @@ If you need to use a tool, simply use the tool. Do not tell the user the tool yo
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"text": system_prompt,
|
"text": system_prompt,
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{"role": "user", "content": "Start the conversation by introducing yourself."},
|
||||||
"role": "user",
|
]
|
||||||
"content": "Start the conversation by introducing yourself."
|
|
||||||
}]
|
|
||||||
|
|
||||||
context = OpenAILLMContext(messages, tools)
|
context = OpenAILLMContext(messages, tools)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
context_aggregator.user(), # User speech to text
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
context_aggregator.user(), # User speech to text
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
transport.output(), # Transport bot output
|
||||||
])
|
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
||||||
|
|
||||||
@ transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
global video_participant_id
|
global video_participant_id
|
||||||
video_participant_id = participant["id"]
|
video_participant_id = participant["id"]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -32,12 +33,8 @@ logger.add(sys.stderr, level="DEBUG")
|
|||||||
|
|
||||||
|
|
||||||
async def get_current_weather(
|
async def get_current_weather(
|
||||||
function_name,
|
function_name, tool_call_id, arguments, llm, context, result_callback
|
||||||
tool_call_id,
|
):
|
||||||
arguments,
|
|
||||||
llm,
|
|
||||||
context,
|
|
||||||
result_callback):
|
|
||||||
logger.debug("IN get_current_weather")
|
logger.debug("IN get_current_weather")
|
||||||
location = arguments["location"]
|
location = arguments["location"]
|
||||||
await result_callback(f"The weather in {location} is currently 72 degrees and sunny.")
|
await result_callback(f"The weather in {location} is currently 72 degrees and sunny.")
|
||||||
@@ -55,8 +52,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -104,26 +101,28 @@ Reminder:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages = [{"role": "system",
|
messages = [
|
||||||
"content": system_prompt},
|
{"role": "system", "content": system_prompt},
|
||||||
{"role": "user",
|
{"role": "user", "content": "Wait for the user to say something."},
|
||||||
"content": "Wait for the user to say something."}]
|
]
|
||||||
|
|
||||||
context = OpenAILLMContext(messages)
|
context = OpenAILLMContext(messages)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport user input
|
[
|
||||||
context_aggregator.user(), # User speech to text
|
transport.input(), # Transport user input
|
||||||
llm, # LLM
|
context_aggregator.user(), # User speech to text
|
||||||
tts, # TTS
|
llm, # LLM
|
||||||
transport.output(), # Transport bot output
|
tts, # TTS
|
||||||
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
transport.output(), # Transport bot output
|
||||||
])
|
context_aggregator.assistant(), # Assistant spoken responses and tool context
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
||||||
|
|
||||||
@ transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
|
|||||||
@@ -17,16 +17,13 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
|
|
||||||
async def configure_with_args(
|
async def configure_with_args(
|
||||||
aiohttp_session: aiohttp.ClientSession,
|
aiohttp_session: aiohttp.ClientSession, parser: argparse.ArgumentParser | None = None
|
||||||
parser: argparse.ArgumentParser | None = None):
|
):
|
||||||
if not parser:
|
if not parser:
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -42,15 +39,19 @@ async def configure_with_args(
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session)
|
aiohttp_session=aiohttp_session,
|
||||||
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
# the future.
|
# the future.
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -60,11 +61,7 @@ for i in range(1, 26):
|
|||||||
# Get the filename without the extension to use as the dictionary key
|
# Get the filename without the extension to use as the dictionary key
|
||||||
# Open the image and convert it to bytes
|
# Open the image and convert it to bytes
|
||||||
with Image.open(full_path) as img:
|
with Image.open(full_path) as img:
|
||||||
sprites.append(OutputImageRawFrame(
|
sprites.append(OutputImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
|
||||||
image=img.tobytes(),
|
|
||||||
size=img.size,
|
|
||||||
format=img.format)
|
|
||||||
)
|
|
||||||
|
|
||||||
flipped = sprites[::-1]
|
flipped = sprites[::-1]
|
||||||
sprites.extend(flipped)
|
sprites.extend(flipped)
|
||||||
@@ -110,7 +107,9 @@ class UserImageRequester(FrameProcessor):
|
|||||||
|
|
||||||
if self.participant_id and isinstance(frame, TextFrame):
|
if self.participant_id and isinstance(frame, TextFrame):
|
||||||
if frame.text == user_request_answer:
|
if frame.text == user_request_answer:
|
||||||
await self.push_frame(UserImageRequestFrame(self.participant_id), FrameDirection.UPSTREAM)
|
await self.push_frame(
|
||||||
|
UserImageRequestFrame(self.participant_id), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
await self.push_frame(TextFrame("Describe the image in a short sentence."))
|
await self.push_frame(TextFrame("Describe the image in a short sentence."))
|
||||||
elif isinstance(frame, UserImageRawFrame):
|
elif isinstance(frame, UserImageRawFrame):
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
@@ -154,8 +153,8 @@ async def main():
|
|||||||
camera_out_height=576,
|
camera_out_height=576,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -163,9 +162,7 @@ async def main():
|
|||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
ta = TalkingAnimation()
|
ta = TalkingAnimation()
|
||||||
|
|
||||||
@@ -188,17 +185,17 @@ async def main():
|
|||||||
|
|
||||||
ura = LLMUserResponseAggregator(messages)
|
ura = LLMUserResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
ura,
|
transport.input(),
|
||||||
llm,
|
ura,
|
||||||
ParallelPipeline(
|
llm,
|
||||||
[sa, ir, va, moondream],
|
ParallelPipeline([sa, ir, va, moondream], [tf, imgf]),
|
||||||
[tf, imgf]),
|
tts,
|
||||||
tts,
|
ta,
|
||||||
ta,
|
transport.output(),
|
||||||
transport.output()
|
]
|
||||||
])
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
await task.queue_frame(quiet_frame)
|
await task.queue_frame(quiet_frame)
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
|
|||||||
async def configure(aiohttp_session: aiohttp.ClientSession):
|
async def configure(aiohttp_session: aiohttp.ClientSession):
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -34,15 +31,18 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -65,37 +66,34 @@ async def start_agent(request: Request):
|
|||||||
if not room.url:
|
if not room.url:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail="Missing 'room' property in request data. Cannot start agent without a target room!")
|
detail="Missing 'room' property in request data. Cannot start agent without a target room!",
|
||||||
|
)
|
||||||
|
|
||||||
# Check if there is already an existing process running in this room
|
# Check if there is already an existing process running in this room
|
||||||
num_bots_in_room = sum(
|
num_bots_in_room = sum(
|
||||||
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None)
|
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None
|
||||||
|
)
|
||||||
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
||||||
status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
|
||||||
|
|
||||||
# Get the token for the room
|
# Get the token for the room
|
||||||
token = await daily_helpers["rest"].get_token(room.url)
|
token = await daily_helpers["rest"].get_token(room.url)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room.url}")
|
|
||||||
|
|
||||||
# Spawn a new agent, and join the user session
|
# Spawn a new agent, and join the user session
|
||||||
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
f"python3 -m bot -u {room.url} -t {token}"
|
|
||||||
],
|
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
)
|
)
|
||||||
bot_procs[proc.pid] = (proc, room.url)
|
bot_procs[proc.pid] = (proc, room.url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
return RedirectResponse(room.url)
|
return RedirectResponse(room.url)
|
||||||
|
|
||||||
@@ -107,8 +105,7 @@ def get_status(pid: int):
|
|||||||
|
|
||||||
# If the subprocess doesn't exist, return an error
|
# If the subprocess doesn't exist, return an error
|
||||||
if not proc:
|
if not proc:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail=f"Bot with process id: {pid} not found")
|
||||||
status_code=404, detail=f"Bot with process id: {pid} not found")
|
|
||||||
|
|
||||||
# Check the status of the subprocess
|
# Check the status of the subprocess
|
||||||
if proc[0].poll() is None:
|
if proc[0].poll() is None:
|
||||||
@@ -125,14 +122,10 @@ if __name__ == "__main__":
|
|||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
default_host = os.getenv("HOST", "0.0.0.0")
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Daily Moondream FastAPI server")
|
||||||
description="Daily Moondream FastAPI server")
|
parser.add_argument("--host", type=str, default=default_host, help="Host address")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument("--port", type=int, default=default_port, help="Port number")
|
||||||
default=default_host, help="Host address")
|
parser.add_argument("--reload", action="store_true", help="Reload code on change")
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -49,41 +50,44 @@ for file in sound_files:
|
|||||||
filename = os.path.splitext(os.path.basename(full_path))[0]
|
filename = os.path.splitext(os.path.basename(full_path))[0]
|
||||||
# Open the sound and convert it to bytes
|
# Open the sound and convert it to bytes
|
||||||
with wave.open(full_path) as audio_file:
|
with wave.open(full_path) as audio_file:
|
||||||
sounds[file] = OutputAudioRawFrame(audio_file.readframes(-1),
|
sounds[file] = OutputAudioRawFrame(
|
||||||
audio_file.getframerate(),
|
audio_file.readframes(-1), audio_file.getframerate(), audio_file.getnchannels()
|
||||||
audio_file.getnchannels())
|
)
|
||||||
|
|
||||||
|
|
||||||
class IntakeProcessor:
|
class IntakeProcessor:
|
||||||
|
|
||||||
def __init__(self, context: OpenAILLMContext):
|
def __init__(self, context: OpenAILLMContext):
|
||||||
print(f"Initializing context from IntakeProcessor")
|
print(f"Initializing context from IntakeProcessor")
|
||||||
context.add_message({"role": "system", "content": "You are Jessica, an agent for a company called Tri-County Health Services. Your job is to collect important information from the user before their doctor visit. You're talking to Chad Bailey. You should address the user by their first name and be polite and professional. You're not a medical professional, so you shouldn't provide any advice. Keep your responses short. Your job is to collect information to give to a doctor. Don't make assumptions about what values to plug into functions. Ask for clarification if a user response is ambiguous. Start by introducing yourself. Then, ask the user to confirm their identity by telling you their birthday, including the year. When they answer with their birthday, call the verify_birthday function."})
|
context.add_message(
|
||||||
context.set_tools([
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"role": "system",
|
||||||
"function": {
|
"content": "You are Jessica, an agent for a company called Tri-County Health Services. Your job is to collect important information from the user before their doctor visit. You're talking to Chad Bailey. You should address the user by their first name and be polite and professional. You're not a medical professional, so you shouldn't provide any advice. Keep your responses short. Your job is to collect information to give to a doctor. Don't make assumptions about what values to plug into functions. Ask for clarification if a user response is ambiguous. Start by introducing yourself. Then, ask the user to confirm their identity by telling you their birthday, including the year. When they answer with their birthday, call the verify_birthday function.",
|
||||||
"name": "verify_birthday",
|
}
|
||||||
"description": "Use this function to verify the user has provided their correct birthday.",
|
)
|
||||||
"parameters": {
|
context.set_tools(
|
||||||
"type": "object",
|
[
|
||||||
"properties": {
|
{
|
||||||
"birthday": {
|
"type": "function",
|
||||||
"type": "string",
|
"function": {
|
||||||
"description": "The user's birthdate, including the year. The user can provide it in any format, but convert it to YYYY-MM-DD format to call this function.",
|
"name": "verify_birthday",
|
||||||
}},
|
"description": "Use this function to verify the user has provided their correct birthday.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"birthday": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The user's birthdate, including the year. The user can provide it in any format, but convert it to YYYY-MM-DD format to call this function.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
async def verify_birthday(
|
async def verify_birthday(
|
||||||
self,
|
self, function_name, tool_call_id, args, llm, context, result_callback
|
||||||
function_name,
|
):
|
||||||
tool_call_id,
|
|
||||||
args,
|
|
||||||
llm,
|
|
||||||
context,
|
|
||||||
result_callback):
|
|
||||||
if args["birthday"] == "1983-01-01":
|
if args["birthday"] == "1983-01-01":
|
||||||
context.set_tools(
|
context.set_tools(
|
||||||
[
|
[
|
||||||
@@ -110,18 +114,35 @@ class IntakeProcessor:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}])
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
# It's a bit weird to push this to the LLM, but it gets it into the pipeline
|
# It's a bit weird to push this to the LLM, but it gets it into the pipeline
|
||||||
# await llm.push_frame(sounds["ding2.wav"], FrameDirection.DOWNSTREAM)
|
# await llm.push_frame(sounds["ding2.wav"], FrameDirection.DOWNSTREAM)
|
||||||
# We don't need the function call in the context, so just return a new
|
# We don't need the function call in the context, so just return a new
|
||||||
# system message and let the framework re-prompt
|
# system message and let the framework re-prompt
|
||||||
await result_callback([{"role": "system", "content": "Next, thank the user for confirming their identity, then ask the user to list their current prescriptions. Each prescription needs to have a medication name and a dosage. Do not call the list_prescriptions function with any unknown dosages."}])
|
await result_callback(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "Next, thank the user for confirming their identity, then ask the user to list their current prescriptions. Each prescription needs to have a medication name and a dosage. Do not call the list_prescriptions function with any unknown dosages.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# The user provided an incorrect birthday; ask them to try again
|
# The user provided an incorrect birthday; ask them to try again
|
||||||
await result_callback([{"role": "system", "content": "The user provided an incorrect birthday. Ask them for their birthday again. When they answer, call the verify_birthday function."}])
|
await result_callback(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "The user provided an incorrect birthday. Ask them for their birthday again. When they answer, call the verify_birthday function.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
async def start_prescriptions(self, function_name, llm, context):
|
async def start_prescriptions(self, function_name, llm, context):
|
||||||
print(f"!!! doing start prescriptions")
|
print(f"!!! doing start prescriptions")
|
||||||
@@ -144,16 +165,22 @@ class IntakeProcessor:
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "What the user is allergic to",
|
"description": "What the user is allergic to",
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}])
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
context.add_message(
|
context.add_message(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "Next, ask the user if they have any allergies. Once they have listed their allergies or confirmed they don't have any, call the list_allergies function."})
|
"content": "Next, ask the user if they have any allergies. Once they have listed their allergies or confirmed they don't have any, call the list_allergies function.",
|
||||||
|
}
|
||||||
|
)
|
||||||
print(f"!!! about to await llm process frame in start prescrpitions")
|
print(f"!!! about to await llm process frame in start prescrpitions")
|
||||||
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
||||||
print(f"!!! past await process frame in start prescriptions")
|
print(f"!!! past await process frame in start prescriptions")
|
||||||
@@ -179,17 +206,22 @@ class IntakeProcessor:
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The user's medical condition",
|
"description": "The user's medical condition",
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
|
)
|
||||||
context.add_message(
|
context.add_message(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "Now ask the user if they have any medical conditions the doctor should know about. Once they've answered the question, call the list_conditions function."})
|
"content": "Now ask the user if they have any medical conditions the doctor should know about. Once they've answered the question, call the list_conditions function.",
|
||||||
|
}
|
||||||
|
)
|
||||||
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
||||||
|
|
||||||
async def start_conditions(self, function_name, llm, context):
|
async def start_conditions(self, function_name, llm, context):
|
||||||
@@ -213,24 +245,31 @@ class IntakeProcessor:
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The user's reason for visiting the doctor",
|
"description": "The user's reason for visiting the doctor",
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}])
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
context.add_message(
|
context.add_message(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "Finally, ask the user the reason for their doctor visit today. Once they answer, call the list_visit_reasons function."})
|
"content": "Finally, ask the user the reason for their doctor visit today. Once they answer, call the list_visit_reasons function.",
|
||||||
|
}
|
||||||
|
)
|
||||||
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
||||||
|
|
||||||
async def start_visit_reasons(self, function_name, llm, context):
|
async def start_visit_reasons(self, function_name, llm, context):
|
||||||
print("!!! doing start visit reasons")
|
print("!!! doing start visit reasons")
|
||||||
# move to finish call
|
# move to finish call
|
||||||
context.set_tools([])
|
context.set_tools([])
|
||||||
context.add_message({"role": "system",
|
context.add_message(
|
||||||
"content": "Now, thank the user and end the conversation."})
|
{"role": "system", "content": "Now, thank the user and end the conversation."}
|
||||||
|
)
|
||||||
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
await llm.process_frame(OpenAILLMContextFrame(context), FrameDirection.DOWNSTREAM)
|
||||||
|
|
||||||
async def save_data(self, function_name, tool_call_id, args, llm, context, result_callback):
|
async def save_data(self, function_name, tool_call_id, args, llm, context, result_callback):
|
||||||
@@ -261,7 +300,7 @@ async def main():
|
|||||||
# tier="nova",
|
# tier="nova",
|
||||||
# model="2-general"
|
# model="2-general"
|
||||||
# )
|
# )
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -274,9 +313,7 @@ async def main():
|
|||||||
# voice_id="846d6cb0-2301-48b6-9683-48f5618ea2f6", # Spanish-speaking Lady
|
# voice_id="846d6cb0-2301-48b6-9683-48f5618ea2f6", # Spanish-speaking Lady
|
||||||
# )
|
# )
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
context = OpenAILLMContext(messages=messages)
|
context = OpenAILLMContext(messages=messages)
|
||||||
@@ -285,33 +322,31 @@ async def main():
|
|||||||
intake = IntakeProcessor(context)
|
intake = IntakeProcessor(context)
|
||||||
llm.register_function("verify_birthday", intake.verify_birthday)
|
llm.register_function("verify_birthday", intake.verify_birthday)
|
||||||
llm.register_function(
|
llm.register_function(
|
||||||
"list_prescriptions",
|
"list_prescriptions", intake.save_data, start_callback=intake.start_prescriptions
|
||||||
intake.save_data,
|
)
|
||||||
start_callback=intake.start_prescriptions)
|
|
||||||
llm.register_function(
|
llm.register_function(
|
||||||
"list_allergies",
|
"list_allergies", intake.save_data, start_callback=intake.start_allergies
|
||||||
intake.save_data,
|
)
|
||||||
start_callback=intake.start_allergies)
|
|
||||||
llm.register_function(
|
llm.register_function(
|
||||||
"list_conditions",
|
"list_conditions", intake.save_data, start_callback=intake.start_conditions
|
||||||
intake.save_data,
|
)
|
||||||
start_callback=intake.start_conditions)
|
|
||||||
llm.register_function(
|
llm.register_function(
|
||||||
"list_visit_reasons",
|
"list_visit_reasons", intake.save_data, start_callback=intake.start_visit_reasons
|
||||||
intake.save_data,
|
)
|
||||||
start_callback=intake.start_visit_reasons)
|
|
||||||
|
|
||||||
fl = FrameLogger("LLM Output")
|
fl = FrameLogger("LLM Output")
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Transport input
|
[
|
||||||
context_aggregator.user(), # User responses
|
transport.input(), # Transport input
|
||||||
llm, # LLM
|
context_aggregator.user(), # User responses
|
||||||
fl, # Frame logger
|
llm, # LLM
|
||||||
tts, # TTS
|
fl, # Frame logger
|
||||||
transport.output(), # Transport output
|
tts, # TTS
|
||||||
context_aggregator.assistant(), # Assistant responses
|
transport.output(), # Transport output
|
||||||
])
|
context_aggregator.assistant(), # Assistant responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=False))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=False))
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
|
|||||||
async def configure(aiohttp_session: aiohttp.ClientSession):
|
async def configure(aiohttp_session: aiohttp.ClientSession):
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -34,15 +31,19 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session)
|
aiohttp_session=aiohttp_session,
|
||||||
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
# the future.
|
# the future.
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -65,37 +66,34 @@ async def start_agent(request: Request):
|
|||||||
if not room.url:
|
if not room.url:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail="Missing 'room' property in request data. Cannot start agent without a target room!")
|
detail="Missing 'room' property in request data. Cannot start agent without a target room!",
|
||||||
|
)
|
||||||
|
|
||||||
# Check if there is already an existing process running in this room
|
# Check if there is already an existing process running in this room
|
||||||
num_bots_in_room = sum(
|
num_bots_in_room = sum(
|
||||||
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None)
|
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None
|
||||||
|
)
|
||||||
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
||||||
status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
|
||||||
|
|
||||||
# Get the token for the room
|
# Get the token for the room
|
||||||
token = await daily_helpers["rest"].get_token(room.url)
|
token = await daily_helpers["rest"].get_token(room.url)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room.url}")
|
|
||||||
|
|
||||||
# Spawn a new agent, and join the user session
|
# Spawn a new agent, and join the user session
|
||||||
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
f"python3 -m bot -u {room.url} -t {token}"
|
|
||||||
],
|
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
)
|
)
|
||||||
bot_procs[proc.pid] = (proc, room.url)
|
bot_procs[proc.pid] = (proc, room.url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
return RedirectResponse(room.url)
|
return RedirectResponse(room.url)
|
||||||
|
|
||||||
@@ -107,8 +105,7 @@ def get_status(pid: int):
|
|||||||
|
|
||||||
# If the subprocess doesn't exist, return an error
|
# If the subprocess doesn't exist, return an error
|
||||||
if not proc:
|
if not proc:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail=f"Bot with process id: {pid} not found")
|
||||||
status_code=404, detail=f"Bot with process id: {pid} not found")
|
|
||||||
|
|
||||||
# Check the status of the subprocess
|
# Check the status of the subprocess
|
||||||
if proc[0].poll() is None:
|
if proc[0].poll() is None:
|
||||||
@@ -125,14 +122,10 @@ if __name__ == "__main__":
|
|||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
default_host = os.getenv("HOST", "0.0.0.0")
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Daily Storyteller FastAPI server")
|
||||||
description="Daily Storyteller FastAPI server")
|
parser.add_argument("--host", type=str, default=default_host, help="Host address")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument("--port", type=int, default=default_port, help="Port number")
|
||||||
default=default_host, help="Host address")
|
parser.add_argument("--reload", action="store_true", help="Reload code on change")
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
print(f"to join a test room, visit http://localhost:{config.port}/start")
|
print(f"to join a test room, visit http://localhost:{config.port}/start")
|
||||||
|
|||||||
@@ -14,14 +14,17 @@ from PIL import Image
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import LLMAssistantResponseAggregator, LLMUserResponseAggregator
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
OutputImageRawFrame,
|
OutputImageRawFrame,
|
||||||
SpriteFrame,
|
SpriteFrame,
|
||||||
Frame,
|
Frame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
TTSAudioRawFrame,
|
TTSAudioRawFrame,
|
||||||
TTSStoppedFrame
|
TTSStoppedFrame,
|
||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
||||||
@@ -34,6 +37,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -49,11 +53,7 @@ for i in range(1, 26):
|
|||||||
# Get the filename without the extension to use as the dictionary key
|
# Get the filename without the extension to use as the dictionary key
|
||||||
# Open the image and convert it to bytes
|
# Open the image and convert it to bytes
|
||||||
with Image.open(full_path) as img:
|
with Image.open(full_path) as img:
|
||||||
sprites.append(OutputImageRawFrame(
|
sprites.append(OutputImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
|
||||||
image=img.tobytes(),
|
|
||||||
size=img.size,
|
|
||||||
format=img.format)
|
|
||||||
)
|
|
||||||
|
|
||||||
flipped = sprites[::-1]
|
flipped = sprites[::-1]
|
||||||
sprites.extend(flipped)
|
sprites.extend(flipped)
|
||||||
@@ -111,7 +111,7 @@ async def main():
|
|||||||
# tier="nova",
|
# tier="nova",
|
||||||
# model="2-general"
|
# model="2-general"
|
||||||
# )
|
# )
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = ElevenLabsTTSService(
|
tts = ElevenLabsTTSService(
|
||||||
@@ -120,7 +120,6 @@ async def main():
|
|||||||
# English
|
# English
|
||||||
#
|
#
|
||||||
voice_id="pNInz6obpgDQGcFmaJgB",
|
voice_id="pNInz6obpgDQGcFmaJgB",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Spanish
|
# Spanish
|
||||||
#
|
#
|
||||||
@@ -128,9 +127,7 @@ async def main():
|
|||||||
# voice_id="gD1IexrzCvsXPHUuT0s3",
|
# voice_id="gD1IexrzCvsXPHUuT0s3",
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -139,7 +136,6 @@ async def main():
|
|||||||
# English
|
# English
|
||||||
#
|
#
|
||||||
"content": "You are Chatbot, a friendly, helpful robot. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative and helpful way, but keep your responses brief. Start by introducing yourself.",
|
"content": "You are Chatbot, a friendly, helpful robot. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative and helpful way, but keep your responses brief. Start by introducing yourself.",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Spanish
|
# Spanish
|
||||||
#
|
#
|
||||||
@@ -152,15 +148,17 @@ async def main():
|
|||||||
|
|
||||||
ta = TalkingAnimation()
|
ta = TalkingAnimation()
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_response,
|
transport.input(),
|
||||||
llm,
|
user_response,
|
||||||
tts,
|
llm,
|
||||||
ta,
|
tts,
|
||||||
transport.output(),
|
ta,
|
||||||
assistant_response,
|
transport.output(),
|
||||||
])
|
assistant_response,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
await task.queue_frame(quiet_frame)
|
await task.queue_frame(quiet_frame)
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
|
|||||||
async def configure(aiohttp_session: aiohttp.ClientSession):
|
async def configure(aiohttp_session: aiohttp.ClientSession):
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -34,15 +31,18 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -65,37 +66,34 @@ async def start_agent(request: Request):
|
|||||||
if not room.url:
|
if not room.url:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail="Missing 'room' property in request data. Cannot start agent without a target room!")
|
detail="Missing 'room' property in request data. Cannot start agent without a target room!",
|
||||||
|
)
|
||||||
|
|
||||||
# Check if there is already an existing process running in this room
|
# Check if there is already an existing process running in this room
|
||||||
num_bots_in_room = sum(
|
num_bots_in_room = sum(
|
||||||
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None)
|
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None
|
||||||
|
)
|
||||||
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
||||||
status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
|
||||||
|
|
||||||
# Get the token for the room
|
# Get the token for the room
|
||||||
token = await daily_helpers["rest"].get_token(room.url)
|
token = await daily_helpers["rest"].get_token(room.url)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room.url}")
|
|
||||||
|
|
||||||
# Spawn a new agent, and join the user session
|
# Spawn a new agent, and join the user session
|
||||||
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
f"python3 -m bot -u {room.url} -t {token}"
|
|
||||||
],
|
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
)
|
)
|
||||||
bot_procs[proc.pid] = (proc, room.url)
|
bot_procs[proc.pid] = (proc, room.url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
return RedirectResponse(room.url)
|
return RedirectResponse(room.url)
|
||||||
|
|
||||||
@@ -107,8 +105,7 @@ def get_status(pid: int):
|
|||||||
|
|
||||||
# If the subprocess doesn't exist, return an error
|
# If the subprocess doesn't exist, return an error
|
||||||
if not proc:
|
if not proc:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail=f"Bot with process id: {pid} not found")
|
||||||
status_code=404, detail=f"Bot with process id: {pid} not found")
|
|
||||||
|
|
||||||
# Check the status of the subprocess
|
# Check the status of the subprocess
|
||||||
if proc[0].poll() is None:
|
if proc[0].poll() is None:
|
||||||
@@ -125,14 +122,10 @@ if __name__ == "__main__":
|
|||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
default_host = os.getenv("HOST", "0.0.0.0")
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Daily Storyteller FastAPI server")
|
||||||
description="Daily Storyteller FastAPI server")
|
parser.add_argument("--host", type=str, default=default_host, help="Host address")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument("--port", type=int, default=default_port, help="Port number")
|
||||||
default=default_host, help="Host address")
|
parser.add_argument("--reload", action="store_true", help="Reload code on change")
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ from pipecat.frames.frames import LLMMessagesFrame, StopTaskFrame, EndFrame
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import LLMAssistantResponseAggregator, LLMUserResponseAggregator
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
from pipecat.services.elevenlabs import ElevenLabsTTSService
|
||||||
from pipecat.services.fal import FalImageGenService
|
from pipecat.services.fal import FalImageGenService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport, DailyTransportMessageFrame
|
from pipecat.transports.services.daily import (
|
||||||
|
DailyParams,
|
||||||
|
DailyTransport,
|
||||||
|
DailyTransportMessageFrame,
|
||||||
|
)
|
||||||
|
|
||||||
from processors import StoryProcessor, StoryImageProcessor
|
from processors import StoryProcessor, StoryImageProcessor
|
||||||
from prompts import LLM_BASE_PROMPT, LLM_INTRO_PROMPT, CUE_USER_TURN
|
from prompts import LLM_BASE_PROMPT, LLM_INTRO_PROMPT, CUE_USER_TURN
|
||||||
@@ -22,6 +29,7 @@ from utils.helpers import load_sounds, load_images
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -33,7 +41,6 @@ images = load_images(["book1.png", "book2.png"])
|
|||||||
|
|
||||||
async def main(room_url, token=None):
|
async def main(room_url, token=None):
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
|
||||||
# -------------- Transport --------------- #
|
# -------------- Transport --------------- #
|
||||||
|
|
||||||
transport = DailyTransport(
|
transport = DailyTransport(
|
||||||
@@ -47,17 +54,14 @@ async def main(room_url, token=None):
|
|||||||
camera_out_height=768,
|
camera_out_height=768,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Transport created for room:" + room_url)
|
logger.debug("Transport created for room:" + room_url)
|
||||||
|
|
||||||
# -------------- Services --------------- #
|
# -------------- Services --------------- #
|
||||||
|
|
||||||
llm_service = OpenAILLMService(
|
llm_service = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
tts_service = ElevenLabsTTSService(
|
tts_service = ElevenLabsTTSService(
|
||||||
api_key=os.getenv("ELEVENLABS_API_KEY"),
|
api_key=os.getenv("ELEVENLABS_API_KEY"),
|
||||||
@@ -65,10 +69,7 @@ async def main(room_url, token=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fal_service_params = FalImageGenService.InputParams(
|
fal_service_params = FalImageGenService.InputParams(
|
||||||
image_size={
|
image_size={"width": 768, "height": 768}
|
||||||
"width": 768,
|
|
||||||
"height": 768
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fal_service = FalImageGenService(
|
fal_service = FalImageGenService(
|
||||||
@@ -110,12 +111,12 @@ async def main(room_url, token=None):
|
|||||||
transport.capture_participant_transcription(participant["id"])
|
transport.capture_participant_transcription(participant["id"])
|
||||||
await intro_task.queue_frames(
|
await intro_task.queue_frames(
|
||||||
[
|
[
|
||||||
images['book1'],
|
images["book1"],
|
||||||
LLMMessagesFrame([LLM_INTRO_PROMPT]),
|
LLMMessagesFrame([LLM_INTRO_PROMPT]),
|
||||||
DailyTransportMessageFrame(CUE_USER_TURN),
|
DailyTransportMessageFrame(CUE_USER_TURN),
|
||||||
sounds["listening"],
|
sounds["listening"],
|
||||||
images['book2'],
|
images["book2"],
|
||||||
StopTaskFrame()
|
StopTaskFrame(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -125,16 +126,18 @@ async def main(room_url, token=None):
|
|||||||
|
|
||||||
# The main story pipeline is used to continue the story based on user
|
# The main story pipeline is used to continue the story based on user
|
||||||
# input.
|
# input.
|
||||||
main_pipeline = Pipeline([
|
main_pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
user_responses,
|
transport.input(),
|
||||||
llm_service,
|
user_responses,
|
||||||
story_processor,
|
llm_service,
|
||||||
image_processor,
|
story_processor,
|
||||||
tts_service,
|
image_processor,
|
||||||
transport.output(),
|
tts_service,
|
||||||
llm_responses
|
transport.output(),
|
||||||
])
|
llm_responses,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
main_task = PipelineTask(main_pipeline)
|
main_task = PipelineTask(main_pipeline)
|
||||||
|
|
||||||
@@ -150,6 +153,7 @@ async def main(room_url, token=None):
|
|||||||
|
|
||||||
await runner.run(main_task)
|
await runner.run(main_task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Daily Storyteller Bot")
|
parser = argparse.ArgumentParser(description="Daily Storyteller Bot")
|
||||||
parser.add_argument("-u", type=str, help="Room URL")
|
parser.add_argument("-u", type=str, help="Room URL")
|
||||||
|
|||||||
@@ -20,10 +20,15 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.responses import FileResponse, JSONResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
|
||||||
from pipecat.transports.services.helpers.daily_rest import (
|
from pipecat.transports.services.helpers.daily_rest import (
|
||||||
DailyRESTHelper, DailyRoomObject, DailyRoomProperties, DailyRoomParams)
|
DailyRESTHelper,
|
||||||
|
DailyRoomObject,
|
||||||
|
DailyRoomProperties,
|
||||||
|
DailyRoomParams,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# ------------ Fast API Config ------------ #
|
# ------------ Fast API Config ------------ #
|
||||||
@@ -38,12 +43,13 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -85,55 +91,50 @@ async def start_bot(request: Request) -> JSONResponse:
|
|||||||
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", "")
|
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", "")
|
||||||
|
|
||||||
if not room_url:
|
if not room_url:
|
||||||
params = DailyRoomParams(
|
params = DailyRoomParams(properties=DailyRoomProperties())
|
||||||
properties=DailyRoomProperties()
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
room: DailyRoomObject = await daily_helpers["rest"].create_room(params=params)
|
room: DailyRoomObject = await daily_helpers["rest"].create_room(params=params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Unable to provision room {e}")
|
||||||
status_code=500,
|
|
||||||
detail=f"Unable to provision room {e}")
|
|
||||||
else:
|
else:
|
||||||
# Check passed room URL exists, we should assume that it already has a sip set up
|
# Check passed room URL exists, we should assume that it already has a sip set up
|
||||||
try:
|
try:
|
||||||
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
room: DailyRoomObject = await daily_helpers["rest"].get_room_from_url(room_url)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Room not found: {room_url}")
|
||||||
status_code=500, detail=f"Room not found: {room_url}")
|
|
||||||
|
|
||||||
# Give the agent a token to join the session
|
# Give the agent a token to join the session
|
||||||
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
|
||||||
if not room or not token:
|
if not room or not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room_url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room_url}")
|
|
||||||
|
|
||||||
# Launch a new VM, or run as a shell process (not recommended)
|
# Launch a new VM, or run as a shell process (not recommended)
|
||||||
if os.getenv("RUN_AS_VM", False):
|
if os.getenv("RUN_AS_VM", False):
|
||||||
try:
|
try:
|
||||||
await virtualize_bot(room.url, token)
|
await virtualize_bot(room.url, token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to spawn VM: {e}")
|
||||||
status_code=500, detail=f"Failed to spawn VM: {e}")
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[f"python3 -m bot -u {room.url} -t {token}"],
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__)))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
# Grab a token for the user to join with
|
# Grab a token for the user to join with
|
||||||
user_token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
user_token = await daily_helpers["rest"].get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
|
||||||
return JSONResponse({
|
return JSONResponse(
|
||||||
"room_url": room.url,
|
{
|
||||||
"token": user_token,
|
"room_url": room.url,
|
||||||
})
|
"token": user_token,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/{path_name:path}", response_class=FileResponse)
|
@app.get("/{path_name:path}", response_class=FileResponse)
|
||||||
@@ -155,6 +156,7 @@ async def catch_all(path_name: Optional[str] = ""):
|
|||||||
|
|
||||||
# ------------ Virtualization ------------ #
|
# ------------ Virtualization ------------ #
|
||||||
|
|
||||||
|
|
||||||
async def virtualize_bot(room_url: str, token: str):
|
async def virtualize_bot(room_url: str, token: str):
|
||||||
"""
|
"""
|
||||||
This is an example of how to virtualize the bot using Fly.io
|
This is an example of how to virtualize the bot using Fly.io
|
||||||
@@ -163,20 +165,19 @@ async def virtualize_bot(room_url: str, token: str):
|
|||||||
FLY_API_HOST = os.getenv("FLY_API_HOST", "https://api.machines.dev/v1")
|
FLY_API_HOST = os.getenv("FLY_API_HOST", "https://api.machines.dev/v1")
|
||||||
FLY_APP_NAME = os.getenv("FLY_APP_NAME", "storytelling-chatbot")
|
FLY_APP_NAME = os.getenv("FLY_APP_NAME", "storytelling-chatbot")
|
||||||
FLY_API_KEY = os.getenv("FLY_API_KEY", "")
|
FLY_API_KEY = os.getenv("FLY_API_KEY", "")
|
||||||
FLY_HEADERS = {
|
FLY_HEADERS = {"Authorization": f"Bearer {FLY_API_KEY}", "Content-Type": "application/json"}
|
||||||
'Authorization': f"Bearer {FLY_API_KEY}",
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
# Use the same image as the bot runner
|
# Use the same image as the bot runner
|
||||||
async with session.get(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS) as r:
|
async with session.get(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Unable to get machine info from Fly: {text}")
|
raise Exception(f"Unable to get machine info from Fly: {text}")
|
||||||
|
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
image = data[0]['config']['image']
|
image = data[0]["config"]["image"]
|
||||||
|
|
||||||
# Machine configuration
|
# Machine configuration
|
||||||
cmd = f"python3 src/bot.py -u {room_url} -t {token}"
|
cmd = f"python3 src/bot.py -u {room_url} -t {token}"
|
||||||
@@ -185,31 +186,28 @@ async def virtualize_bot(room_url: str, token: str):
|
|||||||
"config": {
|
"config": {
|
||||||
"image": image,
|
"image": image,
|
||||||
"auto_destroy": True,
|
"auto_destroy": True,
|
||||||
"init": {
|
"init": {"cmd": cmd},
|
||||||
"cmd": cmd
|
"restart": {"policy": "no"},
|
||||||
},
|
"guest": {"cpu_kind": "shared", "cpus": 1, "memory_mb": 512},
|
||||||
"restart": {
|
|
||||||
"policy": "no"
|
|
||||||
},
|
|
||||||
"guest": {
|
|
||||||
"cpu_kind": "shared",
|
|
||||||
"cpus": 1,
|
|
||||||
"memory_mb": 512
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Spawn a new machine instance
|
# Spawn a new machine instance
|
||||||
async with session.post(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS, json=worker_props) as r:
|
async with session.post(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines", headers=FLY_HEADERS, json=worker_props
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Problem starting a bot worker: {text}")
|
raise Exception(f"Problem starting a bot worker: {text}")
|
||||||
|
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
# Wait for the machine to enter the started state
|
# Wait for the machine to enter the started state
|
||||||
vm_id = data['id']
|
vm_id = data["id"]
|
||||||
|
|
||||||
async with session.get(f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines/{vm_id}/wait?state=started", headers=FLY_HEADERS) as r:
|
async with session.get(
|
||||||
|
f"{FLY_API_HOST}/apps/{FLY_APP_NAME}/machines/{vm_id}/wait?state=started",
|
||||||
|
headers=FLY_HEADERS,
|
||||||
|
) as r:
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
raise Exception(f"Bot was unable to enter started state: {text}")
|
raise Exception(f"Bot was unable to enter started state: {text}")
|
||||||
@@ -221,8 +219,13 @@ async def virtualize_bot(room_url: str, token: str):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Check environment variables
|
# Check environment variables
|
||||||
required_env_vars = ['OPENAI_API_KEY', 'DAILY_API_KEY',
|
required_env_vars = [
|
||||||
'FAL_KEY', 'ELEVENLABS_VOICE_ID', 'ELEVENLABS_API_KEY']
|
"OPENAI_API_KEY",
|
||||||
|
"DAILY_API_KEY",
|
||||||
|
"FAL_KEY",
|
||||||
|
"ELEVENLABS_VOICE_ID",
|
||||||
|
"ELEVENLABS_API_KEY",
|
||||||
|
]
|
||||||
for env_var in required_env_vars:
|
for env_var in required_env_vars:
|
||||||
if env_var not in os.environ:
|
if env_var not in os.environ:
|
||||||
raise Exception(f"Missing environment variable: {env_var}.")
|
raise Exception(f"Missing environment variable: {env_var}.")
|
||||||
@@ -232,20 +235,11 @@ if __name__ == "__main__":
|
|||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
default_host = os.getenv("HOST", "0.0.0.0")
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Daily Storyteller FastAPI server")
|
||||||
description="Daily Storyteller FastAPI server")
|
parser.add_argument("--host", type=str, default=default_host, help="Host address")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument("--port", type=int, default=default_port, help="Port number")
|
||||||
default=default_host, help="Host address")
|
parser.add_argument("--reload", action="store_true", help="Reload code on change")
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
uvicorn.run(
|
uvicorn.run("bot_runner:app", host=config.host, port=config.port, reload=config.reload)
|
||||||
"bot_runner:app",
|
|
||||||
host=config.host,
|
|
||||||
port=config.port,
|
|
||||||
reload=config.reload
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
UserStoppedSpeakingFrame)
|
UserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.transports.services.daily import DailyTransportMessageFrame
|
from pipecat.transports.services.daily import DailyTransportMessageFrame
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ class StoryPromptFrame(TextFrame):
|
|||||||
|
|
||||||
# ------------ Frame Processors ----------- #
|
# ------------ Frame Processors ----------- #
|
||||||
|
|
||||||
|
|
||||||
class StoryImageProcessor(FrameProcessor):
|
class StoryImageProcessor(FrameProcessor):
|
||||||
"""
|
"""
|
||||||
Processor for image prompt frames that will be sent to the FAL service.
|
Processor for image prompt frames that will be sent to the FAL service.
|
||||||
@@ -113,7 +115,7 @@ class StoryProcessor(FrameProcessor):
|
|||||||
# Extract the image prompt from the text using regex
|
# Extract the image prompt from the text using regex
|
||||||
image_prompt = re.search(r"<(.*?)>", self._text).group(1)
|
image_prompt = re.search(r"<(.*?)>", self._text).group(1)
|
||||||
# Remove the image prompt from the text
|
# Remove the image prompt from the text
|
||||||
self._text = re.sub(r"<.*?>", '', self._text, count=1)
|
self._text = re.sub(r"<.*?>", "", self._text, count=1)
|
||||||
# Process the image prompt frame
|
# Process the image prompt frame
|
||||||
await self.push_frame(StoryImageFrame(image_prompt))
|
await self.push_frame(StoryImageFrame(image_prompt))
|
||||||
|
|
||||||
@@ -124,8 +126,7 @@ class StoryProcessor(FrameProcessor):
|
|||||||
if re.search(r".*\[[bB]reak\].*", self._text):
|
if re.search(r".*\[[bB]reak\].*", self._text):
|
||||||
# Remove the [break] token from the text
|
# Remove the [break] token from the text
|
||||||
# so it isn't spoken out loud by the TTS
|
# so it isn't spoken out loud by the TTS
|
||||||
self._text = re.sub(r'\[[bB]reak\]', '',
|
self._text = re.sub(r"\[[bB]reak\]", "", self._text, flags=re.IGNORECASE)
|
||||||
self._text, flags=re.IGNORECASE)
|
|
||||||
self._text = self._text.replace("\n", " ")
|
self._text = self._text.replace("\n", " ")
|
||||||
if len(self._text) > 2:
|
if len(self._text) > 2:
|
||||||
# Append the sentence to the story
|
# Append the sentence to the story
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ LLM_INTRO_PROMPT = {
|
|||||||
"content": "You are a creative storyteller who loves to tell whimsical, fantastical stories. \
|
"content": "You are a creative storyteller who loves to tell whimsical, fantastical stories. \
|
||||||
Your goal is to craft an engaging and fun story. \
|
Your goal is to craft an engaging and fun story. \
|
||||||
Start by asking the user what kind of story they'd like to hear. Don't provide any examples. \
|
Start by asking the user what kind of story they'd like to hear. Don't provide any examples. \
|
||||||
Keep your response to only a few sentences."
|
Keep your response to only a few sentences.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ LLM_BASE_PROMPT = {
|
|||||||
Responses should use the format: <...> story sentence [break] <...> story sentence [break] ... \
|
Responses should use the format: <...> story sentence [break] <...> story sentence [break] ... \
|
||||||
After each response, ask me how I'd like the story to continue and wait for my input. \
|
After each response, ask me how I'd like the story to continue and wait for my input. \
|
||||||
Please ensure your responses are less than 3-4 sentences long. \
|
Please ensure your responses are less than 3-4 sentences long. \
|
||||||
Please refrain from using any explicit language or content. Do not tell scary stories."
|
Please refrain from using any explicit language or content. Do not tell scary stories.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ def load_images(image_files):
|
|||||||
# Open the image and convert it to bytes
|
# Open the image and convert it to bytes
|
||||||
with Image.open(full_path) as img:
|
with Image.open(full_path) as img:
|
||||||
images[filename] = OutputImageRawFrame(
|
images[filename] = OutputImageRawFrame(
|
||||||
image=img.tobytes(), size=img.size, format=img.format)
|
image=img.tobytes(), size=img.size, format=img.format
|
||||||
|
)
|
||||||
return images
|
return images
|
||||||
|
|
||||||
|
|
||||||
@@ -31,8 +32,10 @@ def load_sounds(sound_files):
|
|||||||
filename = os.path.splitext(os.path.basename(full_path))[0]
|
filename = os.path.splitext(os.path.basename(full_path))[0]
|
||||||
# Open the sound and convert it to bytes
|
# Open the sound and convert it to bytes
|
||||||
with wave.open(full_path) as audio_file:
|
with wave.open(full_path) as audio_file:
|
||||||
sounds[filename] = OutputAudioRawFrame(audio=audio_file.readframes(-1),
|
sounds[filename] = OutputAudioRawFrame(
|
||||||
sample_rate=audio_file.getframerate(),
|
audio=audio_file.readframes(-1),
|
||||||
num_channels=audio_file.getnchannels())
|
sample_rate=audio_file.getframerate(),
|
||||||
|
num_channels=audio_file.getnchannels(),
|
||||||
|
)
|
||||||
|
|
||||||
return sounds
|
return sounds
|
||||||
|
|||||||
@@ -17,16 +17,13 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
|
|
||||||
async def configure_with_args(
|
async def configure_with_args(
|
||||||
aiohttp_session: aiohttp.ClientSession,
|
aiohttp_session: aiohttp.ClientSession, parser: argparse.ArgumentParser | None = None
|
||||||
parser: argparse.ArgumentParser | None = None):
|
):
|
||||||
if not parser:
|
if not parser:
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -42,15 +39,19 @@ async def configure_with_args(
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session)
|
aiohttp_session=aiohttp_session,
|
||||||
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
# the future.
|
# the future.
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator, LLMUserResponseAggregator)
|
LLMAssistantResponseAggregator,
|
||||||
|
LLMUserResponseAggregator,
|
||||||
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
@@ -24,6 +26,7 @@ from runner import configure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# Run this script directly from your command line.
|
# Run this script directly from your command line.
|
||||||
@@ -45,15 +48,17 @@ def truncate_content(content, model_name):
|
|||||||
return encoding.decode(truncated_tokens)
|
return encoding.decode(truncated_tokens)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
# Main function to extract content from url
|
# Main function to extract content from url
|
||||||
|
|
||||||
|
|
||||||
async def get_article_content(url: str, aiohttp_session: aiohttp.ClientSession):
|
async def get_article_content(url: str, aiohttp_session: aiohttp.ClientSession):
|
||||||
if 'arxiv.org' in url:
|
if "arxiv.org" in url:
|
||||||
return await get_arxiv_content(url, aiohttp_session)
|
return await get_arxiv_content(url, aiohttp_session)
|
||||||
else:
|
else:
|
||||||
return await get_wikipedia_content(url, aiohttp_session)
|
return await get_wikipedia_content(url, aiohttp_session)
|
||||||
|
|
||||||
|
|
||||||
# Helper function to extract content from Wikipedia url (this is
|
# Helper function to extract content from Wikipedia url (this is
|
||||||
# technically agnostic to URL type but will work best with Wikipedia
|
# technically agnostic to URL type but will work best with Wikipedia
|
||||||
# articles)
|
# articles)
|
||||||
@@ -65,23 +70,24 @@ async def get_wikipedia_content(url: str, aiohttp_session: aiohttp.ClientSession
|
|||||||
return "Failed to download Wikipedia article."
|
return "Failed to download Wikipedia article."
|
||||||
|
|
||||||
text = await response.text()
|
text = await response.text()
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
|
|
||||||
content = soup.find('div', {'class': 'mw-parser-output'})
|
content = soup.find("div", {"class": "mw-parser-output"})
|
||||||
|
|
||||||
if content:
|
if content:
|
||||||
return content.get_text()
|
return content.get_text()
|
||||||
else:
|
else:
|
||||||
return "Failed to extract Wikipedia article content."
|
return "Failed to extract Wikipedia article content."
|
||||||
|
|
||||||
|
|
||||||
# Helper function to extract content from arXiv url
|
# Helper function to extract content from arXiv url
|
||||||
|
|
||||||
|
|
||||||
async def get_arxiv_content(url: str, aiohttp_session: aiohttp.ClientSession):
|
async def get_arxiv_content(url: str, aiohttp_session: aiohttp.ClientSession):
|
||||||
if '/abs/' in url:
|
if "/abs/" in url:
|
||||||
url = url.replace('/abs/', '/pdf/')
|
url = url.replace("/abs/", "/pdf/")
|
||||||
if not url.endswith('.pdf'):
|
if not url.endswith(".pdf"):
|
||||||
url += '.pdf'
|
url += ".pdf"
|
||||||
|
|
||||||
async with aiohttp_session.get(url) as response:
|
async with aiohttp_session.get(url) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
@@ -95,6 +101,7 @@ async def get_arxiv_content(url: str, aiohttp_session: aiohttp.ClientSession):
|
|||||||
text += page.extract_text()
|
text += page.extract_text()
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
# This is the main function that handles STT -> LLM -> TTS
|
# This is the main function that handles STT -> LLM -> TTS
|
||||||
|
|
||||||
|
|
||||||
@@ -116,8 +123,8 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer()
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -129,29 +136,33 @@ async def main():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o-mini")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o-mini")
|
|
||||||
|
|
||||||
messages = [{
|
messages = [
|
||||||
"role": "system", "content": f"""You are an AI study partner. You have been given the following article content:
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": f"""You are an AI study partner. You have been given the following article content:
|
||||||
|
|
||||||
{article_content}
|
{article_content}
|
||||||
|
|
||||||
Your task is to help the user understand and learn from this article in 2 sentences. THESE RESPONSES SHOULD BE ONLY MAX 2 SENTENCES. THIS INSTRUCTION IS VERY IMPORTANT. RESPONSES SHOULDN'T BE LONG.
|
Your task is to help the user understand and learn from this article in 2 sentences. THESE RESPONSES SHOULD BE ONLY MAX 2 SENTENCES. THIS INSTRUCTION IS VERY IMPORTANT. RESPONSES SHOULDN'T BE LONG.
|
||||||
""", }, ]
|
""",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(),
|
[
|
||||||
tma_in,
|
transport.input(),
|
||||||
llm,
|
tma_in,
|
||||||
tts,
|
llm,
|
||||||
transport.output(),
|
tts,
|
||||||
tma_out,
|
transport.output(),
|
||||||
])
|
tma_out,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True, enable_metrics=True))
|
||||||
|
|
||||||
@@ -161,12 +172,15 @@ Your task is to help the user understand and learn from this article in 2 senten
|
|||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "Hello! I'm ready to discuss the article with you. What would you like to learn about?"})
|
"content": "Hello! I'm ready to discuss the article with you. What would you like to learn about?",
|
||||||
|
}
|
||||||
|
)
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ from pipecat.transports.services.daily import (
|
|||||||
DailyParams,
|
DailyParams,
|
||||||
DailyTranscriptionSettings,
|
DailyTranscriptionSettings,
|
||||||
DailyTransport,
|
DailyTransport,
|
||||||
DailyTransportMessageFrame)
|
DailyTransportMessageFrame,
|
||||||
|
)
|
||||||
|
|
||||||
from runner import configure
|
from runner import configure
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -44,7 +46,6 @@ It also isn't saving what the user or bot says into the context object for use i
|
|||||||
# We need to use a custom service here to yield LLM frames without saving
|
# We need to use a custom service here to yield LLM frames without saving
|
||||||
# any context
|
# any context
|
||||||
class TranslationProcessor(FrameProcessor):
|
class TranslationProcessor(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, language):
|
def __init__(self, language):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._language = language
|
self._language = language
|
||||||
@@ -80,10 +81,7 @@ class TranslationSubtitles(FrameProcessor):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if isinstance(frame, TextFrame):
|
if isinstance(frame, TextFrame):
|
||||||
message = {
|
message = {"language": self._language, "text": frame.text}
|
||||||
"language": self._language,
|
|
||||||
"text": frame.text
|
|
||||||
}
|
|
||||||
await self.push_frame(DailyTransportMessageFrame(message))
|
await self.push_frame(DailyTransportMessageFrame(message))
|
||||||
|
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
@@ -100,10 +98,8 @@ async def main():
|
|||||||
DailyParams(
|
DailyParams(
|
||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
transcription_settings=DailyTranscriptionSettings(extra={
|
transcription_settings=DailyTranscriptionSettings(extra={"interim_results": False}),
|
||||||
"interim_results": False
|
),
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tts = AzureTTSService(
|
tts = AzureTTSService(
|
||||||
@@ -112,26 +108,14 @@ async def main():
|
|||||||
voice="es-ES-AlvaroNeural",
|
voice="es-ES-AlvaroNeural",
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o"
|
|
||||||
)
|
|
||||||
|
|
||||||
sa = SentenceAggregator()
|
sa = SentenceAggregator()
|
||||||
tp = TranslationProcessor("Spanish")
|
tp = TranslationProcessor("Spanish")
|
||||||
lfra = LLMFullResponseAggregator()
|
lfra = LLMFullResponseAggregator()
|
||||||
ts = TranslationSubtitles("spanish")
|
ts = TranslationSubtitles("spanish")
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline([transport.input(), sa, tp, llm, lfra, ts, tts, transport.output()])
|
||||||
transport.input(),
|
|
||||||
sa,
|
|
||||||
tp,
|
|
||||||
llm,
|
|
||||||
lfra,
|
|
||||||
ts,
|
|
||||||
tts,
|
|
||||||
transport.output()
|
|
||||||
])
|
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,8 @@ from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
|
|||||||
async def configure(aiohttp_session: aiohttp.ClientSession):
|
async def configure(aiohttp_session: aiohttp.ClientSession):
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-u",
|
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
||||||
"--url",
|
)
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--apikey",
|
"--apikey",
|
||||||
@@ -35,15 +32,18 @@ async def configure(aiohttp_session: aiohttp.ClientSession):
|
|||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
||||||
|
)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
raise Exception(
|
||||||
|
"No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||||
|
)
|
||||||
|
|
||||||
daily_rest_helper = DailyRESTHelper(
|
daily_rest_helper = DailyRESTHelper(
|
||||||
daily_api_key=key,
|
daily_api_key=key,
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
# Create a meeting token for the given room with an expiration 1 hour in
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ async def lifespan(app: FastAPI):
|
|||||||
aiohttp_session = aiohttp.ClientSession()
|
aiohttp_session = aiohttp.ClientSession()
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
daily_helpers["rest"] = DailyRESTHelper(
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", 'https://api.daily.co/v1'),
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
aiohttp_session=aiohttp_session
|
aiohttp_session=aiohttp_session,
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
await aiohttp_session.close()
|
await aiohttp_session.close()
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -65,37 +66,34 @@ async def start_agent(request: Request):
|
|||||||
if not room.url:
|
if not room.url:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail="Missing 'room' property in request data. Cannot start agent without a target room!")
|
detail="Missing 'room' property in request data. Cannot start agent without a target room!",
|
||||||
|
)
|
||||||
|
|
||||||
# Check if there is already an existing process running in this room
|
# Check if there is already an existing process running in this room
|
||||||
num_bots_in_room = sum(
|
num_bots_in_room = sum(
|
||||||
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None)
|
1 for proc in bot_procs.values() if proc[1] == room.url and proc[0].poll() is None
|
||||||
|
)
|
||||||
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
||||||
status_code=500, detail=f"Max bot limited reach for room: {room.url}")
|
|
||||||
|
|
||||||
# Get the token for the room
|
# Get the token for the room
|
||||||
token = await daily_helpers["rest"].get_token(room.url)
|
token = await daily_helpers["rest"].get_token(room.url)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}")
|
||||||
status_code=500, detail=f"Failed to get token for room: {room.url}")
|
|
||||||
|
|
||||||
# Spawn a new agent, and join the user session
|
# Spawn a new agent, and join the user session
|
||||||
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[
|
[f"python3 -m bot -u {room.url} -t {token}"],
|
||||||
f"python3 -m bot -u {room.url} -t {token}"
|
|
||||||
],
|
|
||||||
shell=True,
|
shell=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
cwd=os.path.dirname(os.path.abspath(__file__)),
|
||||||
)
|
)
|
||||||
bot_procs[proc.pid] = (proc, room.url)
|
bot_procs[proc.pid] = (proc, room.url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}")
|
||||||
status_code=500, detail=f"Failed to start subprocess: {e}")
|
|
||||||
|
|
||||||
return RedirectResponse(room.url)
|
return RedirectResponse(room.url)
|
||||||
|
|
||||||
@@ -107,8 +105,7 @@ def get_status(pid: int):
|
|||||||
|
|
||||||
# If the subprocess doesn't exist, return an error
|
# If the subprocess doesn't exist, return an error
|
||||||
if not proc:
|
if not proc:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail=f"Bot with process id: {pid} not found")
|
||||||
status_code=404, detail=f"Bot with process id: {pid} not found")
|
|
||||||
|
|
||||||
# Check the status of the subprocess
|
# Check the status of the subprocess
|
||||||
if proc[0].poll() is None:
|
if proc[0].poll() is None:
|
||||||
@@ -125,14 +122,10 @@ if __name__ == "__main__":
|
|||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
default_host = os.getenv("HOST", "0.0.0.0")
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Daily Storyteller FastAPI server")
|
||||||
description="Daily Storyteller FastAPI server")
|
parser.add_argument("--host", type=str, default=default_host, help="Host address")
|
||||||
parser.add_argument("--host", type=str,
|
parser.add_argument("--port", type=int, default=default_port, help="Port number")
|
||||||
default=default_host, help="Host address")
|
parser.add_argument("--reload", action="store_true", help="Reload code on change")
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
config = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,22 @@ from pipecat.pipeline.runner import PipelineRunner
|
|||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator,
|
LLMAssistantResponseAggregator,
|
||||||
LLMUserResponseAggregator
|
LLMUserResponseAggregator,
|
||||||
)
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.services.deepgram import DeepgramSTTService
|
from pipecat.services.deepgram import DeepgramSTTService
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketTransport, FastAPIWebsocketParams
|
from pipecat.transports.network.fastapi_websocket import (
|
||||||
|
FastAPIWebsocketTransport,
|
||||||
|
FastAPIWebsocketParams,
|
||||||
|
)
|
||||||
from pipecat.vad.silero import SileroVADAnalyzer
|
from pipecat.vad.silero import SileroVADAnalyzer
|
||||||
from pipecat.serializers.twilio import TwilioFrameSerializer
|
from pipecat.serializers.twilio import TwilioFrameSerializer
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -34,15 +38,13 @@ async def run_bot(websocket_client, stream_sid):
|
|||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True,
|
vad_audio_passthrough=True,
|
||||||
serializer=TwilioFrameSerializer(stream_sid)
|
serializer=TwilioFrameSerializer(stream_sid),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv('DEEPGRAM_API_KEY'))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
@@ -59,23 +61,24 @@ async def run_bot(websocket_client, stream_sid):
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Websocket input from client
|
[
|
||||||
stt, # Speech-To-Text
|
transport.input(), # Websocket input from client
|
||||||
tma_in, # User responses
|
stt, # Speech-To-Text
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # Text-To-Speech
|
llm, # LLM
|
||||||
transport.output(), # Websocket output to client
|
tts, # Text-To-Speech
|
||||||
tma_out # LLM responses
|
transport.output(), # Websocket output to client
|
||||||
])
|
tma_out, # LLM responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
@transport.event_handler("on_client_connected")
|
@transport.event_handler("on_client_connected")
|
||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.post('/start_call')
|
@app.post("/start_call")
|
||||||
async def start_call():
|
async def start_call():
|
||||||
print("POST TwiML")
|
print("POST TwiML")
|
||||||
return HTMLResponse(content=open("templates/streams.xml").read(), media_type="application/xml")
|
return HTMLResponse(content=open("templates/streams.xml").read(), media_type="application/xml")
|
||||||
@@ -32,7 +32,7 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
await start_data.__anext__()
|
await start_data.__anext__()
|
||||||
call_data = json.loads(await start_data.__anext__())
|
call_data = json.loads(await start_data.__anext__())
|
||||||
print(call_data, flush=True)
|
print(call_data, flush=True)
|
||||||
stream_sid = call_data['start']['streamSid']
|
stream_sid = call_data["start"]["streamSid"]
|
||||||
print("WebSocket connection accepted")
|
print("WebSocket connection accepted")
|
||||||
await run_bot(websocket, stream_sid)
|
await run_bot(websocket, stream_sid)
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,21 @@ from pipecat.pipeline.runner import PipelineRunner
|
|||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.processors.aggregators.llm_response import (
|
from pipecat.processors.aggregators.llm_response import (
|
||||||
LLMAssistantResponseAggregator,
|
LLMAssistantResponseAggregator,
|
||||||
LLMUserResponseAggregator
|
LLMUserResponseAggregator,
|
||||||
)
|
)
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
from pipecat.services.deepgram import DeepgramSTTService
|
from pipecat.services.deepgram import DeepgramSTTService
|
||||||
from pipecat.services.openai import OpenAILLMService
|
from pipecat.services.openai import OpenAILLMService
|
||||||
from pipecat.transports.network.websocket_server import WebsocketServerParams, WebsocketServerTransport
|
from pipecat.transports.network.websocket_server import (
|
||||||
|
WebsocketServerParams,
|
||||||
|
WebsocketServerTransport,
|
||||||
|
)
|
||||||
from pipecat.vad.silero import SileroVADAnalyzer
|
from pipecat.vad.silero import SileroVADAnalyzer
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
@@ -38,13 +42,11 @@ async def main():
|
|||||||
add_wav_header=True,
|
add_wav_header=True,
|
||||||
vad_enabled=True,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
vad_audio_passthrough=True
|
vad_audio_passthrough=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
|
||||||
model="gpt-4o")
|
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
@@ -63,28 +65,30 @@ async def main():
|
|||||||
tma_in = LLMUserResponseAggregator(messages)
|
tma_in = LLMUserResponseAggregator(messages)
|
||||||
tma_out = LLMAssistantResponseAggregator(messages)
|
tma_out = LLMAssistantResponseAggregator(messages)
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline(
|
||||||
transport.input(), # Websocket input from client
|
[
|
||||||
stt, # Speech-To-Text
|
transport.input(), # Websocket input from client
|
||||||
tma_in, # User responses
|
stt, # Speech-To-Text
|
||||||
llm, # LLM
|
tma_in, # User responses
|
||||||
tts, # Text-To-Speech
|
llm, # LLM
|
||||||
transport.output(), # Websocket output to client
|
tts, # Text-To-Speech
|
||||||
tma_out # LLM responses
|
transport.output(), # Websocket output to client
|
||||||
])
|
tma_out, # LLM responses
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
task = PipelineTask(pipeline)
|
task = PipelineTask(pipeline)
|
||||||
|
|
||||||
@transport.event_handler("on_client_connected")
|
@transport.event_handler("on_client_connected")
|
||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
messages.append(
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
{"role": "system", "content": "Please introduce yourself to the user."})
|
|
||||||
await task.queue_frames([LLMMessagesFrame(messages)])
|
await task.queue_frames([LLMMessagesFrame(messages)])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ moondream = [ "einops~=0.8.0", "timm~=1.0.8", "transformers~=4.44.0" ]
|
|||||||
openai = [ "openai~=1.37.2" ]
|
openai = [ "openai~=1.37.2" ]
|
||||||
openpipe = [ "openpipe~=4.24.0" ]
|
openpipe = [ "openpipe~=4.24.0" ]
|
||||||
playht = [ "pyht~=0.0.28" ]
|
playht = [ "pyht~=0.0.28" ]
|
||||||
silero = [ "silero-vad~=5.1" ]
|
silero = [ "onnxruntime>=1.16.1" ]
|
||||||
together = [ "together~=1.2.7" ]
|
together = [ "together~=1.2.7" ]
|
||||||
websocket = [ "websockets~=12.0", "fastapi~=0.112.1" ]
|
websocket = [ "websockets~=12.0", "fastapi~=0.112.1" ]
|
||||||
whisper = [ "faster-whisper~=1.0.3" ]
|
whisper = [ "faster-whisper~=1.0.3" ]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from abc import ABC, abstractmethod
|
|||||||
|
|
||||||
|
|
||||||
class BaseClock(ABC):
|
class BaseClock(ABC):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_time(self) -> int:
|
def get_time(self) -> int:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from pipecat.clocks.base_clock import BaseClock
|
|||||||
|
|
||||||
|
|
||||||
class SystemClock(BaseClock):
|
class SystemClock(BaseClock):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._time = 0
|
self._time = 0
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class DataFrame(Frame):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AudioRawFrame(DataFrame):
|
class AudioRawFrame(DataFrame):
|
||||||
"""A chunk of audio."""
|
"""A chunk of audio."""
|
||||||
|
|
||||||
audio: bytes
|
audio: bytes
|
||||||
sample_rate: int
|
sample_rate: int
|
||||||
num_channels: int
|
num_channels: int
|
||||||
@@ -58,9 +59,8 @@ class AudioRawFrame(DataFrame):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InputAudioRawFrame(AudioRawFrame):
|
class InputAudioRawFrame(AudioRawFrame):
|
||||||
"""A chunk of audio usually coming from an input transport.
|
"""A chunk of audio usually coming from an input transport."""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -70,14 +70,14 @@ class OutputAudioRawFrame(AudioRawFrame):
|
|||||||
transport's microphone has been enabled.
|
transport's microphone has been enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TTSAudioRawFrame(OutputAudioRawFrame):
|
class TTSAudioRawFrame(OutputAudioRawFrame):
|
||||||
"""A chunk of output audio generated by a TTS service.
|
"""A chunk of output audio generated by a TTS service."""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -87,6 +87,7 @@ class ImageRawFrame(DataFrame):
|
|||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
image: bytes
|
image: bytes
|
||||||
size: Tuple[int, int]
|
size: Tuple[int, int]
|
||||||
format: str | None
|
format: str | None
|
||||||
@@ -112,6 +113,7 @@ class UserImageRawFrame(InputImageRawFrame):
|
|||||||
transport's camera is enabled.
|
transport's camera is enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -125,11 +127,14 @@ class VisionImageRawFrame(InputImageRawFrame):
|
|||||||
shown by the transport if the transport's camera is enabled.
|
shown by the transport if the transport's camera is enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: str | None
|
text: str | None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
return f"{self.name}(pts: {pts}, text: {self.text}, size: {self.size}, format: {self.format})"
|
return (
|
||||||
|
f"{self.name}(pts: {pts}, text: {self.text}, size: {self.size}, format: {self.format})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -138,6 +143,7 @@ class URLImageRawFrame(OutputImageRawFrame):
|
|||||||
transport's camera is enabled.
|
transport's camera is enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: str | None
|
url: str | None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -152,6 +158,7 @@ class SpriteFrame(Frame):
|
|||||||
`camera_out_framerate` constructor parameter.
|
`camera_out_framerate` constructor parameter.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
images: List[ImageRawFrame]
|
images: List[ImageRawFrame]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -165,6 +172,7 @@ class TextFrame(DataFrame):
|
|||||||
be used to send text through pipelines.
|
be used to send text through pipelines.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -178,6 +186,7 @@ class TranscriptionFrame(TextFrame):
|
|||||||
transport's receive queue when a participant speaks.
|
transport's receive queue when a participant speaks.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
language: Language | None = None
|
language: Language | None = None
|
||||||
@@ -190,6 +199,7 @@ class TranscriptionFrame(TextFrame):
|
|||||||
class InterimTranscriptionFrame(TextFrame):
|
class InterimTranscriptionFrame(TextFrame):
|
||||||
"""A text frame with interim transcription-specific data. Will be placed in
|
"""A text frame with interim transcription-specific data. Will be placed in
|
||||||
the transport's receive queue when a participant speaks."""
|
the transport's receive queue when a participant speaks."""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
language: Language | None = None
|
language: Language | None = None
|
||||||
@@ -207,6 +217,7 @@ class LLMMessagesFrame(DataFrame):
|
|||||||
processors.
|
processors.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages: List[dict]
|
messages: List[dict]
|
||||||
|
|
||||||
|
|
||||||
@@ -216,6 +227,7 @@ class LLMMessagesAppendFrame(DataFrame):
|
|||||||
current context.
|
current context.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages: List[dict]
|
messages: List[dict]
|
||||||
|
|
||||||
|
|
||||||
@@ -226,6 +238,7 @@ class LLMMessagesUpdateFrame(DataFrame):
|
|||||||
LLMMessagesFrame.
|
LLMMessagesFrame.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages: List[dict]
|
messages: List[dict]
|
||||||
|
|
||||||
|
|
||||||
@@ -235,13 +248,14 @@ class LLMSetToolsFrame(DataFrame):
|
|||||||
The specific format depends on the LLM being used, but it should typically
|
The specific format depends on the LLM being used, but it should typically
|
||||||
contain JSON Schema objects.
|
contain JSON Schema objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tools: List[dict]
|
tools: List[dict]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMEnablePromptCachingFrame(DataFrame):
|
class LLMEnablePromptCachingFrame(DataFrame):
|
||||||
"""A frame to enable/disable prompt caching in certain LLMs.
|
"""A frame to enable/disable prompt caching in certain LLMs."""
|
||||||
"""
|
|
||||||
enable: bool
|
enable: bool
|
||||||
|
|
||||||
|
|
||||||
@@ -251,6 +265,7 @@ class TTSSpeakFrame(DataFrame):
|
|||||||
pipeline (if any).
|
pipeline (if any).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
@@ -262,6 +277,7 @@ class TransportMessageFrame(DataFrame):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(message: {self.message})"
|
return f"{self.name}(message: {self.message})"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# App frames. Application user-defined frames.
|
# App frames. Application user-defined frames.
|
||||||
#
|
#
|
||||||
@@ -271,6 +287,7 @@ class TransportMessageFrame(DataFrame):
|
|||||||
class AppFrame(Frame):
|
class AppFrame(Frame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# System frames
|
# System frames
|
||||||
#
|
#
|
||||||
@@ -284,6 +301,7 @@ class SystemFrame(Frame):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class StartFrame(SystemFrame):
|
class StartFrame(SystemFrame):
|
||||||
"""This is the first frame that should be pushed down a pipeline."""
|
"""This is the first frame that should be pushed down a pipeline."""
|
||||||
|
|
||||||
clock: BaseClock
|
clock: BaseClock
|
||||||
allow_interruptions: bool = False
|
allow_interruptions: bool = False
|
||||||
enable_metrics: bool = False
|
enable_metrics: bool = False
|
||||||
@@ -294,6 +312,7 @@ class StartFrame(SystemFrame):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class CancelFrame(SystemFrame):
|
class CancelFrame(SystemFrame):
|
||||||
"""Indicates that a pipeline needs to stop right away."""
|
"""Indicates that a pipeline needs to stop right away."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -304,6 +323,7 @@ class ErrorFrame(SystemFrame):
|
|||||||
bot should exit.
|
bot should exit.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
error: str
|
error: str
|
||||||
fatal: bool = False
|
fatal: bool = False
|
||||||
|
|
||||||
@@ -317,6 +337,7 @@ class FatalErrorFrame(ErrorFrame):
|
|||||||
that the bot should exit.
|
that the bot should exit.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fatal: bool = field(default=True, init=False)
|
fatal: bool = field(default=True, init=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -327,6 +348,7 @@ class StopTaskFrame(SystemFrame):
|
|||||||
the pipeline task.
|
the pipeline task.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -338,6 +360,7 @@ class StartInterruptionFrame(SystemFrame):
|
|||||||
guaranteed).
|
guaranteed).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -349,6 +372,7 @@ class StopInterruptionFrame(SystemFrame):
|
|||||||
guaranteed).
|
guaranteed).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -359,13 +383,14 @@ class BotInterruptionFrame(SystemFrame):
|
|||||||
UserStartedSpeakingFrame and UserStoppedSpeakingFrame won't be generated.
|
UserStartedSpeakingFrame and UserStoppedSpeakingFrame won't be generated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MetricsFrame(SystemFrame):
|
class MetricsFrame(SystemFrame):
|
||||||
"""Emitted by processor that can compute metrics like latencies.
|
"""Emitted by processor that can compute metrics like latencies."""
|
||||||
"""
|
|
||||||
data: List[MetricsData]
|
data: List[MetricsData]
|
||||||
|
|
||||||
|
|
||||||
@@ -388,6 +413,7 @@ class EndFrame(ControlFrame):
|
|||||||
was sent (unline system frames).
|
was sent (unline system frames).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -395,12 +421,14 @@ class EndFrame(ControlFrame):
|
|||||||
class LLMFullResponseStartFrame(ControlFrame):
|
class LLMFullResponseStartFrame(ControlFrame):
|
||||||
"""Used to indicate the beginning of an LLM response. Following by one or
|
"""Used to indicate the beginning of an LLM response. Following by one or
|
||||||
more TextFrame and a final LLMFullResponseEndFrame."""
|
more TextFrame and a final LLMFullResponseEndFrame."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMFullResponseEndFrame(ControlFrame):
|
class LLMFullResponseEndFrame(ControlFrame):
|
||||||
"""Indicates the end of an LLM response."""
|
"""Indicates the end of an LLM response."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -412,28 +440,28 @@ class UserStartedSpeakingFrame(ControlFrame):
|
|||||||
with a TranscriptionFrame)
|
with a TranscriptionFrame)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UserStoppedSpeakingFrame(ControlFrame):
|
class UserStoppedSpeakingFrame(ControlFrame):
|
||||||
"""Emitted by the VAD to indicate that a user stopped speaking."""
|
"""Emitted by the VAD to indicate that a user stopped speaking."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BotStartedSpeakingFrame(ControlFrame):
|
class BotStartedSpeakingFrame(ControlFrame):
|
||||||
"""Emitted upstream by transport outputs to indicate the bot started speaking.
|
"""Emitted upstream by transport outputs to indicate the bot started speaking."""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BotStoppedSpeakingFrame(ControlFrame):
|
class BotStoppedSpeakingFrame(ControlFrame):
|
||||||
"""Emitted upstream by transport outputs to indicate the bot stopped speaking.
|
"""Emitted upstream by transport outputs to indicate the bot stopped speaking."""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -445,6 +473,7 @@ class BotSpeakingFrame(ControlFrame):
|
|||||||
since the user might be listening.
|
since the user might be listening.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -457,18 +486,21 @@ class TTSStartedFrame(ControlFrame):
|
|||||||
needing to control this in the TTS service.
|
needing to control this in the TTS service.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TTSStoppedFrame(ControlFrame):
|
class TTSStoppedFrame(ControlFrame):
|
||||||
"""Indicates the end of a TTS response."""
|
"""Indicates the end of a TTS response."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UserImageRequestFrame(ControlFrame):
|
class UserImageRequestFrame(ControlFrame):
|
||||||
"""A frame user to request an image from the given user."""
|
"""A frame user to request an image from the given user."""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
context: Optional[Any] = None
|
context: Optional[Any] = None
|
||||||
|
|
||||||
@@ -478,29 +510,29 @@ class UserImageRequestFrame(ControlFrame):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMModelUpdateFrame(ControlFrame):
|
class LLMModelUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM model.
|
"""A control frame containing a request to update to a new LLM model."""
|
||||||
"""
|
|
||||||
model: str
|
model: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMTemperatureUpdateFrame(ControlFrame):
|
class LLMTemperatureUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM temperature.
|
"""A control frame containing a request to update to a new LLM temperature."""
|
||||||
"""
|
|
||||||
temperature: float
|
temperature: float
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMTopKUpdateFrame(ControlFrame):
|
class LLMTopKUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM top_k.
|
"""A control frame containing a request to update to a new LLM top_k."""
|
||||||
"""
|
|
||||||
top_k: int
|
top_k: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMTopPUpdateFrame(ControlFrame):
|
class LLMTopPUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM top_p.
|
"""A control frame containing a request to update to a new LLM top_p."""
|
||||||
"""
|
|
||||||
top_p: float
|
top_p: float
|
||||||
|
|
||||||
|
|
||||||
@@ -510,6 +542,7 @@ class LLMFrequencyPenaltyUpdateFrame(ControlFrame):
|
|||||||
penalty.
|
penalty.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
frequency_penalty: float
|
frequency_penalty: float
|
||||||
|
|
||||||
|
|
||||||
@@ -519,41 +552,42 @@ class LLMPresencePenaltyUpdateFrame(ControlFrame):
|
|||||||
penalty.
|
penalty.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
presence_penalty: float
|
presence_penalty: float
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMMaxTokensUpdateFrame(ControlFrame):
|
class LLMMaxTokensUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM max tokens.
|
"""A control frame containing a request to update to a new LLM max tokens."""
|
||||||
"""
|
|
||||||
max_tokens: int
|
max_tokens: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMSeedUpdateFrame(ControlFrame):
|
class LLMSeedUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM seed.
|
"""A control frame containing a request to update to a new LLM seed."""
|
||||||
"""
|
|
||||||
seed: int
|
seed: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMExtraUpdateFrame(ControlFrame):
|
class LLMExtraUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new LLM extra params.
|
"""A control frame containing a request to update to a new LLM extra params."""
|
||||||
"""
|
|
||||||
extra: dict
|
extra: dict
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TTSModelUpdateFrame(ControlFrame):
|
class TTSModelUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update the TTS model.
|
"""A control frame containing a request to update the TTS model."""
|
||||||
"""
|
|
||||||
model: str
|
model: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TTSVoiceUpdateFrame(ControlFrame):
|
class TTSVoiceUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to a new TTS voice.
|
"""A control frame containing a request to update to a new TTS voice."""
|
||||||
"""
|
|
||||||
voice: str
|
voice: str
|
||||||
|
|
||||||
|
|
||||||
@@ -563,6 +597,7 @@ class TTSLanguageUpdateFrame(ControlFrame):
|
|||||||
optional voice.
|
optional voice.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
language: Language
|
language: Language
|
||||||
|
|
||||||
|
|
||||||
@@ -572,20 +607,21 @@ class STTModelUpdateFrame(ControlFrame):
|
|||||||
language.
|
language.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model: str
|
model: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class STTLanguageUpdateFrame(ControlFrame):
|
class STTLanguageUpdateFrame(ControlFrame):
|
||||||
"""A control frame containing a request to update to STT language.
|
"""A control frame containing a request to update to STT language."""
|
||||||
"""
|
|
||||||
language: Language
|
language: Language
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FunctionCallInProgressFrame(SystemFrame):
|
class FunctionCallInProgressFrame(SystemFrame):
|
||||||
"""A frame signaling that a function call is in progress.
|
"""A frame signaling that a function call is in progress."""
|
||||||
"""
|
|
||||||
function_name: str
|
function_name: str
|
||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
arguments: str
|
arguments: str
|
||||||
@@ -593,8 +629,8 @@ class FunctionCallInProgressFrame(SystemFrame):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FunctionCallResultFrame(DataFrame):
|
class FunctionCallResultFrame(DataFrame):
|
||||||
"""A frame containing the result of an LLM function (tool) call.
|
"""A frame containing the result of an LLM function (tool) call."""
|
||||||
"""
|
|
||||||
function_name: str
|
function_name: str
|
||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
arguments: str
|
arguments: str
|
||||||
@@ -606,4 +642,5 @@ class VADParamsUpdateFrame(ControlFrame):
|
|||||||
"""A control frame containing a request to update VAD params. Intended
|
"""A control frame containing a request to update VAD params. Intended
|
||||||
to be pushed upstream from RTVI processor.
|
to be pushed upstream from RTVI processor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
params: VADParams
|
params: VADParams
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from pipecat.processors.frame_processor import FrameProcessor
|
|||||||
|
|
||||||
|
|
||||||
class BasePipeline(FrameProcessor):
|
class BasePipeline(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from loguru import logger
|
|||||||
|
|
||||||
|
|
||||||
class Source(FrameProcessor):
|
class Source(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, upstream_queue: asyncio.Queue):
|
def __init__(self, upstream_queue: asyncio.Queue):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._up_queue = upstream_queue
|
self._up_queue = upstream_queue
|
||||||
@@ -34,7 +33,6 @@ class Source(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class Sink(FrameProcessor):
|
class Sink(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, downstream_queue: asyncio.Queue):
|
def __init__(self, downstream_queue: asyncio.Queue):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._down_queue = downstream_queue
|
self._down_queue = downstream_queue
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
|
|
||||||
|
|
||||||
class PipelineSource(FrameProcessor):
|
class PipelineSource(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, upstream_push_frame: Callable[[Frame, FrameDirection], Coroutine]):
|
def __init__(self, upstream_push_frame: Callable[[Frame, FrameDirection], Coroutine]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._upstream_push_frame = upstream_push_frame
|
self._upstream_push_frame = upstream_push_frame
|
||||||
@@ -28,7 +27,6 @@ class PipelineSource(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class PipelineSink(FrameProcessor):
|
class PipelineSink(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, downstream_push_frame: Callable[[Frame, FrameDirection], Coroutine]):
|
def __init__(self, downstream_push_frame: Callable[[Frame, FrameDirection], Coroutine]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._downstream_push_frame = downstream_push_frame
|
self._downstream_push_frame = downstream_push_frame
|
||||||
@@ -44,7 +42,6 @@ class PipelineSink(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class Pipeline(BasePipeline):
|
class Pipeline(BasePipeline):
|
||||||
|
|
||||||
def __init__(self, processors: List[FrameProcessor]):
|
def __init__(self, processors: List[FrameProcessor]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from loguru import logger
|
|||||||
|
|
||||||
|
|
||||||
class PipelineRunner:
|
class PipelineRunner:
|
||||||
|
|
||||||
def __init__(self, *, name: str | None = None, handle_sigint: bool = True):
|
def __init__(self, *, name: str | None = None, handle_sigint: bool = True):
|
||||||
self.id: int = obj_id()
|
self.id: int = obj_id()
|
||||||
self.name: str = name or f"{self.__class__.__name__}#{obj_count(self)}"
|
self.name: str = name or f"{self.__class__.__name__}#{obj_count(self)}"
|
||||||
@@ -42,12 +41,10 @@ class PipelineRunner:
|
|||||||
def _setup_sigint(self):
|
def _setup_sigint(self):
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(
|
||||||
signal.SIGINT,
|
signal.SIGINT, lambda *args: asyncio.create_task(self._sig_handler())
|
||||||
lambda *args: asyncio.create_task(self._sig_handler())
|
|
||||||
)
|
)
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(
|
||||||
signal.SIGTERM,
|
signal.SIGTERM, lambda *args: asyncio.create_task(self._sig_handler())
|
||||||
lambda *args: asyncio.create_task(self._sig_handler())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _sig_handler(self):
|
async def _sig_handler(self):
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from loguru import logger
|
|||||||
|
|
||||||
|
|
||||||
class Source(FrameProcessor):
|
class Source(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, upstream_queue: asyncio.Queue):
|
def __init__(self, upstream_queue: asyncio.Queue):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._up_queue = upstream_queue
|
self._up_queue = upstream_queue
|
||||||
@@ -34,7 +33,6 @@ class Source(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class Sink(FrameProcessor):
|
class Sink(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, downstream_queue: asyncio.Queue):
|
def __init__(self, downstream_queue: asyncio.Queue):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._down_queue = downstream_queue
|
self._down_queue = downstream_queue
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
MetricsFrame,
|
MetricsFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StopTaskFrame)
|
StopTaskFrame,
|
||||||
|
)
|
||||||
from pipecat.metrics.metrics import TTFBMetricsData, ProcessingMetricsData
|
from pipecat.metrics.metrics import TTFBMetricsData, ProcessingMetricsData
|
||||||
from pipecat.pipeline.base_pipeline import BasePipeline
|
from pipecat.pipeline.base_pipeline import BasePipeline
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -37,7 +38,6 @@ class PipelineParams(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Source(FrameProcessor):
|
class Source(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, up_queue: asyncio.Queue):
|
def __init__(self, up_queue: asyncio.Queue):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._up_queue = up_queue
|
self._up_queue = up_queue
|
||||||
@@ -62,12 +62,12 @@ class Source(FrameProcessor):
|
|||||||
|
|
||||||
|
|
||||||
class PipelineTask:
|
class PipelineTask:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pipeline: BasePipeline,
|
pipeline: BasePipeline,
|
||||||
params: PipelineParams = PipelineParams(),
|
params: PipelineParams = PipelineParams(),
|
||||||
clock: BaseClock = SystemClock()):
|
clock: BaseClock = SystemClock(),
|
||||||
|
):
|
||||||
self.id: int = obj_id()
|
self.id: int = obj_id()
|
||||||
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
|
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
|
||||||
|
|
||||||
@@ -133,12 +133,14 @@ class PipelineTask:
|
|||||||
enable_metrics=self._params.enable_metrics,
|
enable_metrics=self._params.enable_metrics,
|
||||||
enable_usage_metrics=self._params.enable_metrics,
|
enable_usage_metrics=self._params.enable_metrics,
|
||||||
report_only_initial_ttfb=self._params.report_only_initial_ttfb,
|
report_only_initial_ttfb=self._params.report_only_initial_ttfb,
|
||||||
clock=self._clock
|
clock=self._clock,
|
||||||
)
|
)
|
||||||
await self._source.process_frame(start_frame, FrameDirection.DOWNSTREAM)
|
await self._source.process_frame(start_frame, FrameDirection.DOWNSTREAM)
|
||||||
|
|
||||||
if self._params.enable_metrics and self._params.send_initial_empty_metrics:
|
if self._params.enable_metrics and self._params.send_initial_empty_metrics:
|
||||||
await self._source.process_frame(self._initial_metrics_frame(), FrameDirection.DOWNSTREAM)
|
await self._source.process_frame(
|
||||||
|
self._initial_metrics_frame(), FrameDirection.DOWNSTREAM
|
||||||
|
)
|
||||||
|
|
||||||
running = True
|
running = True
|
||||||
should_cleanup = True
|
should_cleanup = True
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ class SequentialMergePipeline(Pipeline):
|
|||||||
for idx, pipeline in enumerate(self.pipelines):
|
for idx, pipeline in enumerate(self.pipelines):
|
||||||
while True:
|
while True:
|
||||||
frame = await pipeline.sink.get()
|
frame = await pipeline.sink.get()
|
||||||
if isinstance(
|
if isinstance(frame, EndFrame) or isinstance(frame, EndPipeFrame):
|
||||||
frame, EndFrame) or isinstance(
|
|
||||||
frame, EndPipeFrame):
|
|
||||||
break
|
break
|
||||||
await self.sink.put(frame)
|
await self.sink.put(frame)
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,13 @@ class GatedAggregator(FrameProcessor):
|
|||||||
Goodbye.
|
Goodbye.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, gate_open_fn, gate_close_fn, start_open,
|
def __init__(
|
||||||
direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
self,
|
||||||
|
gate_open_fn,
|
||||||
|
gate_close_fn,
|
||||||
|
start_open,
|
||||||
|
direction: FrameDirection = FrameDirection.DOWNSTREAM,
|
||||||
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._gate_open_fn = gate_open_fn
|
self._gate_open_fn = gate_open_fn
|
||||||
self._gate_close_fn = gate_close_fn
|
self._gate_close_fn = gate_close_fn
|
||||||
@@ -75,7 +80,7 @@ class GatedAggregator(FrameProcessor):
|
|||||||
|
|
||||||
if self._gate_open:
|
if self._gate_open:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
for (f, d) in self._accumulator:
|
for f, d in self._accumulator:
|
||||||
await self.push_frame(f, d)
|
await self.push_frame(f, d)
|
||||||
self._accumulator = []
|
self._accumulator = []
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
|
|
||||||
from typing import List, Type
|
from typing import List, Type
|
||||||
|
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame, OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import (
|
||||||
|
OpenAILLMContextFrame,
|
||||||
|
OpenAILLMContext,
|
||||||
|
)
|
||||||
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
@@ -22,11 +25,11 @@ from pipecat.frames.frames import (
|
|||||||
TranscriptionFrame,
|
TranscriptionFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame)
|
UserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LLMResponseAggregator(FrameProcessor):
|
class LLMResponseAggregator(FrameProcessor):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -36,7 +39,7 @@ class LLMResponseAggregator(FrameProcessor):
|
|||||||
end_frame,
|
end_frame,
|
||||||
accumulator_frame: Type[TextFrame],
|
accumulator_frame: Type[TextFrame],
|
||||||
interim_accumulator_frame: Type[TextFrame] | None = None,
|
interim_accumulator_frame: Type[TextFrame] | None = None,
|
||||||
handle_interruptions: bool = False
|
handle_interruptions: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -175,7 +178,7 @@ class LLMAssistantResponseAggregator(LLMResponseAggregator):
|
|||||||
start_frame=LLMFullResponseStartFrame,
|
start_frame=LLMFullResponseStartFrame,
|
||||||
end_frame=LLMFullResponseEndFrame,
|
end_frame=LLMFullResponseEndFrame,
|
||||||
accumulator_frame=TextFrame,
|
accumulator_frame=TextFrame,
|
||||||
handle_interruptions=True
|
handle_interruptions=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -187,7 +190,7 @@ class LLMUserResponseAggregator(LLMResponseAggregator):
|
|||||||
start_frame=UserStartedSpeakingFrame,
|
start_frame=UserStartedSpeakingFrame,
|
||||||
end_frame=UserStoppedSpeakingFrame,
|
end_frame=UserStoppedSpeakingFrame,
|
||||||
accumulator_frame=TranscriptionFrame,
|
accumulator_frame=TranscriptionFrame,
|
||||||
interim_accumulator_frame=InterimTranscriptionFrame
|
interim_accumulator_frame=InterimTranscriptionFrame,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -295,7 +298,7 @@ class LLMAssistantContextAggregator(LLMContextAggregator):
|
|||||||
start_frame=LLMFullResponseStartFrame,
|
start_frame=LLMFullResponseStartFrame,
|
||||||
end_frame=LLMFullResponseEndFrame,
|
end_frame=LLMFullResponseEndFrame,
|
||||||
accumulator_frame=TextFrame,
|
accumulator_frame=TextFrame,
|
||||||
handle_interruptions=True
|
handle_interruptions=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -308,5 +311,5 @@ class LLMUserContextAggregator(LLMContextAggregator):
|
|||||||
start_frame=UserStartedSpeakingFrame,
|
start_frame=UserStartedSpeakingFrame,
|
||||||
end_frame=UserStoppedSpeakingFrame,
|
end_frame=UserStoppedSpeakingFrame,
|
||||||
accumulator_frame=TranscriptionFrame,
|
accumulator_frame=TranscriptionFrame,
|
||||||
interim_accumulator_frame=InterimTranscriptionFrame
|
interim_accumulator_frame=InterimTranscriptionFrame,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
VisionImageRawFrame,
|
VisionImageRawFrame,
|
||||||
FunctionCallInProgressFrame,
|
FunctionCallInProgressFrame,
|
||||||
FunctionCallResultFrame)
|
FunctionCallResultFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.frame_processor import FrameProcessor
|
from pipecat.processors.frame_processor import FrameProcessor
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -28,12 +29,13 @@ try:
|
|||||||
from openai.types.chat import (
|
from openai.types.chat import (
|
||||||
ChatCompletionToolParam,
|
ChatCompletionToolParam,
|
||||||
ChatCompletionToolChoiceOptionParam,
|
ChatCompletionToolChoiceOptionParam,
|
||||||
ChatCompletionMessageParam
|
ChatCompletionMessageParam,
|
||||||
)
|
)
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error(
|
logger.error(
|
||||||
"In order to use OpenAI, you need to `pip install pipecat-ai[openai]`. Also, set `OPENAI_API_KEY` environment variable.")
|
"In order to use OpenAI, you need to `pip install pipecat-ai[openai]`. Also, set `OPENAI_API_KEY` environment variable."
|
||||||
|
)
|
||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
# JSON custom encoder to handle bytes arrays so that we can log contexts
|
# JSON custom encoder to handle bytes arrays so that we can log contexts
|
||||||
@@ -44,20 +46,18 @@ class CustomEncoder(json.JSONEncoder):
|
|||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
if isinstance(obj, io.BytesIO):
|
if isinstance(obj, io.BytesIO):
|
||||||
# Convert the first 8 bytes to an ASCII hex string
|
# Convert the first 8 bytes to an ASCII hex string
|
||||||
return (f"{obj.getbuffer()[0:8].hex()}...")
|
return f"{obj.getbuffer()[0:8].hex()}..."
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
|
|
||||||
class OpenAILLMContext:
|
class OpenAILLMContext:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
messages: List[ChatCompletionMessageParam] | None = None,
|
messages: List[ChatCompletionMessageParam] | None = None,
|
||||||
tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
|
tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
|
||||||
tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN
|
tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN,
|
||||||
):
|
):
|
||||||
self._messages: List[ChatCompletionMessageParam] = messages if messages else [
|
self._messages: List[ChatCompletionMessageParam] = messages if messages else []
|
||||||
]
|
|
||||||
self._tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = tool_choice
|
self._tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = tool_choice
|
||||||
self._tools: List[ChatCompletionToolParam] | NotGiven = tools
|
self._tools: List[ChatCompletionToolParam] | NotGiven = tools
|
||||||
|
|
||||||
@@ -81,19 +81,10 @@ class OpenAILLMContext:
|
|||||||
"""
|
"""
|
||||||
context = OpenAILLMContext()
|
context = OpenAILLMContext()
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
Image.frombytes(
|
Image.frombytes(frame.format, frame.size, frame.image).save(buffer, format="JPEG")
|
||||||
frame.format,
|
context.add_message(
|
||||||
frame.size,
|
{"content": frame.text, "role": "user", "data": buffer, "mime_type": "image/jpeg"}
|
||||||
frame.image
|
)
|
||||||
).save(
|
|
||||||
buffer,
|
|
||||||
format="JPEG")
|
|
||||||
context.add_message({
|
|
||||||
"content": frame.text,
|
|
||||||
"role": "user",
|
|
||||||
"data": buffer,
|
|
||||||
"mime_type": "image/jpeg"
|
|
||||||
})
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -123,9 +114,7 @@ class OpenAILLMContext:
|
|||||||
def get_messages_json(self) -> str:
|
def get_messages_json(self) -> str:
|
||||||
return json.dumps(self._messages, cls=CustomEncoder)
|
return json.dumps(self._messages, cls=CustomEncoder)
|
||||||
|
|
||||||
def set_tool_choice(
|
def set_tool_choice(self, tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven):
|
||||||
self, tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven
|
|
||||||
):
|
|
||||||
self._tool_choice = tool_choice
|
self._tool_choice = tool_choice
|
||||||
|
|
||||||
def set_tools(self, tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN):
|
def set_tools(self, tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN):
|
||||||
@@ -133,37 +122,40 @@ class OpenAILLMContext:
|
|||||||
tools = NOT_GIVEN
|
tools = NOT_GIVEN
|
||||||
self._tools = tools
|
self._tools = tools
|
||||||
|
|
||||||
async def call_function(self,
|
async def call_function(
|
||||||
f: Callable[[str,
|
self,
|
||||||
str,
|
f: Callable[
|
||||||
Any,
|
[str, str, Any, FrameProcessor, "OpenAILLMContext", Callable[[Any], Awaitable[None]]],
|
||||||
FrameProcessor,
|
Awaitable[None],
|
||||||
'OpenAILLMContext',
|
],
|
||||||
Callable[[Any],
|
*,
|
||||||
Awaitable[None]]],
|
function_name: str,
|
||||||
Awaitable[None]],
|
tool_call_id: str,
|
||||||
*,
|
arguments: str,
|
||||||
function_name: str,
|
llm: FrameProcessor,
|
||||||
tool_call_id: str,
|
) -> None:
|
||||||
arguments: str,
|
|
||||||
llm: FrameProcessor) -> None:
|
|
||||||
|
|
||||||
# Push a SystemFrame downstream. This frame will let our assistant context aggregator
|
# Push a SystemFrame downstream. This frame will let our assistant context aggregator
|
||||||
# know that we are in the middle of a function call. Some contexts/aggregators may
|
# know that we are in the middle of a function call. Some contexts/aggregators may
|
||||||
# not need this. But some definitely do (Anthropic, for example).
|
# not need this. But some definitely do (Anthropic, for example).
|
||||||
await llm.push_frame(FunctionCallInProgressFrame(
|
await llm.push_frame(
|
||||||
function_name=function_name,
|
FunctionCallInProgressFrame(
|
||||||
tool_call_id=tool_call_id,
|
|
||||||
arguments=arguments,
|
|
||||||
))
|
|
||||||
|
|
||||||
# Define a callback function that pushes a FunctionCallResultFrame downstream.
|
|
||||||
async def function_call_result_callback(result):
|
|
||||||
await llm.push_frame(FunctionCallResultFrame(
|
|
||||||
function_name=function_name,
|
function_name=function_name,
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
result=result))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define a callback function that pushes a FunctionCallResultFrame downstream.
|
||||||
|
async def function_call_result_callback(result):
|
||||||
|
await llm.push_frame(
|
||||||
|
FunctionCallResultFrame(
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
arguments=arguments,
|
||||||
|
result=result,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
await f(function_name, tool_call_id, arguments, llm, self, function_call_result_callback)
|
await f(function_name, tool_call_id, arguments, llm, self, function_call_result_callback)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,4 +166,5 @@ class OpenAILLMContextFrame(Frame):
|
|||||||
OpenAIContextAggregator frame processor.
|
OpenAIContextAggregator frame processor.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context: OpenAILLMContext
|
context: OpenAILLMContext
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from pipecat.frames.frames import (
|
|||||||
TextFrame,
|
TextFrame,
|
||||||
TranscriptionFrame,
|
TranscriptionFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame)
|
UserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ResponseAggregator(FrameProcessor):
|
class ResponseAggregator(FrameProcessor):
|
||||||
@@ -49,7 +50,7 @@ class ResponseAggregator(FrameProcessor):
|
|||||||
start_frame,
|
start_frame,
|
||||||
end_frame,
|
end_frame,
|
||||||
accumulator_frame: TextFrame,
|
accumulator_frame: TextFrame,
|
||||||
interim_accumulator_frame: TextFrame | None = None
|
interim_accumulator_frame: TextFrame | None = None,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,7 @@
|
|||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import Frame, InputImageRawFrame, TextFrame, VisionImageRawFrame
|
||||||
Frame,
|
|
||||||
InputImageRawFrame,
|
|
||||||
TextFrame,
|
|
||||||
VisionImageRawFrame
|
|
||||||
)
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +41,8 @@ class VisionImageFrameAggregator(FrameProcessor):
|
|||||||
text=self._describe_text,
|
text=self._describe_text,
|
||||||
image=frame.image,
|
image=frame.image,
|
||||||
size=frame.size,
|
size=frame.size,
|
||||||
format=frame.format)
|
format=frame.format,
|
||||||
|
)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
self._describe_text = None
|
self._describe_text = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
|
|
||||||
|
|
||||||
class FrameFilter(FrameProcessor):
|
class FrameFilter(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, types: List[type]):
|
def __init__(self, types: List[type]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._types = types
|
self._types = types
|
||||||
@@ -25,9 +24,11 @@ class FrameFilter(FrameProcessor):
|
|||||||
if isinstance(frame, t):
|
if isinstance(frame, t):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return (isinstance(frame, AppFrame)
|
return (
|
||||||
or isinstance(frame, ControlFrame)
|
isinstance(frame, AppFrame)
|
||||||
or isinstance(frame, SystemFrame))
|
or isinstance(frame, ControlFrame)
|
||||||
|
or isinstance(frame, SystemFrame)
|
||||||
|
)
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
|
|
||||||
|
|
||||||
class FunctionFilter(FrameProcessor):
|
class FunctionFilter(FrameProcessor):
|
||||||
|
|
||||||
def __init__(self, filter: Callable[[Frame], Awaitable[bool]]):
|
def __init__(self, filter: Callable[[Frame], Awaitable[bool]]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._filter = filter
|
self._filter = filter
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class WakeCheckFilter(FrameProcessor):
|
|||||||
after a wake phrase has been detected. It also has a keepalive timeout to allow for a brief
|
after a wake phrase has been detected. It also has a keepalive timeout to allow for a brief
|
||||||
period of continued conversation after a wake phrase has been detected.
|
period of continued conversation after a wake phrase has been detected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class WakeState(Enum):
|
class WakeState(Enum):
|
||||||
IDLE = 1
|
IDLE = 1
|
||||||
AWAKE = 2
|
AWAKE = 2
|
||||||
@@ -38,8 +39,9 @@ class WakeCheckFilter(FrameProcessor):
|
|||||||
self._keepalive_timeout = keepalive_timeout
|
self._keepalive_timeout = keepalive_timeout
|
||||||
self._wake_patterns = []
|
self._wake_patterns = []
|
||||||
for name in wake_phrases:
|
for name in wake_phrases:
|
||||||
pattern = re.compile(r'\b' + r'\s*'.join(re.escape(word)
|
pattern = re.compile(
|
||||||
for word in name.split()) + r'\b', re.IGNORECASE)
|
r"\b" + r"\s*".join(re.escape(word) for word in name.split()) + r"\b", re.IGNORECASE
|
||||||
|
)
|
||||||
self._wake_patterns.append(pattern)
|
self._wake_patterns.append(pattern)
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -57,7 +59,8 @@ class WakeCheckFilter(FrameProcessor):
|
|||||||
if p.state == WakeCheckFilter.WakeState.AWAKE:
|
if p.state == WakeCheckFilter.WakeState.AWAKE:
|
||||||
if time.time() - p.wake_timer < self._keepalive_timeout:
|
if time.time() - p.wake_timer < self._keepalive_timeout:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Wake phrase keepalive timeout has not expired. Pushing {frame}")
|
f"Wake phrase keepalive timeout has not expired. Pushing {frame}"
|
||||||
|
)
|
||||||
p.wake_timer = time.time()
|
p.wake_timer = time.time()
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
return
|
return
|
||||||
@@ -73,7 +76,7 @@ class WakeCheckFilter(FrameProcessor):
|
|||||||
# and modify the frame in place.
|
# and modify the frame in place.
|
||||||
p.state = WakeCheckFilter.WakeState.AWAKE
|
p.state = WakeCheckFilter.WakeState.AWAKE
|
||||||
p.wake_timer = time.time()
|
p.wake_timer = time.time()
|
||||||
frame.text = p.accumulator[match.start():]
|
frame.text = p.accumulator[match.start() :]
|
||||||
p.accumulator = ""
|
p.accumulator = ""
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -14,18 +14,13 @@ from pipecat.frames.frames import (
|
|||||||
EndFrame,
|
EndFrame,
|
||||||
ErrorFrame,
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
MetricsFrame,
|
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
StopInterruptionFrame,
|
StopInterruptionFrame,
|
||||||
SystemFrame)
|
SystemFrame,
|
||||||
from pipecat.metrics.metrics import (
|
)
|
||||||
LLMTokenUsage,
|
from pipecat.metrics.metrics import LLMTokenUsage, MetricsData
|
||||||
LLMUsageMetricsData,
|
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
|
||||||
MetricsData,
|
|
||||||
ProcessingMetricsData,
|
|
||||||
TTFBMetricsData,
|
|
||||||
TTSUsageMetricsData)
|
|
||||||
from pipecat.utils.utils import obj_count, obj_id
|
from pipecat.utils.utils import obj_count, obj_id
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -36,81 +31,16 @@ class FrameDirection(Enum):
|
|||||||
UPSTREAM = 2
|
UPSTREAM = 2
|
||||||
|
|
||||||
|
|
||||||
class FrameProcessorMetrics:
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self._core_metrics_data = MetricsData(processor=name)
|
|
||||||
self._start_ttfb_time = 0
|
|
||||||
self._start_processing_time = 0
|
|
||||||
self._should_report_ttfb = True
|
|
||||||
|
|
||||||
def _processor_name(self):
|
|
||||||
return self._core_metrics_data.processor
|
|
||||||
|
|
||||||
def _model_name(self):
|
|
||||||
return self._core_metrics_data.model
|
|
||||||
|
|
||||||
def set_core_metrics_data(self, data: MetricsData):
|
|
||||||
self._core_metrics_data = data
|
|
||||||
|
|
||||||
async def start_ttfb_metrics(self, report_only_initial_ttfb):
|
|
||||||
if self._should_report_ttfb:
|
|
||||||
self._start_ttfb_time = time.time()
|
|
||||||
self._should_report_ttfb = not report_only_initial_ttfb
|
|
||||||
|
|
||||||
async def stop_ttfb_metrics(self):
|
|
||||||
if self._start_ttfb_time == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
value = time.time() - self._start_ttfb_time
|
|
||||||
logger.debug(f"{self._processor_name()} TTFB: {value}")
|
|
||||||
ttfb = TTFBMetricsData(
|
|
||||||
processor=self._processor_name(),
|
|
||||||
value=value,
|
|
||||||
model=self._model_name())
|
|
||||||
self._start_ttfb_time = 0
|
|
||||||
return MetricsFrame(data=[ttfb])
|
|
||||||
|
|
||||||
async def start_processing_metrics(self):
|
|
||||||
self._start_processing_time = time.time()
|
|
||||||
|
|
||||||
async def stop_processing_metrics(self):
|
|
||||||
if self._start_processing_time == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
value = time.time() - self._start_processing_time
|
|
||||||
logger.debug(f"{self._processor_name()} processing time: {value}")
|
|
||||||
processing = ProcessingMetricsData(
|
|
||||||
processor=self._processor_name(), value=value, model=self._model_name())
|
|
||||||
self._start_processing_time = 0
|
|
||||||
return MetricsFrame(data=[processing])
|
|
||||||
|
|
||||||
async def start_llm_usage_metrics(self, tokens: LLMTokenUsage):
|
|
||||||
logger.debug(
|
|
||||||
f"{self._processor_name()} prompt tokens: {tokens.prompt_tokens}, completion tokens: {tokens.completion_tokens}")
|
|
||||||
value = LLMUsageMetricsData(
|
|
||||||
processor=self._processor_name(),
|
|
||||||
model=self._model_name(),
|
|
||||||
value=tokens)
|
|
||||||
return MetricsFrame(data=[value])
|
|
||||||
|
|
||||||
async def start_tts_usage_metrics(self, text: str):
|
|
||||||
characters = TTSUsageMetricsData(
|
|
||||||
processor=self._processor_name(),
|
|
||||||
model=self._model_name(),
|
|
||||||
value=len(text))
|
|
||||||
logger.debug(f"{self._processor_name()} usage characters: {characters.value}")
|
|
||||||
return MetricsFrame(data=[characters])
|
|
||||||
|
|
||||||
|
|
||||||
class FrameProcessor:
|
class FrameProcessor:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
sync: bool = True,
|
metrics: FrameProcessorMetrics | None = None,
|
||||||
loop: asyncio.AbstractEventLoop | None = None,
|
sync: bool = True,
|
||||||
**kwargs):
|
loop: asyncio.AbstractEventLoop | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
self.id: int = obj_id()
|
self.id: int = obj_id()
|
||||||
self.name = name or f"{self.__class__.__name__}#{obj_count(self)}"
|
self.name = name or f"{self.__class__.__name__}#{obj_count(self)}"
|
||||||
self._parent: "FrameProcessor" | None = None
|
self._parent: "FrameProcessor" | None = None
|
||||||
@@ -129,7 +59,8 @@ class FrameProcessor:
|
|||||||
self._report_only_initial_ttfb = False
|
self._report_only_initial_ttfb = False
|
||||||
|
|
||||||
# Metrics
|
# Metrics
|
||||||
self._metrics = FrameProcessorMetrics(name=self.name)
|
self._metrics = metrics or FrameProcessorMetrics()
|
||||||
|
self._metrics.set_processor_name(self.name)
|
||||||
|
|
||||||
# Every processor in Pipecat should only output frames from a single
|
# Every processor in Pipecat should only output frames from a single
|
||||||
# task. This avoid problems like audio overlapping. System frames are
|
# task. This avoid problems like audio overlapping. System frames are
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from pipecat.frames.frames import (
|
|||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
TextFrame)
|
TextFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -20,9 +21,7 @@ try:
|
|||||||
from langchain_core.messages import AIMessageChunk
|
from langchain_core.messages import AIMessageChunk
|
||||||
from langchain_core.runnables import Runnable
|
from langchain_core.runnables import Runnable
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.exception(
|
logger.exception("In order to use Langchain, you need to `pip install pipecat-ai[langchain]`. ")
|
||||||
"In order to use Langchain, you need to `pip install pipecat-ai[langchain]`. "
|
|
||||||
)
|
|
||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import asyncio
|
|||||||
|
|
||||||
from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Union
|
from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Union
|
||||||
from pydantic import BaseModel, Field, PrivateAttr, ValidationError
|
from pydantic import BaseModel, Field, PrivateAttr, ValidationError
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
BotInterruptionFrame,
|
BotInterruptionFrame,
|
||||||
BotStartedSpeakingFrame,
|
BotStartedSpeakingFrame,
|
||||||
BotStoppedSpeakingFrame,
|
BotStoppedSpeakingFrame,
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
|
DataFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
ErrorFrame,
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
@@ -24,7 +26,8 @@ from pipecat.frames.frames import (
|
|||||||
TransportMessageFrame,
|
TransportMessageFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
UserStoppedSpeakingFrame)
|
UserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
@@ -39,8 +42,9 @@ ActionResult = Union[bool, int, float, str, list, dict]
|
|||||||
class RTVIServiceOption(BaseModel):
|
class RTVIServiceOption(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
type: Literal["bool", "number", "string", "array", "object"]
|
type: Literal["bool", "number", "string", "array", "object"]
|
||||||
handler: Callable[["RTVIProcessor", str, "RTVIServiceOptionConfig"],
|
handler: Callable[["RTVIProcessor", str, "RTVIServiceOptionConfig"], Awaitable[None]] = Field(
|
||||||
Awaitable[None]] = Field(exclude=True)
|
exclude=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RTVIService(BaseModel):
|
class RTVIService(BaseModel):
|
||||||
@@ -70,8 +74,9 @@ class RTVIAction(BaseModel):
|
|||||||
action: str
|
action: str
|
||||||
arguments: List[RTVIActionArgument] = []
|
arguments: List[RTVIActionArgument] = []
|
||||||
result: Literal["bool", "number", "string", "array", "object"]
|
result: Literal["bool", "number", "string", "array", "object"]
|
||||||
handler: Callable[["RTVIProcessor", str, Dict[str, Any]],
|
handler: Callable[["RTVIProcessor", str, Dict[str, Any]], Awaitable[ActionResult]] = Field(
|
||||||
Awaitable[ActionResult]] = Field(exclude=True)
|
exclude=True
|
||||||
|
)
|
||||||
_arguments_dict: Dict[str, RTVIActionArgument] = PrivateAttr(default={})
|
_arguments_dict: Dict[str, RTVIActionArgument] = PrivateAttr(default={})
|
||||||
|
|
||||||
def model_post_init(self, __context: Any) -> None:
|
def model_post_init(self, __context: Any) -> None:
|
||||||
@@ -116,12 +121,19 @@ class RTVIActionRun(BaseModel):
|
|||||||
arguments: Optional[List[RTVIActionRunArgument]] = None
|
arguments: Optional[List[RTVIActionRunArgument]] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RTVIActionFrame(DataFrame):
|
||||||
|
rtvi_action_run: RTVIActionRun
|
||||||
|
message_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class RTVIMessage(BaseModel):
|
class RTVIMessage(BaseModel):
|
||||||
label: Literal["rtvi-ai"] = "rtvi-ai"
|
label: Literal["rtvi-ai"] = "rtvi-ai"
|
||||||
type: str
|
type: str
|
||||||
id: str
|
id: str
|
||||||
data: Optional[Dict[str, Any]] = None
|
data: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Pipecat -> Client responses and messages.
|
# Pipecat -> Client responses and messages.
|
||||||
#
|
#
|
||||||
@@ -268,12 +280,13 @@ class RTVIProcessorParams(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RTVIProcessor(FrameProcessor):
|
class RTVIProcessor(FrameProcessor):
|
||||||
|
def __init__(
|
||||||
def __init__(self,
|
self,
|
||||||
*,
|
*,
|
||||||
config: RTVIConfig = RTVIConfig(config=[]),
|
config: RTVIConfig = RTVIConfig(config=[]),
|
||||||
params: RTVIProcessorParams = RTVIProcessorParams(),
|
params: RTVIProcessorParams = RTVIProcessorParams(),
|
||||||
**kwargs):
|
**kwargs,
|
||||||
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(sync=False, **kwargs)
|
||||||
self._config = config
|
self._config = config
|
||||||
self._params = params
|
self._params = params
|
||||||
@@ -310,25 +323,23 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
await self._maybe_send_bot_ready()
|
await self._maybe_send_bot_ready()
|
||||||
|
|
||||||
async def handle_function_call(
|
async def handle_function_call(
|
||||||
self,
|
self,
|
||||||
function_name: str,
|
function_name: str,
|
||||||
tool_call_id: str,
|
tool_call_id: str,
|
||||||
arguments: dict,
|
arguments: dict,
|
||||||
llm: FrameProcessor,
|
llm: FrameProcessor,
|
||||||
context: OpenAILLMContext,
|
context: OpenAILLMContext,
|
||||||
result_callback):
|
result_callback,
|
||||||
|
):
|
||||||
fn = RTVILLMFunctionCallMessageData(
|
fn = RTVILLMFunctionCallMessageData(
|
||||||
function_name=function_name,
|
function_name=function_name, tool_call_id=tool_call_id, args=arguments
|
||||||
tool_call_id=tool_call_id,
|
)
|
||||||
args=arguments)
|
|
||||||
message = RTVILLMFunctionCallMessage(data=fn)
|
message = RTVILLMFunctionCallMessage(data=fn)
|
||||||
await self._push_transport_message(message, exclude_none=False)
|
await self._push_transport_message(message, exclude_none=False)
|
||||||
|
|
||||||
async def handle_function_call_start(
|
async def handle_function_call_start(
|
||||||
self,
|
self, function_name: str, llm: FrameProcessor, context: OpenAILLMContext
|
||||||
function_name: str,
|
):
|
||||||
llm: FrameProcessor,
|
|
||||||
context: OpenAILLMContext):
|
|
||||||
fn = RTVILLMFunctionCallStartMessageData(function_name=function_name)
|
fn = RTVILLMFunctionCallStartMessageData(function_name=function_name)
|
||||||
message = RTVILLMFunctionCallStartMessage(data=fn)
|
message = RTVILLMFunctionCallStartMessage(data=fn)
|
||||||
await self._push_transport_message(message, exclude_none=False)
|
await self._push_transport_message(message, exclude_none=False)
|
||||||
@@ -357,10 +368,14 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
# finish and the task finishes when EndFrame is processed.
|
# finish and the task finishes when EndFrame is processed.
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
await self._stop(frame)
|
await self._stop(frame)
|
||||||
elif isinstance(frame, UserStartedSpeakingFrame) or isinstance(frame, UserStoppedSpeakingFrame):
|
elif isinstance(frame, UserStartedSpeakingFrame) or isinstance(
|
||||||
|
frame, UserStoppedSpeakingFrame
|
||||||
|
):
|
||||||
await self._handle_interruptions(frame)
|
await self._handle_interruptions(frame)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
elif isinstance(frame, BotStartedSpeakingFrame) or isinstance(frame, BotStoppedSpeakingFrame):
|
elif isinstance(frame, BotStartedSpeakingFrame) or isinstance(
|
||||||
|
frame, BotStoppedSpeakingFrame
|
||||||
|
):
|
||||||
await self._handle_bot_speaking(frame)
|
await self._handle_bot_speaking(frame)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
# Data frames
|
# Data frames
|
||||||
@@ -369,6 +384,8 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
elif isinstance(frame, TransportMessageFrame):
|
elif isinstance(frame, TransportMessageFrame):
|
||||||
await self._message_queue.put(frame)
|
await self._message_queue.put(frame)
|
||||||
|
elif isinstance(frame, RTVIActionFrame):
|
||||||
|
await self._handle_action(frame.message_id, frame.rtvi_action_run)
|
||||||
# Other frames
|
# Other frames
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
@@ -393,8 +410,8 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
|
|
||||||
async def _push_transport_message(self, model: BaseModel, exclude_none: bool = True):
|
async def _push_transport_message(self, model: BaseModel, exclude_none: bool = True):
|
||||||
frame = TransportMessageFrame(
|
frame = TransportMessageFrame(
|
||||||
message=model.model_dump(exclude_none=exclude_none),
|
message=model.model_dump(exclude_none=exclude_none), urgent=True
|
||||||
urgent=True)
|
)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
async def _handle_transcriptions(self, frame: Frame):
|
async def _handle_transcriptions(self, frame: Frame):
|
||||||
@@ -405,17 +422,15 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
if isinstance(frame, TranscriptionFrame):
|
if isinstance(frame, TranscriptionFrame):
|
||||||
message = RTVITranscriptionMessage(
|
message = RTVITranscriptionMessage(
|
||||||
data=RTVITranscriptionMessageData(
|
data=RTVITranscriptionMessageData(
|
||||||
text=frame.text,
|
text=frame.text, user_id=frame.user_id, timestamp=frame.timestamp, final=True
|
||||||
user_id=frame.user_id,
|
)
|
||||||
timestamp=frame.timestamp,
|
)
|
||||||
final=True))
|
|
||||||
elif isinstance(frame, InterimTranscriptionFrame):
|
elif isinstance(frame, InterimTranscriptionFrame):
|
||||||
message = RTVITranscriptionMessage(
|
message = RTVITranscriptionMessage(
|
||||||
data=RTVITranscriptionMessageData(
|
data=RTVITranscriptionMessageData(
|
||||||
text=frame.text,
|
text=frame.text, user_id=frame.user_id, timestamp=frame.timestamp, final=False
|
||||||
user_id=frame.user_id,
|
)
|
||||||
timestamp=frame.timestamp,
|
)
|
||||||
final=False))
|
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
await self._push_transport_message(message)
|
await self._push_transport_message(message)
|
||||||
@@ -539,10 +554,11 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
function_name=data.function_name,
|
function_name=data.function_name,
|
||||||
tool_call_id=data.tool_call_id,
|
tool_call_id=data.tool_call_id,
|
||||||
arguments=data.arguments,
|
arguments=data.arguments,
|
||||||
result=data.result)
|
result=data.result,
|
||||||
|
)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
async def _handle_action(self, request_id: str, data: RTVIActionRun):
|
async def _handle_action(self, request_id: str | None, data: RTVIActionRun):
|
||||||
action_id = self._action_id(data.service, data.action)
|
action_id = self._action_id(data.service, data.action)
|
||||||
if action_id not in self._registered_actions:
|
if action_id not in self._registered_actions:
|
||||||
await self._send_error_response(request_id, f"Action {action_id} not registered")
|
await self._send_error_response(request_id, f"Action {action_id} not registered")
|
||||||
@@ -553,8 +569,11 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
for arg in data.arguments:
|
for arg in data.arguments:
|
||||||
arguments[arg.name] = arg.value
|
arguments[arg.name] = arg.value
|
||||||
result = await action.handler(self, action.service, arguments)
|
result = await action.handler(self, action.service, arguments)
|
||||||
message = RTVIActionResponse(id=request_id, data=RTVIActionResponseData(result=result))
|
# Only send a response if request_id is present. Things that don't care about
|
||||||
await self._push_transport_message(message)
|
# action responses (such as webhooks) don't set a request_id
|
||||||
|
if request_id:
|
||||||
|
message = RTVIActionResponse(id=request_id, data=RTVIActionResponseData(result=result))
|
||||||
|
await self._push_transport_message(message)
|
||||||
|
|
||||||
async def _maybe_send_bot_ready(self):
|
async def _maybe_send_bot_ready(self):
|
||||||
if self._pipeline_started and self._client_ready:
|
if self._pipeline_started and self._client_ready:
|
||||||
@@ -567,9 +586,8 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
|
|
||||||
message = RTVIBotReady(
|
message = RTVIBotReady(
|
||||||
id=self._client_ready_id,
|
id=self._client_ready_id,
|
||||||
data=RTVIBotReadyData(
|
data=RTVIBotReadyData(version=RTVI_PROTOCOL_VERSION, config=self._config.config),
|
||||||
version=RTVI_PROTOCOL_VERSION,
|
)
|
||||||
config=self._config.config))
|
|
||||||
await self._push_transport_message(message)
|
await self._push_transport_message(message)
|
||||||
|
|
||||||
async def _send_error_frame(self, frame: ErrorFrame):
|
async def _send_error_frame(self, frame: ErrorFrame):
|
||||||
|
|||||||
@@ -15,20 +15,23 @@ from pipecat.frames.frames import (
|
|||||||
OutputAudioRawFrame,
|
OutputAudioRawFrame,
|
||||||
OutputImageRawFrame,
|
OutputImageRawFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
SystemFrame)
|
SystemFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gst', '1.0')
|
|
||||||
gi.require_version('GstApp', '1.0')
|
gi.require_version("Gst", "1.0")
|
||||||
|
gi.require_version("GstApp", "1.0")
|
||||||
from gi.repository import Gst, GstApp
|
from gi.repository import Gst, GstApp
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error(
|
logger.error(
|
||||||
"In order to use GStreamer, you need to `pip install pipecat-ai[gstreamer]`. Also, you need to install GStreamer in your system.")
|
"In order to use GStreamer, you need to `pip install pipecat-ai[gstreamer]`. Also, you need to install GStreamer in your system."
|
||||||
|
)
|
||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +123,8 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
audioresample = Gst.ElementFactory.make("audioresample", None)
|
audioresample = Gst.ElementFactory.make("audioresample", None)
|
||||||
audiocapsfilter = Gst.ElementFactory.make("capsfilter", None)
|
audiocapsfilter = Gst.ElementFactory.make("capsfilter", None)
|
||||||
audiocaps = Gst.Caps.from_string(
|
audiocaps = Gst.Caps.from_string(
|
||||||
f"audio/x-raw,format=S16LE,rate={self._out_params.audio_sample_rate},channels={self._out_params.audio_channels},layout=interleaved")
|
f"audio/x-raw,format=S16LE,rate={self._out_params.audio_sample_rate},channels={self._out_params.audio_channels},layout=interleaved"
|
||||||
|
)
|
||||||
audiocapsfilter.set_property("caps", audiocaps)
|
audiocapsfilter.set_property("caps", audiocaps)
|
||||||
appsink_audio = Gst.ElementFactory.make("appsink", None)
|
appsink_audio = Gst.ElementFactory.make("appsink", None)
|
||||||
appsink_audio.set_property("emit-signals", True)
|
appsink_audio.set_property("emit-signals", True)
|
||||||
@@ -152,7 +156,8 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
videoscale = Gst.ElementFactory.make("videoscale", None)
|
videoscale = Gst.ElementFactory.make("videoscale", None)
|
||||||
videocapsfilter = Gst.ElementFactory.make("capsfilter", None)
|
videocapsfilter = Gst.ElementFactory.make("capsfilter", None)
|
||||||
videocaps = Gst.Caps.from_string(
|
videocaps = Gst.Caps.from_string(
|
||||||
f"video/x-raw,format=RGB,width={self._out_params.video_width},height={self._out_params.video_height}")
|
f"video/x-raw,format=RGB,width={self._out_params.video_width},height={self._out_params.video_height}"
|
||||||
|
)
|
||||||
videocapsfilter.set_property("caps", videocaps)
|
videocapsfilter.set_property("caps", videocaps)
|
||||||
|
|
||||||
appsink_video = Gst.ElementFactory.make("appsink", None)
|
appsink_video = Gst.ElementFactory.make("appsink", None)
|
||||||
@@ -182,9 +187,11 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
def _appsink_audio_new_sample(self, appsink: GstApp.AppSink):
|
def _appsink_audio_new_sample(self, appsink: GstApp.AppSink):
|
||||||
buffer = appsink.pull_sample().get_buffer()
|
buffer = appsink.pull_sample().get_buffer()
|
||||||
(_, info) = buffer.map(Gst.MapFlags.READ)
|
(_, info) = buffer.map(Gst.MapFlags.READ)
|
||||||
frame = OutputAudioRawFrame(audio=info.data,
|
frame = OutputAudioRawFrame(
|
||||||
sample_rate=self._out_params.audio_sample_rate,
|
audio=info.data,
|
||||||
num_channels=self._out_params.audio_channels)
|
sample_rate=self._out_params.audio_sample_rate,
|
||||||
|
num_channels=self._out_params.audio_channels,
|
||||||
|
)
|
||||||
asyncio.run_coroutine_threadsafe(self.push_frame(frame), self.get_event_loop())
|
asyncio.run_coroutine_threadsafe(self.push_frame(frame), self.get_event_loop())
|
||||||
buffer.unmap(info)
|
buffer.unmap(info)
|
||||||
return Gst.FlowReturn.OK
|
return Gst.FlowReturn.OK
|
||||||
@@ -195,7 +202,8 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
frame = OutputImageRawFrame(
|
frame = OutputImageRawFrame(
|
||||||
image=info.data,
|
image=info.data,
|
||||||
size=(self._out_params.video_width, self._out_params.video_height),
|
size=(self._out_params.video_width, self._out_params.video_height),
|
||||||
format="RGB")
|
format="RGB",
|
||||||
|
)
|
||||||
asyncio.run_coroutine_threadsafe(self.push_frame(frame), self.get_event_loop())
|
asyncio.run_coroutine_threadsafe(self.push_frame(frame), self.get_event_loop())
|
||||||
buffer.unmap(info)
|
buffer.unmap(info)
|
||||||
return Gst.FlowReturn.OK
|
return Gst.FlowReturn.OK
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ class IdleFrameProcessor(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
|
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
|
||||||
timeout: float,
|
timeout: float,
|
||||||
types: List[type] = [],
|
types: List[type] = [],
|
||||||
**kwargs):
|
**kwargs,
|
||||||
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(sync=False, **kwargs)
|
||||||
|
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user