Skip to content

Commit

Permalink
Merge pull request #4 from pipecat-ai/jpt/services
Browse files Browse the repository at this point in the history
Services table for managing services providers and API keys
  • Loading branch information
jptaylor authored Oct 24, 2024
2 parents d857ad3 + b971aee commit 3b5b0e3
Show file tree
Hide file tree
Showing 30 changed files with 1,452 additions and 299 deletions.
72 changes: 46 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,37 @@ source venv/bin/activate # ... or OS specific activation
pip install -r server/dev-requirements.txt
```

#### 2. Create a local .env
#### 2. Create a database

```shell
cp server/env.example server/.env
```
Sesame uses data storage for users, workspace settings and conversation history. The current default schema assumes Postgres.

You must set the following:
Create a new local database: `psql -U postgres -c "CREATE DATABASE sesame;"`

`SESAME_DATABASE_ADMIN_URL`
... or alternatively, use a hosted database provider such as [Render](www.render.com) or [Supabase](www.supabase.com).

Database superuser credentials (i.e. Supabase URI on port `5432`.) _Note: Must support asyncpg or equivalent asychnronous driver._

> 🛑 Ensure you are using a database URL on a session port (typically `5432`) If you are using Supabase, the URL provided in the settings panel defaults to "transaction mode". See [Database setup](#database-setup) for details.
#### 3. Create a local .env

You database URL should look something like:
```shell
cp server/env.example server/.env
```

You must set the following:

```bash
SESAME_DATABASE_ADMIN_URL="postgresql://postgres.ID:PASSWORD@aws-0-us-west-1.pooler.supabase.com:5432/postgres"
# Note port 5432, not 6543
SESAME_APP_SECRET # For data encryption
SESAME_DATABASE_ADMIN_USER # Privileged database user
SESAME_DATABASE_ADMIN_PASSWORD # Privileged user password
SESAME_DATABASE_NAME # E.g. sesame
SESAME_DATABASE_HOST # E.g. localhost
SESAME_DATABASE_PORT # E.g. 5432
```

#### 3. Create database roles and schema
_Note: Your database must support asyncpg or equivalent asychnronous driver._

> 🛑 Ensure you are using a database that accepts session mode typically available on port `5432`. If you are using Supabase, the URL provided in the settings panel defaults to "transaction mode". See [Database setup](#database-setup) for details.
#### 4. Create database roles and schema

From the root of the project, run the schema script found in [scripts/run_shema.sh](./scripts/run_schema.sh)

Expand All @@ -61,15 +70,13 @@ bash scripts/run_schema.sh

Note: the `run_schema.sh` script requires Postgres to run. Install the necessary package for your system (e.g. `brew install postgresql` for MacOS).

If the schema runs correctly, the script will print out a non-superuser URL to the terminal which you should add to your environment.

Add `SESAME_DATABASE_URL` in `server/.env` with the output of the script.
If the schema runs correctly, the script will print out a non-superuser user and password.

> 🛑 If your admin database password includes "@" characters, the bash script may fail to correctly replace the `SESAME_DATABASE_URL` password using `sed`. In this scenario, please manually adjust.
Edit `SESAME_DATABASE_USER` and `SESAME_DATABASE_PASSWORD` in `server/.env` with the output of the script.

For more information about database configuration, read [here](#database-setup)

#### 4. Create a user
#### 5. Create a user

From the root of the project still, create a user account and password from [scripts/create_user.sh](./scripts/create_user.sh).

Expand All @@ -79,7 +86,7 @@ bash scripts/create_user.sh

Running this script will create a user account in your database. Make a note of your username and password; the password will be encrypted and not recoverable later.

#### 5. Run the Sesame server and generate access token
#### 6. Run the Sesame server and generate access token

```shell
cd server/
Expand All @@ -90,14 +97,22 @@ You should see a URL in your terminal window to visit, for example `http://127.0

<img alt="open-sesame-dashboard" width="280px" height="auto" src="./docs/sesame-dashboard.png">

Log in with the user name and password you set in step 4.
Log in with the user name and password you set in step 5.

Now, create a new access token to authenticate web requests in any of the Open Sesame clients. For more information, see [authentication](./docs/authentication.md).

#### 6. Create your first workspace
#### 7. Run the tests to check your configuration

```bash
cd server/
PYTHONPATH=. pytest tests/ -s -v
```

#### 8. Create your first workspace

Follow the [workspace creation steps](#create-your-first-workspace), and run a [client](#run-a-client-app) of your choosing.


## Overview

### Project requirements
Expand All @@ -111,20 +126,25 @@ _Note: Sesame bots are configured to use [Daily](https://www.daily.co) as a tran

### Database setup

Sesame works with most types of database. If you are using Supabase, there are no additional setup steps required. If not, you may need to install additional extensions specified in [database/schema.sql](./database/schema.sql).
Sesame requires a Postgres database (support for other database types, such as SQLite, coming soon). You may need to install additional extensions specified in [database/schema.sql](./database/schema.sql).

#### 1. Update your .env

Update`SESAME_DATABASE_ADMIN_URL` (admin) in `server/.env`.
Update the non-optional `SESAME_DATABASE_*` variables in `server/.env`.

For Supabase can find your URL here: https://supabase.com/dashboard/project/[YOUR_PROJECT]/settings/database. **Note: be sure to use the URI for `Mode: session`.**
For Supabase, for example, you can find credentials URL here: https://supabase.com/dashboard/project/[YOUR_PROJECT]/settings/database. **Note: be sure to use the URI for `Mode: session` to get the correct port for session mode.**

![](./docs/supabaseurl.png)

Your database URI should look something like this:
Your database credentials should look something like this:

```bash
SESAME_DATABASE_ADMIN_URL=postgresql://postgres.user:pass@region.pooler.supabase.com:5432/postgres
SESAME_DATABASE_ADMIN_USER="postgres"
SESAME_DATABASE_ADMIN_PASSWORD="password"
SESAME_DATABASE_NAME="postgres"
SESAME_DATABASE_HOST="region.pooler.supabase.com"
# - Use a session port (typically 5432)
SESAME_DATABASE_PORT=5432
```

#### 2. Apply database schema
Expand All @@ -135,7 +155,7 @@ This script will create the necessary tables, functions and triggers, as well ec

#### 3. Update your .env with the public database URL

Update`SESAME_DATABASE_URL` (public) in `server/.env` with the randomly generated password created by the `run_schema` script.
Update`SESAME_DATABASE_USER` and `SESAME_DATABASE_PASSWORD` (public) in `server/.env` with the randomly generated password created by the `run_schema` script.

Alternatively, you can set this yourself in `database/schema.sql` by changing the `%%REPLACED%%` input near the bottom.

Expand Down
61 changes: 61 additions & 0 deletions database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,62 @@ USING (
)
);

-- ========================
-- Services Table
-- ========================
CREATE TABLE IF NOT EXISTS services (
service_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(64) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
workspace_id UUID REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
service_type VARCHAR(255) NOT NULL,
service_provider VARCHAR(255) NOT NULL,
api_key TEXT NOT NULL,
options JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Prevent duplicate providers within the same workspace
CONSTRAINT unique_provider_per_workspace UNIQUE (workspace_id, service_provider)
);

-- Indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_services_user_id ON services(user_id);
CREATE INDEX IF NOT EXISTS idx_services_workspace_id ON services(workspace_id);
CREATE INDEX IF NOT EXISTS idx_services_service_type ON services(service_type);

-- Create a unique partial index for user-level services
CREATE UNIQUE INDEX IF NOT EXISTS unique_provider_per_user
ON services (user_id, service_provider)
WHERE workspace_id IS NULL;

-- Enable row-level security
ALTER TABLE services ENABLE ROW LEVEL SECURITY;

-- Policy: Users can only view their own services
CREATE POLICY services_select_policy
ON services
FOR SELECT
USING (user_id = get_current_user_id());

-- Policy: Users can only insert services for themselves
CREATE POLICY services_insert_policy
ON services
FOR INSERT
WITH CHECK (user_id = get_current_user_id());

-- Policy: Users can only update their own services
CREATE POLICY services_update_policy
ON services
FOR UPDATE
USING (user_id = get_current_user_id());

-- Policy: Users can only delete their own services
CREATE POLICY services_delete_policy
ON services
FOR DELETE
USING (user_id = get_current_user_id());


-- ========================
-- Functions and Triggers
-- ========================
Expand Down Expand Up @@ -310,6 +366,11 @@ BEFORE UPDATE OF content ON messages
FOR EACH ROW
EXECUTE PROCEDURE update_updated_at_column();

CREATE TRIGGER trg_services_updated_at
BEFORE UPDATE ON services
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

-- Function to update 'workspaces.updated_at' when a new conversation is created
CREATE OR REPLACE FUNCTION update_workspace_updated_at()
RETURNS trigger AS $$
Expand Down
7 changes: 4 additions & 3 deletions scripts/create_user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ else
exit 1
fi

# Check if SESAME_DATABASE_ADMIN_URL (for anon_user) is set
if [ -z "$SESAME_DATABASE_ADMIN_URL" ]; then
echo "SESAME_DATABASE_ADMIN_URL is not set in .env file. Please set it."
# Check for database credetials
if [ -z "$SESAME_DATABASE_ADMIN_USER" ] || [ -z "$SESAME_DATABASE_ADMIN_PASSWORD" ] || [ -z "$SESAME_DATABASE_NAME" ] || [ -z "$SESAME_DATABASE_HOST" ] || [ -z "$SESAME_DATABASE_PORT" ]; then
echo "One or more required environment variables are not set in .env file. Please set them."
exit 1
fi

SESAME_DATABASE_ADMIN_URL="$SESAME_DATABASE_PROTOCOL://$SESAME_DATABASE_ADMIN_USER:$SESAME_DATABASE_ADMIN_PASSWORD@$SESAME_DATABASE_HOST:$SESAME_DATABASE_PORT/$SESAME_DATABASE_NAME"

# Prompt for username
while true; do
Expand Down
10 changes: 2 additions & 8 deletions scripts/example_workspace_config.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
{
"title": "My First Workspace",
"config": {
"api_keys": {
"cartesia": "${CARTESIA_API_KEY}",
"daily": "${DAILY_API_KEY}",
"deepgram": "${DEEPGRAM_API_KEY}",
"openai": "${OPENAI_API_KEY}",
"together": "${TOGETHER_API_KEY}"
},
"config": [
{
"options": [
Expand Down Expand Up @@ -75,7 +68,8 @@
"services": {
"llm": "together",
"stt": "deepgram",
"tts": "cartesia"
"tts": "cartesia",
"transport": "daily"
}
}
}
19 changes: 11 additions & 8 deletions scripts/run_schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ else
exit 1
fi

# Check if SESAME_DATABASE_ADMIN_URL (for anon_user) is set
if [ -z "$SESAME_DATABASE_ADMIN_URL" ]; then
echo "SESAME_DATABASE_ADMIN_URL is not set in .env file. Please set it."
# Check if necessary database vars (for admin user) is set
if [ -z "$SESAME_DATABASE_ADMIN_USER" ] || [ -z "$SESAME_DATABASE_ADMIN_PASSWORD" ] || [ -z "$SESAME_DATABASE_NAME" ] || [ -z "$SESAME_DATABASE_HOST" ] || [ -z "$SESAME_DATABASE_PORT" ]; then
echo "One or more required environment variables are not set in .env file. Please set them."
exit 1
fi

SESAME_USER_ROLE=${SESAME_DATABASE_USER:-"sesame"}

# Create a database URL
SESAME_DATABASE_ADMIN_URL="$SESAME_DATABASE_PROTOCOL://$SESAME_DATABASE_ADMIN_USER:$SESAME_DATABASE_ADMIN_PASSWORD@$SESAME_DATABASE_HOST:$SESAME_DATABASE_PORT/$SESAME_DATABASE_NAME"

# Generate a random password for the anon user
ANON_USER_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9')

if [ -z "$ANON_USER_PASSWORD" ]; then
echo "Failed to generate password for anon_user from SESAME_DATABASE_URL."
echo "Failed to generate password for anon_user."
exit 1
fi

# Create the SESAME_DATABASE_URL using the new role and password
SESAME_DATABASE_URL=$(echo "$SESAME_DATABASE_ADMIN_URL" | sed -E "s|(postgresql://)[^.]+(\..+)?(:[^@]+)@|\1$SESAME_DATABASE_USER\2:$ANON_USER_PASSWORD@|")

# Run schema with password substitution directly, passing the result to psql
echo "Running schema on $SESAME_DATABASE_ADMIN_URL..."

Expand All @@ -45,5 +47,6 @@ fi

echo "--------------------------------------------------"
echo -e "\033[32mUpdate your server/.env to include:"
echo -e "SESAME_DATABASE_URL=${SESAME_DATABASE_URL}\033[0m"
echo -e "SESAME_DATABASE_USER=\"${SESAME_USER_ROLE}\""
echo -e "SESAME_DATABASE_PASSWORD=\"${ANON_USER_PASSWORD}\"\033[0m"
echo "--------------------------------------------------"
31 changes: 24 additions & 7 deletions server/bots/http/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from bots.context_storage import PersistentContextStorage
from bots.http.frame_serializer import BotFrameSerializer
from bots.rtvi import create_rtvi_processor
from bots.service_factory import service_factory_get
from bots.types import BotConfig, BotParams
from common.models import Service
from common.service_factory import ServiceFactory, ServiceType
from fastapi import HTTPException, status
from openai._types import NOT_GIVEN
from pipecat.pipeline.pipeline import Pipeline
Expand All @@ -24,19 +25,35 @@


async def http_bot_pipeline(
params: BotParams, config: BotConfig, messages, db: AsyncSession, language_code: str = "english"
params: BotParams,
config: BotConfig,
services: list[Service],
messages,
db: AsyncSession,
language_code: str = "english",
) -> Tuple[AsyncGenerator[Any, None], Any]:
api_keys = config.api_keys
services = config.services
service_options = config.service_options

if "llm" not in services:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Service `llm` not available in provided services.",
)

llm = cast(LLMService, service_factory_get(services["llm"], api_keys, service_options))
try:
llm = cast(
LLMService,
ServiceFactory.get_service(
services["llm"].service_provider,
ServiceType.ServiceLLM,
services["llm"].api_key,
services["llm"].options,
),
)

except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error creating LLM service: {e}",
)

tools = NOT_GIVEN
context = OpenAILLMContext(messages, tools)
Expand Down
2 changes: 1 addition & 1 deletion server/bots/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
python-deepcompare
pipecat-ai[daily,anthropic,cartesia,deepgram,openai,silero,together,websocket,elevenlabs,playht,azure]==0.0.45
pipecat-ai[daily,anthropic,cartesia,deepgram,openai,silero,together,websocket,elevenlabs,playht,azure]==0.0.47
2 changes: 1 addition & 1 deletion server/bots/rtvi_services.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import List

from pipecat.audio.vad.vad_analyzer import VADParams
from pipecat.frames.frames import (
ErrorFrame,
LLMUpdateSettingsFrame,
Expand All @@ -15,7 +16,6 @@
RTVIServiceOption,
RTVIServiceOptionConfig,
)
from pipecat.vad.vad_analyzer import VADParams


async def register_rtvi_services(rtvi: RTVIProcessor, user_aggregator: LLMUserContextAggregator):
Expand Down
Loading

0 comments on commit 3b5b0e3

Please sign in to comment.